升级Java 8以后,上线就翻车了。这次是泛型的锅

2023年 9月 27日 23.4k 0

近期在线上环境遇到一个奇怪的问题。关于类型推断、泛型、重载的问题。

问题发现

一段原本运行良好的代码,在升级为JDK8后,突然遇到了异常情况。

java.lang.ClassCastException: java.lang.String cannot be cast to [C

大家面面相觑、非常困惑。明明没有任何代码变更

  • 怎么就不行了呢?
  • 怎么就会报类型转换异常呢?
  • [C 类型是个啥?
  • [cchar[[ 数组类型。截图中显示 String类型转为char[] 类型异常。

    一看到异常堆栈就感到困惑,再仔细看代码,更觉得不可思议~

    抛出异常的代码处既有嵌套调用,又涉及业务逻辑。为了保护代码机密性,我对相关部分进行了转换。

    @Test
    public void test2() {
       System.out.println(String.valueOf(getSimpleString()));
    }
    
    public static  T getSimpleString() {
       return (T) "121";
    }
    

    调用堆栈并没有明确异常抛出的方法,只是在 String.valueOf(getSimpleString()) 这一行抛出了异常。这行代码怎么看也不会有问题吧?

    问题解决

    正当一筹莫展之际,有人怀疑是升级JDK8的问题。尝试在本地环境使用JDK8复现问题。最终确认JDK8环境下稳定复现这个问题。

    然后尝试分割这两个方法,再次验证。

    String value = getSimpleString();
    System.out.println(String.valueOf(value));
    

    将泛型返回值声明为 String以后,问题解决,不会再有类型转换异常。

    虽然解决了问题,但是非常困惑,为什么String.valueOf 将一个String泛型对象,转为String,会报类型转换异常呢?

    离谱、很离谱。为什么会出现这个问题,为什么换成两行代码,问题解决了?

    image.png

    正常情况下应该使用 String.valueOf(Object value)这个重载方法,为什么会选择valueOf(char[])呢?JDK8 为什么会选择一个错误的重载方法

    总结

    在查阅无数资料后,看起来比较可信的解释是

    如果在编译阶段不能够确认 返回值类型,在运行时,jdk8会对泛型返回值类型进行推断,如果恰好方法存在重载,jdk就逐个对方法进行类型比对,如果类型可以匹配上,则使用相关重载方法,如果无法匹配,则使用 valueOf(char[])

    我做了类似的测试,返回值类型为

  • (T)"123"
  • (T) new Long(1212L)
  • (T) new Integer(1212L)
  • (T) new Order();
  • (T) new Object()
  • 无论是String类型,还是Long等包装器类型、Object类型、业务自定义类。都报出类型转换异常。

    当泛型返回值为Object对象时,居然也抛出异常,从现象来看,jdk8并没有逐个比较重载方法是否类型一致,而是选择了char[] 这个默认方法。

    实际上任何类型只要没有显示声明返回值类型,使用泛型返回值且编译时不确定类型,使用String.valueOf方法,那么就会出现类型转换异常。

    对于泛型返回值不明确的的情况,JDK6、7中,会自动视为Object类型。为什么jdk8 这么干,咱也不确定是咋想的,姑且认为是bug吧。

    所以也建议大家,针对泛型返回值,一定要使用字段明确声明具体的类型,避免jvm在运行时错误的推断返回值类型。在和String.valueOf等重载方法一起使用时,要格外小心。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论