码出高效读书笔记:集合与泛型

集合与泛型的联合使用,可以把泛型的功能发挥到极致,很多程序员不清楚List、List<Object>、List<?>三者的区别,更加不能区分<? extends T>与<? super T>的使用场景。

List<?>是一个泛型,在没有赋值之前,表示它可以接受任何类型的集合赋值,赋值之后就不能再随便往里添加元素了,下面的代码很好地说明了List、List<Object>、List<?>三者的区别。

package Test;

import java.util.List;

public class ListNoGeneric {
    public static void main(String[] args){
        
        //第一段:泛型出现之前的集合定义方式
        List a1 = new ArrayList();
        a1.add(new Object());
        a1.add(new Integer(111));
        a1.add(new String("hello a1a1"));
        
        //第二段:把a1引用赋值给a2,注意a2与a1的区别是增加了泛型限制<Object>
        List<Object> a2 = a1;
        a2.add(new Object());
        a2.add(new Integer(222));
        a2.add(new String("hello a2a2"));
        
        //第三段:把a1引用赋值给a3,注意a3与a1的区别是增加了泛型<Integer>
        List<Integer> a3 = a1;
        a3.add(new Integer(333));
        //以下两行代码编译错误,不允许增加非Integer的元素进入集合
        a3.add(new Object());
        a3.add(new Object());
        
        //第四段:把a1引用赋值给a4,a1与a4的区别是增加了通配符
        List<?> a4 = a1;
        //允许删除和消除元素
        a1.remove(0);
        a4.clear();
        //编译出错,不允许增加任何元素
        a4.add(new Object());
    }
}
  • 第一段说明:在定义List之后,毫不犹豫地往集合里装入三种不同的对象,即Object、Integer、String,遍历没有问题。
  • 第二段说明:把a1赋值给a2,a2是List<Object>类型的,因为Object是所有对象的始祖,所以也可以再往里面装入三种不同的对象。我们经常认为List和List<Object>是完全相同的,至少从前两段来看是这样的。
  • 第三段说明:泛型是在JDK5之后才出现的,考虑到向前兼容,因此历史代码有时需要赋值给新泛型代码,从编译器角度来说是允许的。
  • 第四段说明:问号在正则表达式中可以匹配任何字符,List<?>成为通配符集合。它可以接收任何类型的集合引用赋值,不能添加任何元素,但是可以remove和clear,并非immuntable集合。List<?>一般作为参数来接收外部的集合,或者返回一个不知道具体元素类型的集合。

List<T>最大的问题就是只能放置一种类型,如果随意转换类型的话,就是“破窗理论”,泛型的作用本来就是进行类型安全检查,如果能随意转换类型的话,那么这种类型安全检查就毫无意义可言了。那么该如何防止多种受泛型约束的类型呢?JDK的开发者实现了<? extends T>与<? super T>两种语法。两者的区别非常微妙:

  • <? extends T>是Get First,适用于,消费集合元素为主的场景;<? extends T>可以赋值给任何T及T的子类集合,上界为T,取出来的类型带有泛型限制,向上强转类型为T。null可以表示任何类型,所以除null以外,任何元素都不得添加进<? extends T>集合内。
  • <? super T>是Put First,适用于,生产集合元素为主的场景;<? super T>可以赋值给任何T及T的父类集合,下界为T。在生活中,投票选举类似于<? super T>的操作。选举代表时,你只能往里投选票,取数据时,根本不知道这是谁的票,相当于泛型丢失。

extends的场景是put(add)功能受限,而super的场景是get功能受限。下例中,以加菲猫、猫、动物为例,说明extends和super的详细语法差异:

package Test;

import java.util.List;

