- 浏览: 2579417 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (880)
- 每日总结 (26)
- java (37)
- lucene 2.4源码学习 (11)
- 庖丁分词的源码分析 (5)
- cindy源码阅读 (10)
- jetty (3)
- java基础 (49)
- hadoop (37)
- python (7)
- 那些蛋疼的事 (15)
- 测试 (5)
- spring (3)
- redis (4)
- git (4)
- 我的开源项目 (2)
- linux (15)
- mysql (8)
- bootsharp (1)
- idea (1)
- Disruptor源码分析 (11)
- 高并发 (22)
- jdk (4)
- 领域驱动 (1)
- 坑 (6)
- 表达式框架 (7)
- 游戏 (3)
- Guava (1)
- 缓存 (1)
- 数据库 (1)
- 游戏项目 (3)
- java8 (1)
最新评论
-
hsluoyz:
PyCasbin是一个用Python语言打造的轻量级开源访问控 ...
权限管理的一个简单后台 -
liuyachao111:
谢谢 问题解决了
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾 -
jnjeC:
多谢博主分享,在配置文件里的&也要改成& ...
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾 -
大维啊:
同志,你这不行啊!
java 的 AccessController.doPrivileged使用 -
lisha2009:
解决了我的问题,多谢博主分享!
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾
原文: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>> 即可解决我最初的问题。
下面以这段短小的代码来作为例子解释:
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>> 即可解决我最初的问题。
发表评论
-
获取字符长度的正确姿势
2017-05-23 16:09 1087public static void main(String[ ... -
解决tomcat中反序列化找不到class
2017-05-19 09:59 2109tomcat反序列化的过程中一直报ClassNotFoundE ... -
java的sun.jnu.encoding有什么用
2017-02-10 15:45 5411目前看到的影响有两个:影响类名的读取和Main方法参数的读取。 ... -
jsckson序列化处理泛型
2017-01-10 15:02 3287我有这样两个类 package com.vipshop. ... -
java的double乘法精度问题
2016-12-22 09:31 5534项目中实际的代码,我们实际的金额单位是元,精确到分,另外一个系 ... -
Calendar.getInstance()的坑
2016-12-06 16:50 5865Calendar.getInstance()看起来应该是个单例 ... -
针对jquery的when方法做的应变
2016-10-13 17:09 1078需求:a,b两个任务都处理(不管a,b是成功还是失败)后,执行 ... -
http的501错误
2016-10-09 15:37 8642普通的url请求是get put之类的,如果是乱七八糟的,比如 ... -
java对象初始化的顺序
2016-10-08 17:18 993public class Son extends F ... -
java序列化框架性能比较
2016-05-24 09:22 32321. Kryo 2. FST 3. java 原生序列化 ... -
java 关闭main方法中的定时器线程(2)
2016-05-20 15:49 1780import java.util.concurrent ... -
java 关闭main方法中的定时器线程
2016-05-20 15:29 1187public class TestTreadClose { ... -
java用cyclicBarrier来实现Phaser的分段功能
2015-01-26 10:22 1564cyclicBarrier是有自动重置功能的,我们可以用这个功 ... -
Java的Integer是由缓存决定的,我们可以改变缓存来改变Integer
2015-01-25 16:00 1623import java.lang.reflect.Fi ... -
BigDecimal做四舍五入的坑
2015-01-12 10:23 4111BigDecimal decimal = new BigD ... -
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾
2015-01-03 11:27 36206在hibernate启动的时候报了个错: 对实体 " ... -
mvel表达式遇到的坑
2014-12-31 18:02 5162简单的说是:mvel中int和一个double做乘法,是可能出 ... -
spring启动的时候尝试多线程发生的死锁
2014-09-12 11:12 3902具体的死锁线程dump: Found one Java-l ... -
multimap的get方法犯的误区
2014-07-28 12:02 1106原文:http://java-performance.info ... -
java方法的syntethic类型
2014-04-15 19:16 1108我们一般说java的方法有public private,但其实 ...
相关推荐
java 泛型类的类型识别示例 java 泛型类的类型识别示例 java 泛型类的类型识别示例
Java Generics and Collections 英文版,详细描述java 泛型技术
Java泛型编程指南.pdf 此文章译自SUN的泛型编程指南
这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...
java 泛型接口示例 java 泛型接口示例 java 泛型接口示例
java 泛型方法使用示例 java 泛型方法使用示例 java 泛型方法使用示例
Java中的泛型,在运行时刻其具体类型是被擦除的,这样我们就不能用new T(),instanceof等关操作,特别是对泛型类型的类的实例化问题,在此根据《Thinking in Java》中所讲的对类型擦除所带来问题的三种解决方案,比较...
java泛型技术之发展,学习JAVA 泛型的不错东东
深入理解java泛型,包括类名泛型的定义,方法泛型定义,泛型的返回
秒懂Java类型(Type)系统 Java 运行时如何获取泛型参数的类型 Java类型Type 之 ParameterizedType,GenericArrayType,TypeVariabl,WildcardType 从实现的接口获取泛型参数 定义一个泛型父类: public interface ...
对java泛型以及反射机制进行原理和应用上的讲解,帮助初学者对这两个概念进行更轻松的掌握
1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....
java,学习java泛型,java培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptx
详细的介绍了Java是伪泛型的原因,介绍了类型擦除的内容等。
主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
java泛型详解.pdf
很好的Java泛型的总结,看完之后你一定会知道java泛型的底层机制,你一定会学会Java泛型!
4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...