集合中泛型正确地擦除

先提问:
数组存储与集合存储在编译期的区别
集合中对于泛型的使用规则
<? extends T>和<T>的区别与二者何时被擦除



正文:
先给出3个实验类
class Fruit{ }
class Orange extends Fruit{ }
class Apple extends Fruit{ }

数组
编译器对代码信息了解的足够多,一些地方允许编译通过
以下4行编译均通过:
Fruit[] fruit = new Apple[10];    
fruit[0] = new Apple();               
fruit[0] = new Orange();
fruit[0] = new Fruit();
但是很显然,运行时3,4行的运行是不通过的,你无法将一个Orange放到一个Apple数组中,你也无法讲一个父类对象放到一个子类对象数组中


集合
编译器对代码了解的不够多,相对于数组来说有些地方不允许编译通过
下面这行编译不通过
List<Fruit> flist = new ArrayList<Apple>

编程思想上的解释是:大家第一反应是“不能讲一个Apple容器赋值给一个Fruit容器”,但正确说法是“不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型”,就像在数组中的情况一样,编译期对代码的了解足够多,可以确定所涉及到的容器,那么他可能会留下一些雨滴,但是它不知道任何有关这方面的信息,因此他拒绝向上转型。然而实际上这根本不是向上转型——Apple的List不是Fruit的List。Apple的List将持有Apple和Apple的子类型,而Fruit的List将持有任何类型的Fruit,诚然其中包含Apple,但他不是一个Apple的List,他仍然是Fruit的List,Apple的List在类型上不等价与Fruit的List,即使Apple是一种Fruit类型

换句话说,在泛型确定之后,不同的泛型的集合之间是不能转换的,即使一个泛型能上转型成另一个泛型


下面讨论<? extends Fruit>
 

<? extends T>的作用是为了让泛型在符合继承的条件下正确准确的擦除
List<Fruit1> flist = new ArrayList<Apple1>();
List<? extends Fruit1> flist1 = new ArrayList<Apple1> ();
(1)第一句是不允许的,已经说了,容器级别不能直接由编译期识别向上转型
(2)但是第二句是允许的,他在擦除时将泛型确定为了Apple1,编译通过也是因为Apple1符合 extends Fruit1的规则
(3)可是,你无法向里面add对象,编译期无法在你加东西地时候确定你加的到底是什么类型,他无法准确擦除泛型,所以编译失败
但是有一点,你要从里面拿东西,那是可以的,因为无论如何,它至少能保证拿出来的是Fruit类型

public class GenericReading {
       //方法为泛型
       static <T> T readExact(List<T> list ){
             return list .get(0);
      }
       static List<Apple> apples = Arrays.asList( new Apple());
       static List<Fruit> fruit = Arrays.asList( new Fruit());

       static void f1(){
            Apple1 a = readExact( apples );
            Fruit1 f = readExact( fruit );
             //方法级别上的泛型,接收时确定泛型为Apple1,返回时也返回Apple1,只是f作为Fruit1类型
             //接受,所以是成立的
             f = readExact( apples );
      }


//----------------------------------------------------------------------------------------

       //类中带泛型
       static class Reader<T>{
            T readExact(List<T> list ){
                   return list .get(0);
            }
      }
       static void f2(){
            Reader<Fruit> reader = new Reader<Fruit>();
            Fruit f = reader .readExact( fruit );
             //Fruit1 f1 = reader.readExact(apples);
             //这句不通过,因为在定义时便已经确定泛型T为Fruit1,这里使用的时候传入的泛型T又为Apple1
             //二者不一致,编译不通过
      }


//----------------------------------------------------------------------------------------

       static class Reader2<T>{
            T ReaderExact(List<? extends T> list ){
                   return list .get(0);
            }
      }
       static void f3(){
            Reader2<Fruit> reader2 = new Reader2<Fruit>();
            Fruit1 f = reader2 .ReaderExact( fruit );
            Fruit1 f2 = reader2 .ReaderExact( apples );
             //这句编译通过是因为虽然在创建对象时确定了泛型T为Fruit,但是在方法中参数list使用的泛型
             //并不是T,而是<? extends T>,又因为传入的是apples,对应的泛型为Apple,且<Apple extends Fruit>
             //符合规范,最后返回值自动向上转型成T,也就是Fruit输出出去
      
      }
}


             类中<? extends T>的参数不会因为类中的泛型T被确定而确定,只有使用的时候才会确定,在执行完最后         
     该方法最后一句时,Reader2类被擦除泛型成
             static class Reader2{
                Fruit ReaderExact(List <Apple> list){
                      return list.get(0);
                }     
             }

猜你喜欢

转载自blog.csdn.net/lyandyhk/article/details/51174616