2021.6.3:extends通配符

1、方法参数的泛型继承

我们前面已经讲到了泛型的继承关系:Pair<Integer>不是Pair<Number>的子类。

假设我们定义了Pair<T>

public class Pair<T> {...}

然后,我们又针对Pair<Number>类型写了一个静态方法,它接收的参数类型Pair<Number>

public class PairHelper{
    static int add(Pair<Number> p){
        Number first=p.getFirst();
        Number last=p.getLast();
        return first.intValue()+last.intValue();
    }
}

上述代码可以正常编译。使用时,我们传入:


Pair<Integer> p = new Pair<Number>(123, 456);//正确
int sum = PairHelper.add(p);

注意:传入的类型是Pair<Number>,实际参数类型是(Integer , Integer)

既然是Integer类型,试试传入Pair<Integer>

Pair<Integer> p = new Pair<>(123, 456);//编译错误
int n = PairHelper.add(p);

运行时会得到一个编译错误:

incompatible types:Pair<Integer> cannot be converted to Pair<Number>

但是从add()方法的代码可知,传入Pair<Integer>是完全符合内部代码的类型规范,因为语句:

Number first = p.getFirst();
Number last = p.getLast();

问题在于方法参数类型定死了只能是Pair<Number>

static int add(Pair<Number> p)

如何使方法接收Pair<Integer>呢?

方法是使用Pair<? extends Number>使得方法接收所有泛型类型NumberNumber子类的Pair类型,我们要把代码改写如下:

static int add (Pair<? extends Number> p){
    Number first = p.getFirst();
    Number last = p.getLast();
    return first.intValue() + last.intValue();
}

这样一来,给方法传入Pair<Integer>类型时,它符合参数Pair<? extends Number>类型

这种使用<? extends Number>的泛型定义为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定为Number

除了传入Pair<Integer>类型,我们还可以传入Pair<Double>Pair<BigDecimal>类型等等,因为DoubleBigDecimal都是Number子类

如果我们考察对Pair<? extends Number>类型调用getFirst()方法,会发现实际的签名变成了:

<? extends Number> getFirst();

然后,我们不可预测实际类型就是Integer,例如,下面的代码是无法通过编译的:

Integer x = p.getFirst();//错误

这是因为实际返回类型可能是Integer,也可能是Double或者其他类型,然而编译器只能确定类型一定是Number子类(包括Number自身),只是具体类型无法确定

 

接下来,我们再来考察一下Pair<T>set方法:

class Pair<T>{
    private T first;
    private T last;
    ...
    public void setFirst(T first){
        this.first = first;
    }
    public void setLast(T last){
        this.last = last;
    }
}
static int add(Pair <? extends Number> p){
    Number first = p.getFirst();
    Number last = p.getLast();
    p.setFirst(new Integer(first.intValue() + 100));
    p.setLast(new Integer(last.intValue() + 100));
    return p.getFirst().intValue() + p.getLast().intValue();
}

 

不过,我们仍然会得到一个编译错误:

incompatible types: Integer cannot be converted to CAP#1
where CAP #1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number

编译错误发生在p.setFirst()传入的参数Integer类型

不过,既然p的定义是Pair<? extends Number>,那么setFirst(? extends Number)为什么不能传入Integer

原因还在于擦拭法。如果我们传入的pPair<Double>,显然它满足参数定义Pair<? extends Number>,然而,Pair<Double>setFirst()显然是无法接受Integer类型

这就是<? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型setFirst(? extends Number)

这里唯一的例外是给参数传入null

p.setFirst(null);//OK,但是后边会抛出NullPointerException
p.getFirst().intValue();//NullPointerException

extends通配符的作用

如果我们考察Java标准库的java.util.List<T>接口,它实现的是一个类似可变数组的List,主要功能包括:

public interface List<T> {
    int size();//数据项个数
    T get(int index);//查找
    void add(T t);//增加
    void remove(T t);//产出
}

现在,我们可以定义一个方法来处理List的每个元素:

int sumOfList(List<? extends Integer> list){
    int sum=0;
    for(int i=0;i<list.size();i++){
        Integer n =list.get(i);
        sum+=n;
    }
    return sum;
}

为什么我们定义的方法参数类型是List<? extends Integer>而不是List<Integer>?从内部方法代码看,传入List<? extends Integer>或者List<Integer>完全一样,但是,注意到List<? extends Integer>的限制:

  • 允许调用get()方法获取Integer的引用
  • 不允许调用set(? extends Integer)方法传入任何Integer的引用(null除外)

因此,方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)remove()这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法)。

使用extends限定T类型

在定义泛型类型Pair<T>的时候,也可以使用extends通配符限定T类型

public class Pair<T extends Number> { ... }

现在我们只能定义:

Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1,2);
Pair<Double> p3 = null;

因为Number、Integer和Double都符合<T extends Number>

非Number类型将无法通过编译:

Pair<String> p1 = null;
Pair<Object> p2 = null;

因为String、Object都不符合<T extends Number>,因为它们不是NumberNumber子类。

小结

1、使用类似<? extends Number>通配符作为方法参数时:

  • 方法内部可以调用get Number引用的方法,例如:Number n = obj.getFirst();
  • 方法内部无法传入set Number引用的方法(除了null),例如:obj.setFirst(Number n)

总而言之,使用extends通配符只能,不能

使用时只能用Number对象承接,因为传入的泛型类型可以是Pair<Integer>、Pair<Double>、Pair<BigDecimal>

<? extends Number> getFirst();//定义时

Number first = p.getFirst();//使用时

2、使用类似<T extends Number>定义泛型类时表示:

  • 泛型类型限定为Number及其子类(Integer、Double、BigDecimal)Pair类型,T只能是上述类型
    //正确
    Pair<Number> p1 = null;
    Pair<Integer> p2 = new Pair<>(1,2);
    Pair<Double> p3 = null;
    //错误
    Pair<String> p1 = null;
    Pair<Object> p2 = null;

3、<? extends Number><T extends Number>的区别:?用于方法参数的泛型定义、T用于泛型类的泛型定义。

 

posted @ 2021-06-03 11:14  ShineLe  阅读(35)  评论(0)    收藏  举报