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

栏目: Java · 发布时间: 5年前

内容简介:在 v2 遇到有同学反馈了个问题, 第一眼的感觉应该是泛型擦除(Type Erasure)和类型推断(Type Inference)导致的. 但当我尝试去彻底解释这个问题的时候, 才发现关键原因是: 如果在调用方法时有 unchecked conversion, 那么方法返回的是定义中返回类型经过擦除(erasure)后的结果.具体问题是这个样子的:如果对 type erasure, unchecked warning 不太熟悉, 可以先阅读后几节.

问题

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

具体问题是这个样子的:

public static List<String> methodA(Collection<String> stringCollection) {
    List<String> 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<String>) new ArrayList<String>()).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<String>()).stream().forEach(p -> methodB(p));
}

猜测过程

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

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

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

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

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

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

Collection rawCollection = new ArrayList<String>();
// methodA 返回的是 raw type List, 此处赋值会因为 parameterzied type 指向 raw type 而导致 unchecked warning
List<String> 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<String> , 调用触发 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<String> stringList = new ArrayList()


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python算法教程

Python算法教程

[挪威] Magnus Lie Hetland 赫特兰 / 凌杰、陆禹淳、顾俊 / 人民邮电出版社 / 2016-1-1 / 69.00元

本书用Python语言来讲解算法的分析和设计。本书主要关注经典的算法,但同时会为读者理解基本算法问题和解决问题打下很好的基础。全书共11章。分别介绍了树、图、计数问题、归纳递归、遍历、分解合并、贪心算法、复杂依赖、Dijkstra算法、匹配切割问题以及困难问题及其稀释等内容。本书在每一章结束的时候均有练习题和参考资料,这为读者的自我检查以及进一步学习提供了较多的便利。在全书的最后,给出了练习题的提......一起来看看 《Python算法教程》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码