- 浏览: 2611066 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (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 1131public static void main(String[ ... -
解决tomcat中反序列化找不到class
2017-05-19 09:59 2180tomcat反序列化的过程中一直报ClassNotFoundE ... -
java的sun.jnu.encoding有什么用
2017-02-10 15:45 5525目前看到的影响有两个:影响类名的读取和Main方法参数的读取。 ... -
jsckson序列化处理泛型
2017-01-10 15:02 3393我有这样两个类 package com.vipshop. ... -
java的double乘法精度问题
2016-12-22 09:31 5602项目中实际的代码,我们实际的金额单位是元,精确到分,另外一个系 ... -
Calendar.getInstance()的坑
2016-12-06 16:50 6002Calendar.getInstance()看起来应该是个单例 ... -
针对jquery的when方法做的应变
2016-10-13 17:09 1111需求:a,b两个任务都处理(不管a,b是成功还是失败)后,执行 ... -
http的501错误
2016-10-09 15:37 8738普通的url请求是get put之类的,如果是乱七八糟的,比如 ... -
java对象初始化的顺序
2016-10-08 17:18 1036public class Son extends F ... -
java序列化框架性能比较
2016-05-24 09:22 32871. Kryo 2. FST 3. java 原生序列化 ... -
java 关闭main方法中的定时器线程(2)
2016-05-20 15:49 1819import java.util.concurrent ... -
java 关闭main方法中的定时器线程
2016-05-20 15:29 1232public class TestTreadClose { ... -
java用cyclicBarrier来实现Phaser的分段功能
2015-01-26 10:22 1602cyclicBarrier是有自动重置功能的,我们可以用这个功 ... -
Java的Integer是由缓存决定的,我们可以改变缓存来改变Integer
2015-01-25 16:00 1670import java.lang.reflect.Fi ... -
BigDecimal做四舍五入的坑
2015-01-12 10:23 4195BigDecimal decimal = new BigD ... -
对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾
2015-01-03 11:27 36338在hibernate启动的时候报了个错: 对实体 " ... -
mvel表达式遇到的坑
2014-12-31 18:02 5233简单的说是:mvel中int和一个double做乘法,是可能出 ... -
spring启动的时候尝试多线程发生的死锁
2014-09-12 11:12 3954具体的死锁线程dump: Found one Java-l ... -
multimap的get方法犯的误区
2014-07-28 12:02 1146原文:http://java-performance.info ... -
java方法的syntethic类型
2014-04-15 19:16 1189我们一般说java的方法有public private,但其实 ...
相关推荐
10. **Erasure和类型安全**:尽管类型擦除可能让人感觉泛型在运行时失去了作用,但实际上,编译器通过类型擦除实现了类型安全。它会在编译期间检查泛型的使用,防止不兼容类型的对象被放入泛型容器中。 通过学习...
- 但是,泛型不支持协变(Covariance)和逆变(Contravariance),所以 `List<Dog>` 不是 `List<Animal>` 的子类型。 8. 类型推断(Type Inference): - 自Java 7起,编译器可以自动推断泛型的类型,例如在创建...
6. **协变和逆变**:理解如何在泛型中实现类型参数的协变和逆变。 7. **比较和equals**:在泛型上下文中正确地实现Comparable和equals()方法。 8. **枚举类型与泛型**:结合使用枚举和泛型来增强类型安全。 通过...
Java泛型的实现方式被称为“擦除”(Erasure),即在编译阶段,编译器会根据泛型参数进行类型检查和推断,但最终生成的字节码并不包含泛型信息。这意味着,如`List<Integer>`和`List<String>`在运行时实际上是同一个...
8. 对于数组,由于历史原因,Java的泛型不支持通配符数组,这意味着你不能创建一个`Number[]`类型的数组并将其赋值给`List<Number>`,因为数组是固定类型的,而泛型列表是协变的。因此,通常需要将数组转换为`List`...
Java泛型是JDK 1.5引入的重要特性,它为Java编程提供了类型安全的集合框架,使得在编译时期就能进行类型检查,避免了运行时的类型转换风险,极大地提高了代码的可读性和健壮性。泛型的引入是为了在不牺牲面向对象...
4. **类型擦除**:Java泛型在编译后会进行类型擦除,这意味着在运行时所有的泛型信息都会丢失,泛型只在编译时起作用,用于增强类型安全。 5. **协变与逆变**:泛型的协变(Covariance)和逆变(Contravariance)...
Java泛型是Java语言提供的一种编程特性,旨在支持在编译时期进行类型检查和类型消除,使得编写的代码在不放弃类型安全的前提下,具有更好的通用性和复用性。学习Java泛型能够帮助我们更好地编写和使用通用的类、接口...
7. **协变与逆变**:在Java中,泛型是协变的,意味着`List<Number>`是`List<Object>`的子类型,这允许将Number列表赋值给Object列表。然而,对于泛型的参数类型,它是逆变的,例如`List<? super Integer>`可以接受...
10. **协变与逆变**:泛型的协变和逆变涉及到类型参数的位置以及它们如何影响方法签名的兼容性。例如,`List<? extends Number>` 是协变的,因为它允许更具体的子类型作为参数;而 `Comparator<? super T>` 是逆变的...
6. **原始类型与类型擦除**:Java泛型是通过类型擦除实现的,这意味着在运行时所有的泛型信息都会被移除。为了保持向后兼容,Java允许使用未指定类型的原始类型(如`List`而非`List<String>`),但这样会失去泛型...
泛型的协变和逆变是关于类型参数在作为方法返回类型和参数类型时的行为。简单来说,如果类型参数在方法返回类型中使用,那么它是协变的;如果在方法参数中使用,它是逆变的。了解这些概念有助于编写更加灵活和安全...
通配符是Java泛型中一个重要的工具,用于处理未知类型或类型范围。它们在保持类型安全的同时增加了代码的灵活性。了解如何恰当地使用通配符可以帮助开发人员编写更安全、更健壮的Java代码。在实际编程中,正确理解和...
Java泛型是自JDK 1.5版本引入的一项重要特性,它极大地提高了代码的类型安全性和重用性。泛型允许我们在定义类、接口和方法时指定参数化类型,这样在编译时期就能检查类型匹配,避免了运行时的类型转换异常。以下是...
**Java泛型是JDK1.5引入的一个重要特性,极大地提高了代码的类型安全性和重用性。在泛型出现之前,程序员需要在运行时进行强制类型转换,这可能导致ClassCastException。泛型通过在类、接口和方法声明中引入类型参数...
6. **协变与逆变:**Java泛型支持协变和逆变,这是在处理泛型接口和类时的重要特性。协变允许子类型替换父类型,而逆变则允许父类型替换子类型。在并发编程中,了解这些概念有助于编写更灵活和安全的代码。 7. **...
Java泛型是编程语言中一个强大的工具,它允许开发者在编写代码时定义具有类型参数的类、接口和方法。在JDK 5引入后,泛型显著提高了代码的类型安全性和可读性,减少了类型转换的需要,并且帮助避免了运行时的...
8. **类型参数的协变和逆变**:在处理泛型集合时,了解协变和逆变的概念非常重要。比如,`List<Number>`是`List<Object>`的子类型,这称为协变;而`List<? extends Number>`可以赋值给`List<Number>`,这称为逆变。 ...
泛型的协变和逆变也是重要概念。在Java中,集合类的读操作是协变的,意味着`List<Number>`可以被赋值给`List<Object>`,因为Number是Object的子类。然而,写操作是逆变的,`List<Object>`不能被赋值给`List<Number>`...
6. **泛型**:两种语言都支持泛型,但C#的泛型更灵活,允许协变和逆变,Java泛型则是非协变的。 7. **接口与抽象类**:Java只允许单继承,但可以多实现接口;C#允许单一的基类和多接口实现。转换时,需要根据具体...