Java泛型大全( 二 )

这样就相当与告诉编译器 ,  fruitReader的readCovariant方法接受的参数只要是满足Fruit的子类就行(包括Fruit自身) , 这样子类和父类之间的关系也就关联上了 。
PECS原则 
上面我们看到了类似的用法 , 利用它我们可以从list里面get元素 , 那么我们可不可以往list里面add元素呢?我们来尝试一下:
public class GenericsAndCovariance { public static void main(String[] args) { List<?extends Fruit> flist = new ArrayList<Apple>(); flist.add(null); Fruit f = flist.get(0); }}答案是否定 , Java编译器不允许我们这样做 , 为什么呢?对于这个问题我们不妨从编译器的角度去考虑 。因为Listflist它自身可以有多种含义:
List<?extends Fruit> flist = new ArrayList<Fruit>();List<?extends Fruit> flist = new ArrayList<Apple>();List<?extends Fruit> flist = new ArrayList<Orange>();当我们尝试add一个Apple的时候 , flist可能指向new ArrayList();
当我们尝试add一个Orange的时候 , flist可能指向new ArrayList();
当我们尝试add一个Fruit的时候 , 这个Fruit可以是任何类型的Fruit , 而flist可能只想某种特定类型的Fruit , 编译器无法识别所以会报错 。华为 Java 编程军规 , 牛逼!
所以对于实现了的集合类只能将它视为Producer向外提供(get)元素 , 而不能作为Consumer来对外获取(add)元素 。
如果我们要add元素应该怎么做呢?可以使用:
public class GenericWriting { static List<Apple> apples = new ArrayList<Apple>(); static List<Fruit> fruit = new ArrayList<Fruit>(); static <T> void writeExact(List<T> list, T item) { list.add(item); } static void f1() { writeExact(apples, new Apple()); writeExact(fruit, new Apple()); } static <T> void writeWithWildcard(List<?super T> list, T item) { list.add(item) } static void f2() { writeWithWildcard(apples, new Apple()); writeWithWildcard(fruit, new Apple()); } public static void main(String[] args) { f1(); f2(); }}这样我们可以往容器里面添加元素了 , 但是使用super的坏处是以后不能get容器里面的元素了 , 原因很简单 , 我们继续从编译器的角度考虑这个问题 , 对于List list , 它可以有下面几种含义:
List<?super Apple> list = new ArrayList<Apple>();List<?super Apple> list = new ArrayList<Fruit>();List<?super Apple> list = new ArrayList<Object>();当我们尝试通过list来get一个Apple的时候 , 可能会get得到一个Fruit , 这个Fruit可以是Orange等其他类型的Fruit 。
根据上面的例子 , 我们可以总结出一条规律 , ”Producer Extends, Consumer Super”:
“Producer Extends” – 如果你需要一个只读List , 用它来produce T , 那么使用? extends T 。
“Consumer Super” – 如果你需要一个只写List , 用它来consume T , 那么使用? super T 。
如果需要同时读取以及写入 , 那么我们就不能使用通配符了 。
如何阅读过一些Java集合类的源码 , 可以发现通常我们会将两者结合起来一起用 , 比如像下面这样:
public class Collections { public static <T> void copy(List<?super T> dest, List<?extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i, src.get(i)); }} 类型擦除 
Java泛型中最令人苦恼的地方或许就是类型擦除了 , 特别是对于有C++经验的程序员 。类型擦除就是说Java泛型只能用于在编译期间的静态类型检查 , 然后编译器生成的代码会擦除相应的类型信息 , 这样到了运行期间实际上JVM根本就知道泛型所代表的具体类型 。这样做的目的是因为Java泛型是1.5之后才被引入的 , 为了保持向下的兼容性 , 所以只能做类型擦除来兼容以前的非泛型代码 。对于这一点 , 如果阅读Java集合框架的源码 , 可以发现有些类其实并不支持泛型 。别乱打日志了 , 这才是正确的打日志姿势!
说了这么多 , 那么泛型擦除到底是什么意思呢?我们先来看一下下面这个简单的例子:
public class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = https://www.isolves.com/it/cxkf/yy/JAVA/2019-08-07/data; this.next = next; } public T getData() { return data; }}编译器做完相应的类型检查之后 , 实际上到了运行期间上面这段代码实际上将转换成:


推荐阅读