super通配符转载

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

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

请检查以下内容 set 方法:

void set(Pair p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}

传入 Pair<Integer> 被允许,但传入的 Pair<Number> 是不允许的。

extends 这一次,我们想要接受,而不是通配符。 Pair<Integer> 类型,和 Pair<Number>Pair<Object> ,因为 NumberObjectInteger 的父类, setFirst(Number)setFirst(Object) 实际上允许接受 Integer 类型。

我们使用 super 用于重写此方法的通配符:

void set(Pair p, Integer first, Integer last) {
    p.setFirst(first);
    p.setLast(last);
}

注意到 Pair<? super Integer> 表示该方法参数接受所有泛型类型。 IntegerInteger 父类的 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;
}
public void setFirst(T first) {
    this.first = first;
}
public void setLast(T last) {
    this.last = last;
}

}

Run

考察 Pair<? super Integer>setFirst() 方法,其方法签名实际上是:

void setFirst(? super Integer);

因此,它可以安全地传递进来。 Integer 类型。

再考察 Pair<? super Integer>getFirst() 方法,其方法签名实际上是:

? super Integer getFirst();

这里需要注意的是,我们不能使用 Integer 要接收的类型 getFirst() 不会编译以下语句的返回值:

Integer x = p.getFirst();

因为如果传入的实际类型是 Pair<Number> ,则编译器不能 Number 类型转换 Integer

注:虽然 Number 是我们不能直接实例化的抽象类。然而,即使 Number 不是抽象类,还是不能在这里编译。此外,即将到来的 Pair<Object> 类型,则编译器不能也是 Object 类型转换 Integer

唯一一个可以接受 getFirst() 该方法的返回值为 Object 类型:

Object obj = p.getFirst();

因此,使用 <? super Integer> 通配符表示:

  • 允许调用 set(? super Integer) 方法传入 Integer 的引用;

  • 不允许呼叫 get() 方法获得 Integer 的引用。

唯一的例外是你可以得到 Object 的引用: Object o = p.getFirst()

换句话说,使用 <? super Integer> 作为方法参数的通配符表示该方法的内部代码只能针对该参数写入,而不能读取。

使用super限定T类型

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

public class Pair { ... }

现在,我们只能定义:

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

因为 NumberIntegerObject 都符合 <T super Number>

Integer 该类型及其子类将无法编译:

Pair p1 = null; // compile error!

对比extends和super通配符

让我们再复习一遍 extends 通配符。作为方法参数, <? extends T> 类型和 <? super T> 类型之间的区别在于:

  • <? extends T> 允许调用读取方法 T get() 获取 T 的引用,但不允许呼叫写方法 set(T) 传入 T 引用(传入)的 null 除外);

  • <? super T> 允许调用以写入方法 set(T) 传入 T 的引用,但不允许呼叫读方法 T get() 获取 T 引用的(GET Object 除外)。

一个是允许读而不允许写,另一个是允许写而不允许读。

首先,记住上面的结论,让我们来看看它。Java标准库的 Collections 类定义的 copy() 方法:

public class Collections {
    // 把src复制的每个元素dest中:
    public static  void copy(List dest, List src) {
        for (int i=0; i

它的作用是将一个 List 的每个元素依次加到另一个元素上。 List 在……里面。它的第一个论点是 List<? super T> ,表示目标 List ,第二个参数 List<? extends T> ,表明 List 。我们可以简单地使用 for 用于复制的循环。在……里面 for 循环,我们可以看到对于类型 <? extends T> 的变量 src ,我们可以安全地获得类型。 T 引用的类型,而对于类型 <? super T> 的变量 dest ,我们可以安全地通过 T 的引用。

这个 copy() 该方法的定义完美地说明了 extendssuper 的意图:

  • copy() 方法未在内部读取 dest 因为它不能被调用。 dest.get() 来获取 T 的引用;

  • copy() 该方法在内部也没有修改。 src 因为它不能被调用。 src.add(T)

这是通过编译器检查实现的。如果在方法代码中意外修改 src ,或不小心读到 dest ,将导致编译错误:

public class Collections {
    // 把src复制的每个元素dest中:
    public static  void copy(List dest, List src) {
        ...
        T t = dest.get(0); // compile error!
        src.add(t); // compile error!
    }
}

这个 copy() 该方法的另一个优点是,将一个 List<Integer> 添加到 List<Number> ,但不能以相反方向添加:

// copy List to List ok:
List numList = ...;
List intList = ...;
Collections.copy(numList, intList);

// ERROR: cannot copy List to List:
Collections.copy(intList, numList);

这些都是通过 superextends 通配符,并由编译器强制检查实现。

PECS原则

何时使用 extends ,何时使用 super ?为了便于记忆,我们可以使用PECS原则:Producer Extends Consumer Super。

如果你需要回来的话。 T ,它是制片人(Producer),以使用 extends 通配符;如果需要写入 T ,它是消费者(Consumer),以使用 super 通配符。

还是以 Collectionscopy() 方法为例:

public class Collections {
    public static  void copy(List dest, List src) {
        for (int i=0; i

需要返回 Tsrc 是制片人,因此被宣布为 List<? extends T> ,需要写 Tdest 是消费者,因此被声明为 List<? super T>

无限定通配符

我们已经讨论过 <? extends T><? super T> 角色作为方法参数。事实上,Java泛型还允许使用无限通配符(Unbounded Wildcard Type),即只定义了一个。 ?

void sample(Pair p) {
}

因为 <?> 通配符既没有 extends ,也没有 super ,因此:

  • 不允许呼叫 set(T) 方法并传入引用( null 除外);
  • 不允许呼叫 T get() 方法和获取 T 引用(仅获取 Object 引用)。

换句话说,既不会读也不会写,那只能做一些 null 判断:

static boolean isNull(Pair p) {
    return p.getFirst() == null || p.getLast() == null;
}

在大多数情况下,可以引入泛型参数 <T> 消除 <?> 通配符:

static  boolean isNull(Pair p) {
    return p.getFirst() == null || p.getLast() == null;
}

<?> 通配符的一个独特功能是: Pair<?> 是所有 Pair<T> 的超类:

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

上述代码可以正常编译和运行,因为 Pair<Integer>Pair<?> 可以安全地向上转换的子类。

小结

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

  • 在该方法内部,您可以调用传入的。 Integer 引用的方法,例如: obj.setFirst(Integer n);

  • 方法无法调用Get Inside Integer 引用的方法( Object 例外),例如: Integer n = obj.getFirst();

即使用 super 通配符表示您只能写而不能读。

使用 extendssuper 后跟通配符PECS原则。

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

  • 泛型类型是有限的 IntegerInteger 的超类。

无限定通配符 <?> 它很少使用,而且可以使用。 <T> 趁一切还好,换掉它 <T> 类型的超类。

地址: https://www.liaoxuefeng.com/wiki/1252599548343744/1265105920586976

版权声明

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