0%

Java 泛型(二):解析通配符

1. 通配符有哪几类

  • ? 表示通配符
  • Java 中通配符分三类
    • <?>: 无限定通配符
    • <? extends T>: 有限定通配符,匹配 TT某个子类型
    • <? super T>: 超类型通配符,匹配 T某个父类型

2. <T extends E><? extends E> 到底有什么关系

  • <T extends E> 用于定义类型参数,它声明了一个类型参数 T,可放在泛型类定义中类名后面、泛型方法返回值前面
  • <? extends E> 用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是 EE 的某个子类型

3. 用通配符形式重写下面方法声明代码

1
2
3
4
5
public <T extends E> void addAll(DynamicArray<T> c) {
for(int i = 0; i < c.size; i++) {
add(c.get(i));
}
}
1
2
3
4
5
6
//泛型的通配符形式的类型参数 ? 可以写在方法参数里,而不用声明在方法返回值之前
public void addAll(DynamicArray<? extends E> c) {
for(int i = 0; i < c.size; i++) {
add(c.get(i));
}
}

4. 用通配符形式重写下面方法声明代码

1
2
3
4
5
6
7
8
public static <T> int indexOf(DynamicArray<T> arr, Object obj) {
for(int i = 0; i < arr.size(); i++) {
if(arr.get(i).equals(obj)) {
return i;
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
//泛型的通配符形式的类型参数 ? 可以写在方法参数里,而不用声明在方法返回值之前
public static int indexOf(DynamicArray<?> arr, Object obj) {
for(int i = 0; i < arr.size(); i++) {
if(arr.get(i).equals(obj)) {
return i;
}
}
return -1;
}

5. 下面代码是否正确

1
2
3
4
5
6
DynamicArray<Integer> ints = new DynamicArray<> ();
DynamicArray<? extends Number> numbers = ints;
Integer a = 200;
numbers.add(a); //Java 编译报错
numbers.add((Number) a); //Java 编译报错
numbers.add((Object) a); //Java 编译报错
  • 三个 add() 方法都是非法的,Java 编译器会报错
  • 上面通配符有一个重要的限制:只能读,不能写
  • 如果允许写入, Java 就无法确保类型安全性,这显然违背了 Java 关于类型安全的承诺,所以 Java 干脆禁止

6. 下面代码是否正确,怎样解决

1
2
3
4
5
public static void swap(DynamicArray<?> arr, int i, int j) {
Object tmp = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, tmp);
}
  • Java 编译报错

  • 解决方法:借助带类型参数的泛型方法(Java 容器类中就有类似这样的用法,公共的 API 是通配符形式,形式更简单,但内部调用带类型参数的方法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static <T> void swapInternal(DynamicArray<T> arr, int i, int j) {
    T tmp = arr.get(i);
    arr.set(i, arr.get(j));
    arr.set(j, tmp);
    }

    public static void swap(DynamicArray<?> arr, int i, int j) {
    swapInternal(arr, i, j);
    }

7. 使用通配符形式简化下面方法声明的代码

1
2
3
4
5
6
//简化前
public static <D, S extends D> void copy(DynamicArray<D> dest, DynamicArray<S> src) {
for(int i = 0; i < src.size(); i++) {
dest.add(src.get(i));
}
}
1
2
3
4
5
6
//简化后
public static <D> void copy(DynamicArray<D> dest, DynamicArray<? extends D> src) {
for(int i = 0; i < src.size(); i++) {
dest.add(src.get(i));
}
}

8. 下面代码能使用通配符优化吗,为什么

1
2
3
4
5
6
7
8
9
public static <T extends Comparable<T>> T max(DynamicArray<T> arr) {
T max = arr.get(0);
for(int i = 1; i < arr.size(); i++) {
if(arr.get(i).compareTo(max) > 0) {
max = arr.get(i);
}
}
return max;
}
  • 不能
  • 如果返回值依赖于类型参数,则不能用通配符

9. 泛型方法到底应该用通配符的形式还是加类型参数,两者到底有什么关系

  • 通配符形式都可以用类型参数的形式来替代,通配符能做的,类型参数都能做
    • 通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好。所以,能用通配符的就用通配符
    • 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数
  • 通配符形式和类型参数往往配合使用,比如,上面的 copy() 方法,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型

10. 下面代码是否正确,怎样解决

1
2
3
4
5
6
7
8
9
10
11
12
//在 DynamicArray 中添加一个方法
pubilc void copyTo(DynamicArray<E> dest) {
for(int i = 0; i < size; i++) {
dest.add(get(i));
}
}

DynamicArray<Integer> ints = new DynamicArray<Integer> ();
ints.add(100);
ints.add(34);
DynamicArray<Number> numbers = new DynamicArray(Number);
ints.copyTo(numbers);
  • Java 编译报错。因为期望的类型是 DynamicArray<Integer>,不是 DynamicArray<Number>

  • 解决方法:使用超类型通配符

    1
    2
    3
    4
    5
    public void copyTo(DynamicArray<? super E> dest) {
    for(int i = 0; i < size; i++) {
    dest.add(get(i));
    }
    }

11. 下面代码是否正确,怎样解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base implements Comparable<Base> {
private int sortOrder;

public Base(int sortOrder) {
this.sortOrder = sortOrder;
}

@Override
public int compareTo(Base o) {
if(sortOder < o.sortOrder) {
return -1;
} else if(sortOrder > o.sortOrder) {
return 1;
} else {
return 0;
}
}
}

class Child extends Base {
public Child(int sortOrder) {
super(sortOrder);
}
}

DynamicArray<Child> childs = new DynamicArray<Child> ();
childs.add(new Child(20));
childs.add(new Child(80));
Child maxChild = max(childs);
  • Java 编译报错,类型不匹配
  • 原因是,我们可能会以为 Java 会将 max() 方法 的类型参数 T 推断为 Child 类型,但类型 T 的要求是 extends Comparable<T>,而 Child 并没有实现 Comparable<Child>,它实现的是 Comparable<Base>
  • 解决方法:使用超类型通配符,修改 max() 方法 声明,即:public static <T extends Comparable<? super T>> T max(DynamicArray<T> arr)
  • 这么修改一下就可以了,这种写法比较抽象,将 T 替换为 Child,即:Child extends Comparable<? super Child>,因为 <? super Child> 可以匹配 Base,所以整体就是匹配的

12. 下面这种写法是否正确

1
public <T super E> void copyTo(DynamicArray<T> dest) {} //非法
  • Java 不支持这种语法
    • 对于超类型通配符不能用参数类型替代
    • 类型参数限定只有 extends 形式,没有 super 形式
  • 对于有限定的通配符形式 <? extends E>可以用类型参数替代

13. 三种通配符的比较

  • 它们的目的都是为了使方法接口更为灵活,可以接受更为广泛的类型
  • <? super E> 用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象,它不能被类型参数形式替代
  • <?><? extends E> 用于灵活读取,使得方法可以读取 EE 的任意子类型的容器对象,它们可以用类型参数的形式替代,但通配符形式更为简洁
-------------------- 本文结束感谢您的阅读 --------------------