如何检测会在 Java 8 中导致 ClassCastException 的模棱两可的方法调用?

Posted

技术标签:

【中文标题】如何检测会在 Java 8 中导致 ClassCastException 的模棱两可的方法调用?【英文标题】:How to detect ambiguous method calls that would cause a ClassCastException in Java 8? 【发布时间】: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 将自动修复有问题的调用,因此具有讽刺意味的是那里不会有任何更改(我可能永远不会知道它们在哪里:-)。 【参考方案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 之间不同的重载方法调用 - 但它可以解决已知的方法调用导致这个问题。从那时起,我们就没有在其他地方找到它。

【讨论】:

以上是关于如何检测会在 Java 8 中导致 ClassCastException 的模棱两可的方法调用?的主要内容,如果未能解决你的问题,请参考以下文章

struct 成员上的 free() 仅在 Debug 中导致 Hardfault

JPA 在 Spring Boot 中导致 java.lang.NullPointerException

setDefaultAuthenticator 在 GWT Web 应用程序中导致 java.security.AccessControlException

此代码如何在 ghostscript 中导致 -100 错误

UISearchDisplayController autorelease 如何在不同的视图控制器中导致崩溃?

用于评估后缀表达式的 java 程序在 leetcode #150 for stack 的一种方法中导致 numberformat 异常。请建议更改