类型通配符 (?)

Posted woshi123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类型通配符 (?)相关的知识,希望对你有一定的参考价值。

类型通配符

如果Sub是Par的一个子类型(子类或子接口),而G是具有泛型声明的类或接口,G并不是G的子类型!

在早期的Java中,允许Integer[]数组赋值给Number[]变量存在着缺陷:

Integer[] integers = new Integer[5];
Number[] numbers = integers;
//下面程序会报异常java.lang.ArrayStoreException: java.lang.Double
//因为0.5不是Integer
numbers[0] = 0.5;

因此,Java在泛型设计时进行了改进,它不再允许把List对象赋给List变量:

List<Integer> list = new ArrayList<>();
//编译错误
List<Number> list1 = list; //这俩也不是父类子类的关系

Java泛型的设计原则是:只要代码在编译时没有出现警告,就不会遇到运行时的ClassCastException异常。

使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List),问号可以匹配任何类型。

public void test(List<?> list){
    for (int i = 0;i<list.size();i++){
        System.out.println(list.get(i));

    }
}

现在,使用任何类型的List来调用这个方法,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么,它包含的都是Object。

但这种带通配符的List仅表示他是各种泛型的父类,并不能把元素加入到其中:

List<?> list = new ArrayList<>();
//编译错误
list.add(new Object());

因为程序无法确定list集合中元素的类型,所以不能向其中添加对象。

类型通配符的上限

写一个抽象类Shape:

public abstract class Shape {
    public abstract void draw();
}

写一个子类Circle:

class Circle extends Shape{

    @Override
    public void draw() {
        System.out.println("?");
    }
}

写一个子类Rectangle:

class Rectangle extends Shape{

    @Override
    public void draw() {
        System.out.println("??");
    }
}

写一个测试类Canvas:

class Canvas{
    public void drawAll(List<Shape> shapes){
        for(Shape s:shapes){
            s.draw();
        }
    }

    public static void main(String[] args) {
        List<Circle> circleList = new ArrayList<>();
        Canvas canvas = new Canvas();
        //因为List<Shape>不是List<Circle>的父类,下面程序编译错误
        //canvas.drawAll(circleList);
    }
}

为了能够传入List,我们可以设置类型通配符。

将Canvas测试类重写??:

class Canvas{
    public void drawAll(List<?> shapes){
        for(Object shape:shapes){
            //增加了强转
            Shape s = (Shape)shape; 
            s.draw();
        }
    }

    public static void main(String[] args) {
        List<Circle> circleList = new ArrayList<>();
        Canvas canvas = new Canvas();
        //编译不再报错
        canvas.drawAll(circleList);
    }
}

这样也是一种解决办法,但这种解决显得比较笨拙(因为还要强转)。

实际上需要一种泛型表示方法,它可以表示所有的Shape泛型List的父类。为了满足这种需求,Java泛型提供了被限制的类型通配符:

class Canvas{
    //表示所有Shape泛型List的父类
    public void drawAll(List<? extends Shape> shapes){
        for(Shape s:shapes){
            s.draw();
        }
    }

    public static void main(String[] args) {
        List<Circle> circleList = new ArrayList<>();
        Canvas canvas = new Canvas();

        canvas.drawAll(circleList);
    }
}

这样就可以把List对象当成List<? extends Shape>来使用,即List<? extends Shape>可以表示List,List的父类。

此时Shape 是这个通配符?的上限(upper bound)

在定义类或者接口的时候设定通配符上限

public class Orangle<T extends Number> {
    T org;

    public static void main(String[] args) {
        Orangle<Integer> oi = new Orangle<>();
        Orangle<Double> od = new Orangle<>();
        //下列代码将引发编译异常,下面代码试图把String类型赋给T形参
        //String不是Number的子类型
   //     Orangle<String> os = new Orangle<>(); //not within its bound
    }
}

还有更狠的情况:程序为类型形参设定多个上限(至多一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。

//表明T类型必须是Number类或其子类,并必须实现java.io.Serializable和Comparable接口
public class Demo1 <T extends Number & Serializable & Comparable> {
}

以上是关于类型通配符 (?)的主要内容,如果未能解决你的问题,请参考以下文章

jsonpath的使用

Java 泛型概述与应用

为什么Java Collector.toList()在其返回类型中需要通配符类型占位符?

在代码片段中包含类型转换

使用通配符增强泛型

对这个带有 & 不带 = 的代码片段返回类型感到非常困惑