//用动物的猫科与加菲猫的继承关系说明extends和super在集合中的意义
public class AnimalCatGarfield {
    public static void main(String[] args){

        //第一段:声明三个一次继承的类的集合:Object > 动物 > 猫 > 加菲猫
        List<Animal> animal = new ArrayList<Animal>();
        List<Cat> cat = new ArrayList<Cat>();
        List<Garfield> garfield = new ArrayList<Garfield>();

        animal.add(new Animal());
        cat.add(new Cat());
        garfield.add(new Garfield());

        //第二段:测试赋值操作
        //下行编译出错。只能赋值Cat或者Cat子类的集合
        List<? extends Cat> extendsCatFromAnimal = animal;
        List<? super Cat> superCatFromAnimal = animal;

        List<? extends Cat> extendsCatFromCat = cat;
        List<? super Cat> superCatFromCat = cat;

        List<? extends Cat> extendsCatFromGarfield = garfield;
        //下行编译出错,只能赋值Cat或者Cat父类的集合
        List<? super Cat> superCatFromGarfield = garfield;

        //第三段:测试add方法
        //下面三行中所有的<? extends T>都无法进行add操作,编译均出错
        extendsCatFromCat.add(new Animal());
        extendsCatFromCat.add(new Cat());
        extendsCatFromCat.add(new Garfield());

        //下行编译出错。只能添加Cat或Cat子类的集合
        superCatFromCat.add(new Animal());
        superCatFromCat.add(new Cat());
        superCatFromCat.add(new Garfield());

        //第四段:测试get方法
        //所有的super操作能够返回元素,但是泛型丢失,只能返回Object对象

        //以下extends操作能够返回元素
        Object catExtends2 = extendsCatFromCat.get(0);
        Cat catExtends1 = extendsCatFromCat.get(0);
        //下行编译出错。但是Cat集合从Garfield赋值而来,但类型擦除后,是不知道的
        Garfield garfield1 = extendsCatFromGarfield.get(0);

    }

}

class Animal{

}

class Cat extends Animal{

}

class Garfield extends Cat{

}
  • 第一段,就是三个泛型集合,可以理解为三个不同的笼子,List<Animal>住的是动物,List<Cat>住的是猫,List<Garfield>住的是加菲猫。继承关系为:Garfield继承自Cat,Cat继承自Animal。
  • 第二段,以Cat类为核心,因为它有父类(Animal)也有子类(Garfield)。定义类型限定集合,分别为List<? extends Cat>和List<? super Cat>。理解这两个概念时,暂时不要引入上界与下界,专注于代码本身。把List<Cat>对象赋值给两者都是可以的。但是把List<Animal>赋值给List<? extends Cat>时会编译出错,因为能赋值给<? extends Cat>的类型,只有Cat自己和Cat的子类集合。尽管它是类型安全的,但依然有泛型信息,因而从笼子里取出来的必然是只猫,而List<Animal>里面可能住着毒蛇、老虎、狮子等其他动物。把List<Garfield>赋值给List<? super Cat>时,也会编译报错,因为能赋值给List<? super Cat>类型的,只有Cat自己和Cat的父类集合。
  • 第三段,所有的List<? extends T>都会编译出错,无法进行add操作,这是因为除了null以外,任何元素都不能被添加进List<? extends T>集合内。List<? super Cat>可以往里面增加元素,但是只能添加Cat自身及子类对象,加入放一块石头,则明显违背了Animal大类的性质。
  • 第四段,所有List<? super T>集合可以执行get操作,虽然能够返回元素,但是类型丢失,只能返回Object对象。List<? extends Cat>可以返回带类型的元素,但只能返回Cat自身及其父类对象,因为子类类型被擦除了。

Java中的通配符“上界”与“下界”:

  • <? extends T>被称为上界,只能放T及T子类的集合,只能取(get),不能添加(add)。如果你是为了调用T的方法,请使用<? extends T>,因为可以使用get操作。
  • <? super T>被称为下界,只能放T及T父类的集合,只能添加(add),不能取(get)。如果你是为了往容器里存储数据,请使用<? super T>,因为可以使用add操作。

猜你喜欢

转载自blog.csdn.net/weixin_41047704/article/details/86475356
今日推荐