资讯专栏INFORMATION COLUMN

Unchecked Conversion 导致的 Java 方法返回类型变更

liangzai_cool / 515人阅读

摘要:问题在遇到有同学反馈了个问题第一眼的感觉应该是泛型擦除和类型推断导致的但当我尝试去彻底解释这个问题的时候才发现关键原因是如果在调用方法时有那么方法返回的是定义中返回类型经过擦除后的结果具体问题是这个样子的错误不兼容的类型无法转换为猜测

问题

在 v2 遇到有同学反馈了个问题, 第一眼的感觉应该是泛型擦除(Type Erasure)和类型推断(Type Inference)导致的. 但当我尝试去彻底解释这个问题的时候, 才发现关键原因是: 如果在调用方法时有 unchecked conversion, 那么方法返回的是定义中返回类型经过擦除(erasure)后的结果.

具体问题是这个样子的:

public static List methodA(Collection stringCollection) {
    List stringList = new ArrayList<>();
    for (String s : stringCollection) {
        stringList.add(s);
    }
    return stringList;
}

public static void methodB(String s) {}

public static void main(String args[]) {
    // ok
    methodA((Collection) new ArrayList()).stream().forEach(p -> methodB(p));

    // compile error
    // Question.java:29: 错误: 不兼容的类型: Object无法转换为String
    // methodA((Collection) map.get("A")).stream().forEach(p -> methodB(p));
    //                                                                  ^
    methodA((Collection) new ArrayList()).stream().forEach(p -> methodB(p));
}
猜测过程

如果对 type erasure, unchecked warning 不太熟悉, 可以先阅读后几节.

依我的理解, lambda 中 p 的类型应该被推断为 String. 考虑 streamforEach 的定义为 Stream Collection.stream(), void Stream.forEach(Consumer action). 整个类型推断的过程应该如下:

List stringList = methodA(...);
Stream stringStream = stringList.stream();
stringStream.forEach(p -> methodB(p));

但从实际编译的结果来看, methodA((Collection) new ArrayList()) 版本是符合预期的, 而 methodA((Collection) new ArrayList()) 版本中, p 的类型被推断为 Object.

有了两个版本的对比, 很容易就会去猜测是不是 mehtodA 返回的结果因为入参类型的不同而不同. 但tm我就走歪了, 因为映像中没有什么情况下方法返回类型会和定义不一致, 所以我先去排查了一遍泛型擦除, 类型推断, Java 对 Lambda 的处理方式等. 直到最后走头无路,才尝试去看返回类型是否有猫腻.

最终发现报错版本返回的结果类型为 raw type List, 而不是预期的 parameterzied type List. 验证的代码如下:

Collection rawCollection = new ArrayList();
// methodA 返回的是 raw type List, 此处赋值会因为 parameterzied type 指向 raw type 而导致 unchecked warning
List stringList = methodA(rawCollection);

官方的解释 是:

Otherwise, if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure (§4.6) of the method"s declared return type.

嗯, 报错版本的传入参数类型是 Collection, 而 methodA 定义的参数类型为 Collection, 调用触发 unchecked conversion.

泛型擦除

Generic type 是泛型的定义, 由 Java 传统的类型结合 type parameter 组成.
通过提供具体的 type argument, generic type 实例化成为 parameterized type.

引用 Java Generic FAQs 中的内容, 泛型擦除指的是: 在编译过程中通过消除 type parameter 和 type argument 来将同一 generic type 对应的不同实例映射成同一个类的过程.
具体可以分为两个部分:

Parameterized type 的 type parameters 被删除.

Generic type 中的 type arguments 被替换为具体的类型.

在泛型擦除的过程中, 编译器可能按需加入额外的方法和类型转换来保证编译结果的正确性.

unchecked warning

uncheked warning 在编译期间产生, 表示编译器无法保证完全的类型安全, 当然也不代表一定类型不安全.
产生的几种场景基本都和泛型有关. 和本文关联的场景是: 将 parameterized type 指向 raw type, 比如 List stringList = new ArrayList()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73069.html

相关文章

  • Java™ 教程(泛型原始类型

    泛型原始类型 原始类型是没有任何类型参数的泛型类或接口的名称,例如,给定Box泛型类: public class Box { public void set(T t) { /* ... */ } // ... } 要创建参数化类型的Box,请为形式类型参数T提供实际类型参数: Box intBox = new Box(); 如果省略实际的类型参数,则创建一个原始类型Box: Box...

    史占广 评论0 收藏0
  • java异常设计和使用

    摘要:异常处理作用调试程序定位缺陷。对异常的处理是系统容错可靠性的一环。抛出需要具体子异常捕获同时也需要具体子异常不同子异常处理会不同。如有基础基类异常调用者可以选择使用这个基类该类下面的所有子异常使用统一处理也可以单独对子异常处理。 showImg(https://segmentfault.com/img/bVbrm5g); 1 背景 1年前的文章,源于Sonar静态代码扫描中,项目历史代...

    sshe 评论0 收藏0
  • Java知识点总结(注解-内置注解)

    摘要:知识点总结注解内置注解知识点总结注解定义在中,此注释只适用于修饰方法,表示一个方法声明打算重写父类的另一个方法声明。此注释可用于修饰方法属性类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。 Java知识点总结(注解-内置注解) @(Java知识点总结)[Java, 注解] @Override 定义在java.lang.Override 中,此注释只适用于修饰方法...

    J4ck_Chan 评论0 收藏0
  • Java™ 教程(类型推断)

    类型推断 类型推断是Java编译器查看每个方法调用和相应声明的能力,以确定使调用适用的类型参数,推理算法确定参数的类型,如果可用,还确定分配或返回结果的类型,最后,推理算法尝试查找适用于所有参数的最具体类型。 为了说明最后一点,在下面的示例中,推断确定传递给pick方法的第二个参数是Serializable类型: static T pick(T a1, T a2) { return a2; } ...

    JerryC 评论0 收藏0
  • Java异常处理最佳实践

    摘要:然而,我更倾向于使用来单元测试来文档化异常。单元测试允许我在使用中查看异常,并且作为一个可以被执行的文档来使用。通过为异常编写单元测试,你不仅可以记录异常如何触发,还可以使你的代码在经过这些测试后更加健壮。 本文是关于 Exception 处理的一篇不错的文章,从 Java Exception 的概念介绍起,依次讲解了 Exception 的类型(Checked/Unchecked),...

    mayaohua 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<