通配符

泛型允许使用通配符“?”,通配符“?”是所有泛型的共同父类,它表示任何泛型,因此List<?>
是List、List这类的父类。在读取List<?>类型的集合时,都是安全并且可以读取到的,不管list的真实类型是什么,它包含的都是Object;但是如果想把一个对象写入List<?>则是不行的,因为并不知道List<?>的真实类型,所以不允许写入对象(null除外)。

注意事项
通配符“?”不能声明在类上:
image.png
通配符不能用在泛型方法声明上,返回值类型不能使用“<?>”:
image.png
通配符“?”不能用于创建对象上,一般用于对象的引用上:
image.png
image.png

限定符

在java泛型中,? 表示通配符,代表未知类型,< ? extends Object>表示上边界限定通配符,< ? super Object>表示下边界限定通配符。
注意通配符 与 T 的区别:
T:作用于模板上,用于将数据类型进行参数化,不能用于实例化对象。
?:在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象定义。

上界限定符

<? extends A>是一个上界通配符,它的意思是限定“?”的类型必须是A类的子类或者A类本身,如果A类是一个接口,那么“?”必须是A类的一个实现类。
比如:<? extend Number> ,表示所有继承Number的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道,故add受阻。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Number,所以,我都可以用以最大的父类Number来get,也就是把所有的子类向上转型为Number。如:

  1. List<? extends Number> eList = null;
  2. eList = new ArrayList<Integer>();
  3. //语句1,正确:取出Number(或者Number子类)对象直接赋值给Number类型的变量是符合java规范的。
  4. Number numObject = eList.get(0);
  5. //语句2,错误:取出Number(或者Number子类)对象直接赋值给Integer类型(Number子类)的变量是不符合java规范的。
  6. Integer intObject = eList.get(0);
  7. //语句3,错误:List<? extends Number> eList并没有确定实例化对象的具体类型,因此无法add具体对象至列表。
  8. eList.add(new Integer(1));

上界类型通配符add方法受限,但可以获取列表中的各种类型的数据,并赋值给父类型(extends Number)的引用。因此如果想从一个数据类型里获取数据,就使用 ? extends 通配符。

下界限定符

<? super B>是一个下界通配符,它的意思是限定“?”的类型必须是B类的父类或者B类本身。
比如:<? super Integer>,表示Integer的所有父类,包括Integer,一直可以追溯到老祖宗Object 。那么当我add的时候,不能add Integer的父类,因为List<? super Integer>并没有确定里面存放的到底是哪个父类。但是可以add Integer及其子类。因为不管子类是什么类型,它都可以向上转型为Integer及其所有的父类甚至转型为Object 。但是当get的时候,Integer的父类这么多,除了Object,其他的都不行。如:

  List<? super Integer> sList = null;
        sList = new ArrayList<Number>();

        //语句1,错误:List<? super Integer> 无法确定sList中存放的对象的具体类型,因此sList.get获取的值存在不确定性,子类对象的引用无法赋值给兄弟类的引用,父类对象的引用无法赋值给子类的引用,因此语句错误。
        Number numObj = sList.get(0);
        //语句2,错误:同上理由
        Integer intObj = sList.get(0);
        //语句3,正确:所以类都可以转化为老祖宗Object
        Object object = sList.get(0);
        //语句4,正确:子类对象的引用可以赋值给父类对象的引用,因此语句正确
        sList.add(new Integer(1));

下界类型通配符get方法受限,但是可以add下界的子类或其本身。因此如果想把一个对象写进一个数据结构,就使用 ? super 通配符。

总结

  • 限定通配符总是包括自己
  • 上界类型通配符:add方法受限
  • 下界类型通配符:get方法受限
  • 如果想从一个数据类型里获取数据,使用 ? extends 通配符
  • 如果想把对象写入一个数据结构里,使用 ? super 通配符
  • 如果既想存,又想取,那就别用限定符
  • 不能同时声明泛型通配符上界和下界