extends通配符转载

原创
小哥 3年前 (2022-10-27) 阅读数 50 #大杂烩

我们已经讨论过泛型继承关系: Pair<Integer> 不是 Pair<Number> 的子类。

假设我们定义 Pair<T>

public class Pair { ... }

然后,我们瞄准 Pair<Number> Type编写接收参数类型的静态方法。 Pair<Number>

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

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

int sum = PairHelper.add(new Pair(1, 2));

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

由于实际参数是 Integer 类型,请尝试传入 Pair<Integer>

public class Main {

}

class Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } }

Run

直接运行时会出现编译错误:

incompatible types: Pair cannot be converted to Pair

原因是显而易见的,因为 Pair<Integer> 不是 Pair<Number> 因此, add(Pair<Number>) 不接受参数类型 Pair<Integer>

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

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

实际类型为 Integer ,引用类型为 Number ,没问题。问题是方法参数类型是固定的,只能传入。 Pair<Number>

没有办法让方法参数接受。 Pair<Integer> ?有一种方法,这就是使用。 Pair<? extends Number> 使该方法接收所有泛型类型 NumberNumber 子类的 Pair 类型。我们按如下方式重写代码:

public class Main {

}

class Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } }

Run

通过这种方式,该方法被传入。 Pair<Integer> 类型,则它符合参数。 Pair<? extends Number> 类型。此用途 <? extends Number> 的一般定义称为上界通配符(Upper Bounds Wildcards),即泛型类型。 T 的上界 Number 了。

除了能够通过 Pair<Integer> 类型,我们也可以传入 Pair<Double> 类型, Pair<BigDecimal> 类型等,因为 DoubleBigDecimal 都是 Number 的子类。

如果我们检查一下对 Pair<? extends Number> 类型调用 getFirst() 方法,则实际的方法签名将变为:

 getFirst();

也就是说,返回值为 NumberNumber 因此,可以安全赋值给 Number 类型的变量:

Number x = p.getFirst();

那么,我们就无法预测实际的类型是 Integer 例如,无法编译以下代码:

Integer x = p.getFirst();

这是因为实际返回类型可能是 Integer ,也可能是 Double 或其他类型,则编译器只能确定该类型必须是 Number 子类(包括 Number 类型本身),但无法确定具体类型。

让我们再看一遍。 Pair<T>set 方法:

public class Main {

}

class Pair { private T first; private T last;

public Pair(T first, T last) {
    this.first = first;
    this.last = last;
}

public T getFirst() {
    return first;
}
public T getLast() {
    return last;
}
public void setFirst(T first) {
    this.first = first;
}
public void setLast(T last) {
    this.last = last;
}

}

Run

毫不奇怪,我们得到一个编译错误:

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> 接口,该接口实现类似于“变量数组”的列表,其主要功能包括:

public interface List {
    int size(); // 获取个数
    T get(int index); // 根据索引获取指定的元素。
    void add(T t); // 添加新元素
    void remove(T t); // 删除现有元素
}

现在,让我们定义一个方法来处理列表的每个元素:

int sumOfList(List list) {
    int sum = 0;
    for (int i=0; i

为什么我们定义的方法参数类型是 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(? extends Integer) 这些方法。换句话说,这是一对参数。 List<? extends Integer> 创建只读方法(恶意调用)。 set(null) 除外)。

使用extends限定T类型

在定义泛型类型时 Pair<T> 也可以使用。 extends 使用通配符进行限定 T 的类型:

public class Pair { ... }

现在,我们只能定义:

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

因为 NumberIntegerDouble 都符合 <T extends Number>

Number 将不编译类型:

Pair p1 = null; // compile error!
Pair p2 = null; // compile error!

因为 StringObject 都不符合 <T extends Number> 因为他们不是 Number 类型或 Number 的子类。

小结

使用类似 <? extends Number> 当通配符是方法参数时,表示:

  • 方法可以在内部调用以获取。 Number 引用的方法,例如: Number n = obj.getFirst();

  • 方法内部无法调用传入 Number 引用的方法( null 例外),例如: obj.setFirst(Number n);

也就是说,用一句话概括:使用 extends 通配符表示它可以读取,但不能写入。

使用类似 <T extends Number> 在定义泛型类时,它意味着:

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门