Java 泛型通配符详解
1 package com.wenhui.arithmetic.pecs; 2 3 /** 4 * Created by wenhui.yu on 2019/4/18. 5 */ 6 public class Problem { 7 static class Fruit { 8 } 9 10 static class Apple extends Fruit { 11 } 12 13 static class Banana extends Fruit { 14 } 15 16 static class GreenApple extends Apple{ 17 } 18 19 public static void main(String[] args) { 20 // shouldRight(); 21 shouldDo(); 22 } 23 24 /** 25 * 逻辑上应该是这样玩的 26 */ 27 public static void shouldRight() { 28 Apple apple = new Apple(); 29 /** 30 * 定义一个果盘,逻辑上果盘内是可以放苹果的,看似操作没有问题 31 * 32 * 编译器报错 33 * 不兼容的类型: 34 * com.wenhui.arithmetic.pecs.Plate<com.wenhui.arithmetic.pecs.Apple>无法转换为 35 * com.wenhui.arithmetic.pecs.Plate<com.wenhui.arithmetic.pecs.Fruit> 36 * 37 * 原因: 38 * 泛型不符合里氏替换原则,或者说不是协变的 39 * 编译器认为: 40 * 苹果 is a 水果 41 * 装苹果的盘子 not is a 装水果的盘子 42 */ 43 Plate<Fruit> fruitPlate = new Plate<Apple>(apple); 44 45 /** 46 * 非泛型(如数组)是支持合里氏替换原则,或者说协变的 47 * 48 * 支持插入子类的子类 49 */ 50 Fruit[] fruits = new Apple[]{apple, new GreenApple()}; 51 52 } 53 54 /** 55 * 实际上是要这样玩的 56 */ 57 public static void shouldDo() { 58 /** 59 * 一个能放水果及其子类的盘子 60 * 61 * 副作用: 62 * set方法失效 63 */ 64 Plate<? extends Fruit> fruitPlate1 = new Plate<Apple>(new Apple()); 65 // 放子类的子类(苹果的子类青苹果)同样支持 66 Plate<? extends Fruit> fruitPlate2 = new Plate<Apple>(new GreenApple()); 67 68 /** 69 * 编译器报错: 70 * Error:(30, 35) java: 不兼容的类型: com.wenhui.arithmetic.pecs.Plate<com.wenhui.arithmetic.pecs.Apple>无法转换为 71 * com.wenhui.arithmetic.pecs.Plate<com.wenhui.arithmetic.pecs.Fruit> 72 * Error:(54, 29) java: 不兼容的类型: 73 * com.wenhui.arithmetic.pecs.Apple无法转换为capture#1, 共 ? extends com.wenhui.arithmetic.pecs.Fruit 74 * 解析: 75 * 原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。 76 * 可能是Fruit?可能是Apple?也可能是Banana,GreenApple或者其他 77 * 编译器在看到后面用new Plate赋值以后,盘子里没有被标上Apple,而是标上一个占位符(capture#1),来表示捕获Fruit类或其子类 78 * 所以无论是想插入Apple或者其他,编译器都不知道能不能和capture#1匹配 79 * 为了类型安全,所以阻止想其中加入任何子类 80 */ 81 fruitPlate1.setItem(new Apple()); 82 // get方法正常 83 Fruit item = fruitPlate1.getItem(); 84 85 /** 86 * 一个能放苹果及其父类的盘子 87 * 88 * 副作用: 89 * get方法失效 90 */ 91 Plate<? super Apple> fruitPlate3 = new Plate<Fruit>(new Fruit()); 92 // set 方法正常 93 fruitPlate3.setItem(new Apple()); 94 /** 95 * 同理: 96 * 因为允许放苹果及其所有父类,所以get 的时候,不能确认到底是那个父类,所以只有Object对象能接收 97 */ 98 Object item1 = fruitPlate3.getItem(); 99 System.out.println(item1); 100 101 /** 102 * 总结: 103 * 1: 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends T;(Producer Extends) 104 * 2: 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super T;(Consumer Super) 105 * 3: 没事不要用泛型通配符 106 */ 107 } 108 }
容器类(Plate)
1 package com.wenhui.arithmetic.pecs; 2 3 /** 4 * 盘子 5 * @param T 盘子中放的物品 6 * Created by wenhui.yu on 2019/4/18. 7 */ 8 public class Plate<T> { 9 private T item; 10 11 public T getItem() { 12 return item; 13 } 14 15 public void setItem(T item) { 16 this.item = item; 17 } 18 19 public Plate(T item) { 20 this.item = item; 21 } 22 }