`

Java 泛型系统的不协变问题和类型推断

 
阅读更多
原文:http://jerrypeng.me/2013/02/java-generics-invariant-and-inference/

下面以这段短小的代码来作为例子解释:
static interface Plant {}
static class Grass implements Plant {}
static class Tree implements Plant {}
static class AppleTree extends Tree {}
static class BananaTree extends Tree {}

public static void main(String[] args) {
    List<Class<? extends Tree>> list1
         = Arrays.asList(AppleTree.class, BananaTree.class);
    List<Class<? extends Plant>> list2
         = Arrays.asList(AppleTree.class, BananaTree.class);
    List<Class<? extends Plant>> list3
         = Arrays.asList(AppleTree.class,  BananaTree.class, Grass.class);
    List<? extends Class<? extends Plant>> list4
         = Arrays.asList(AppleTree.class,  BananaTree.class);
}
上面的代码编译无法通过,读者可以猜测一下是哪一处有问题。
答案是 list2 处。但为什么会这样呢?这要分两部分来说明:
泛型的“不协变(invariant)”问题
Java 泛型方法调用的类型推断
Java 泛型的“不协变”问题
其实这个问题是所有接触到 Java 泛型的人很快就会遇到的,应该属于很基础的 内容。Java 数组是协变(covariant)的,而泛型系统在不用 wildcard type 的 情况下是不协变的(invariant)1。比如可以把 Integer[] 赋值 Number[] ,但是不能把 List<Integer> 赋值给 List<Number> 。
但是当出现嵌套的泛型类型加上 wildcard type 时,我们还是容易迷 惑2。比如 List<Integer> 可以赋值给 List<? extends Number> ,那么 Set<List<Integer>> 是否可以赋值给 Set<List<? extends Number>> 呢?乍一看好像是可以的,但其实是不行的, 而我犯的就是这个错误。应该牢记,在不使用 wildcard type 的情况下泛型是不 协变的。虽然可以认为 List<Integer> 是 List<? extends Number> 的子类 型,但 Set<List<Integer>> 不是 Set<List<? extends Number>> 的子类 型。为了解决这个问题,我们还是要加上 wildcard,把 Set<List<? extends Number>> 改成 Set<? extends List<? extends Number>> 即可解决问题。
说了这么多,其实就是一个简单的道理:想获得协变的效果,就要使用 wildcard 加 extends。
回到前面的例子, list2 那里编译不通过的原因,看一下错误信息,结合上 面的解释应该就很明了:
Type mismatch: cannot convert from List<Class<? extends TypeInference.Tree>> to List<Class<? extends TypeInference.Plant>>
在类型声明处加上 ? extends 就可以解决问题, list4 处加上以后编译立 刻能通过了。
剩下的问题是,为啥 list1 和 list3 两处可以通过编译?
方法调用的类型推断
方法调用的类型推断是个十分复杂的过程,对其完整的规则我还没有一个深入的 理解,说实话试图阅读 Java Language Specification 相关部分对我来说都十分 困难,感觉好难懂,有兴趣的读者可以自行查看相关章节(15.12.2.7 Inferring Type Arguments Based on Actual Arguments)。
不过对于上面那个简单的例子,我可以得出一个比较 naive 的结论:
对于某个泛型方法 M 中包含的泛型参数 T1..Tn,Java 编译器会根据调用上下文
(Calling Context),包括实际参数和返回值等,推断出尽可能“具体”的实际
类型。
另外还有一条我还不太确定的结论:如果一个泛型参数同时出现在参数和返回值 中,则类型推断以参数为准,仅当不包含泛型参数的时候才会参考函数返回值。
看一下上面的例子, list1 , list2 , list3 看起来差不多,为什么 只有 list2 处编译不通过?我们可以结合上面提到的规则看一下:
根据 list1 处的两个参数 AppleTree.class 和 BananaTree.clas 可 以推断出来的最“具体”的类型是 List<Class<? extends Tree>> ,和前面 list1 的声明完全吻合,所以不受不协变的影响,是合法的。
根据 list3 处的三个参数 AppleTree.class , BananaTree.class 和 Grass.class 可以推断出来的最“具体”的类型是 List<Class<? extends Plant>> ,和 list3 的声明也完全吻合,同理也是合法的。
list2 处推断出来的是 List<Class<? extends Tree>> ,和前面声明的 List<Class<? extends Plant>> 不兼容,所以编译报错。
再回到最开始那个例子,其中的 Module 是 Google Guice 的一个接口,而下 面那句 newHashSet 调用推断出来的是 Set<Class<? extends AbstractModule>> ,所以会报错。只要相应地把方法返回值改成 Set<? extends Class<? extends Module>> 即可解决我最初的问题。
分享到:
评论

相关推荐

    java 泛型类的类型识别示例

    java 泛型类的类型识别示例 java 泛型类的类型识别示例 java 泛型类的类型识别示例

    Java泛型和集合

    Java Generics and Collections 英文版,详细描述java 泛型技术

    Java泛型编程指南.pdf

    Java泛型编程指南.pdf 此文章译自SUN的泛型编程指南

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    java 泛型接口示例

    java 泛型接口示例 java 泛型接口示例 java 泛型接口示例

    java 泛型方法使用示例

    java 泛型方法使用示例 java 泛型方法使用示例 java 泛型方法使用示例

    Java 泛型擦除后的三种补救方法

    Java中的泛型,在运行时刻其具体类型是被擦除的,这样我们就不能用new T(),instanceof等关操作,特别是对泛型类型的类的实例化问题,在此根据《Thinking in Java》中所讲的对类型擦除所带来问题的三种解决方案,比较...

    java泛型技术之发展

    java泛型技术之发展,学习JAVA 泛型的不错东东

    java泛型总结

    深入理解java泛型,包括类名泛型的定义,方法泛型定义,泛型的返回

    Java如何获取泛型类型

    秒懂Java类型(Type)系统 Java 运行时如何获取泛型参数的类型 Java类型Type 之 ParameterizedType,GenericArrayType,TypeVariabl,WildcardType 从实现的接口获取泛型参数 定义一个泛型父类: public interface ...

    java泛型和反射机制

    对java泛型以及反射机制进行原理和应用上的讲解,帮助初学者对这两个概念进行更轻松的掌握

    1.java泛型定义.zip

    1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....

    java泛型学习ppt

    java,学习java泛型,java培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptx

    Java泛型不是真正的泛型

    详细的介绍了Java是伪泛型的原因,介绍了类型擦除的内容等。

    Java泛型的用法及T.class的获取过程解析

    主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    java泛型详解.pdf

    java泛型详解.pdf

    很好的Java泛型的总结

    很好的Java泛型的总结,看完之后你一定会知道java泛型的底层机制,你一定会学会Java泛型!

    4.java泛型的限制.zip

    4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...

Global site tag (gtag.js) - Google Analytics