【问题标题】:How to detect ambiguous method calls that would cause a ClassCastException in Java 8?如何检测会在 Java 8 中导致 ClassCastException 的模棱两可的方法调用?
【发布时间】:2015-05-28 21:58:17
【问题描述】:

我们目前正在将应用程序从 Java 7 迁移到 Java 8。在修复了一些编译问题后,我偶然发现了一个类似于以下问题的问题:ClassCast Error: Java 7 vs Java 8

总而言之,这是一个显示问题的示例代码:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception 
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(String param) {
        // do some computation based on param...
        return (T) result; // actual return type only depends on param so the caller knows what to expect
    }
}

我们的想法是,我们会强调调用者知道预期的类型,这样可以避免他显式转换(我并不是说这是个好主意……)。在很多情况下,调用者只需要一个Object,所以根本没有隐式转换。

如上述问题所述,String.valueOf 示例在 Java 7 中运行良好,因为没有类型推断,因此假定为 Object。现在在 Java 8 中,编译器会选择最具体的类型(此处为 char[]),这会在运行时生成 ClastCastException

问题是我们有大约 350 次调用此 getVal 方法。 有没有办法检测 Java 7 和 Java 8 之间不同的重载方法调用?检测 Java 8 编译器何时会选择与 Java 7 编译器不同的方法。

【问题讨论】:

  • 我们有一个类似的方法,也被数百个地方调用过。我替换了它,引入了额外的 Class&lt;T&gt; clazz 参数。通过这种方式,您可以进行显式错误处理:返回 null 或抛出一些更具体的异常。我只是手动花费了大约半个小时。我建议你也这样做。我不知道如何使其自动化,因此这不能作为您问题的答案。
  • 有一个简单的答案:不要抑制“未经检查的”警告。他们已经告诉你你的代码是错误的。如果你现在想修复你的代码已经太晚了,只需搜索出现的@SuppressWarnings("unchecked")...
  • @Holger 你能告诉我的同事,当他在 2011 年实施这个时? ;-) 我一开始就知道这是一个坏主意(尽管不像现在在 Java 8 中那么糟糕)。另外,方法调用本身没有@SuppressWarnings,它只在getVal的声明中。因此,在此处删除此注释不会显示任何有问题的用法。
  • 好吧,getVal 的声明方式的问题。因此,寻找注释将引导您到正确的位置。然后,修复签名当然会在调用者处引发编译器错误,因此很容易找到它们。除非您使用一次性重构签名和调用者的 IDE,否则这是一个可行的替代方案。当然,一旦注释将您引导到有问题的方法,如果您使用体面的 IDE,即使不更改它也可以找到它的调用者。所以有必要的工具......
  • 我认为这确实是要走的路。这并不能真正回答这个问题,因为与少数有问题的调用相比,我必须更改更多的代码,但至少它会解决我们的问题。此外,似乎将返回类型更改为Object 将自动修复有问题的调用,因此具有讽刺意味的是那里不会有任何更改(我可能永远不会知道它们在哪里:-)。

标签: java generics java-8 classcastexception overloading


【解决方案1】:

更好的选择是:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(T param) {
        // do some computation based on param...
        return   param; // actual return type only depends on param so the caller knows what to expect
    }
}

适用于 Java 7 和 Java 8。

【讨论】:

  • 这不是替代方案,因为您强制要求返回类型与参数相同(在我的情况下是 String,我无法轻松更改它) .此外,问题是确定 Java 7 和 8 之间重载方法调用不同的情况,以避免全部更改或手动搜索它们。
【解决方案2】:

最终解决方案是将getVal()改为返回Object

    public static Object getVal(String param) {
        // do some computation based on param...
        return result;
    }

并添加第二个方法,该方法也将所需的类作为参数:

    public static <T> T getVal(String param, Class<T> clazz) {
        return clazz.cast(getVal(param));
    }

然后通过添加适当的类参数来修复所有编译问题(调用者没想到Object)。

添加一个演员也可以,但它会导致很多无条件演员的警告。无条件强制转换实际上仍然存在(通过clazz 参数),但这允许在使用带有 2 个参数的方法时轻松识别所有需要强制转换的调用者。

此外——这对于这种情况非常具体——似乎param 本身通常是对某些TypedParam&lt;T&gt; 的方法调用的结果,其中TgetVal() 的预期返回类型,并且其中也包含了 T 的类。

因此我可以实现一个额外的便利方法:

    public static <T> T getVal(TypedParam<T> param) {
        return getVal(param.stringValue(), param.getValueClass());
    }

并将所有 getVal(param.stringValue()) 替换为 getVal(param)

此解决方案不能解决一般情况 - 检测 Java 7 和 Java 8 之间不同的重载方法调用 - 但它可以解决已知的方法调用导致这个问题。从那时起,我们就没有在其他地方找到它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多