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 }

 

posted @ 2019-04-18 14:02  西瓜的小弟西西瓜  阅读(347)  评论(0编辑  收藏  举报