如何检测会在 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<T> 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<T>
的方法调用的结果,其中T
是getVal()
的预期返回类型,并且其中也包含了 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 异常。请建议更改