泛型

  • 没有泛型类的时候, 泛型程序用继承实现, ArrayList类只维护一个Object引用数组, 这样就会每次都需要进行强制类型转换
  • 现在是使用尖括号<String>
  • Java类库使用E表示集合类型, K,V表示键值对, T,U等表示任意类型

泛型类

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
public class Pair<T> {
T first;
T second;

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

public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newV) {
first = newV;
}
public void setSecond(T newV) {
second = newV;
}
}

泛型方法

1
2
3
4
5
class A {
public static <T> T getMiddle(T...a) {
return a[a.length / 2];
}
}

泛型代码和虚拟机

  • 无论何时定义一个泛型类, 都会自动提供一个相应的原始类型, 就是去掉类型参数以后的泛型类型名

  • 类型变量会被擦除, 替换为其限定类型, 如果没有限定类型就是Object

  • 泛型方法的类型擦除会存在一些问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class D extends Pair<LocalDate> {
    public void setSecond(LocalDate second) {
    if (second.compareTo(getFirst()) >= 0) {
    super.setSecond(second);
    }
    }
    ...
    }
    // 日期区间是一对LocalDate对象, 覆盖这个方法保证第二个值永远不小于第一个值, 类擦除以后就是
    class D extends Pair {
    public void setSecond(LocalDate second) {...}
    ...
    }
    // 但是还有另一个从Pair继承的setSecond方法
    public void setSecond(Object second) {}
    // 这两个方法参数不同, 所以是不同的方法, 但是他们不应该不同
    // 此时类型擦除和多态就产生了冲突, 编译器就会在D类中生成一个桥方法
    public void setSecond(Object second) {setSecond((LocalDate) second);}
    1
    2
    3
    4
    5
    6
    7
    // 同样, 在编译器中, 如果是getSecond(), 会同时存在两个
    public LocalDate getSecond();
    public Object getSecond();
    // 这两个方法只有返回类型不同, 不能这么编写代码
    // 但是虚拟机会根据参数类型和返回类型共同指定一个方法, 所以虚拟机可以正确处理这两个方法
    // 所以编译器可以为只有返回类型不同的两个方法生成字节码文件
    // 只是自己编写这样的代码不合法
  • 虚拟机中没有泛型, 只有普通的类和方法
  • 所有的类型参数都会替换为他们的限定类型
  • 会合成桥方法来保持多态
  • 会插入强制类型转换来保持类型安全性

限制

  1. 不能使用基本类型实例化类型参数
    • 不能使用Pair<double>, 因为在类型擦除后的Object类中不能存储基本类型, 只能存储Double
  2. 运行时类型查询只适用于原始类型
    1
    2
    3
    4
    5
    6
    7
    8
    if (a instanceof Pair<String>) // Error 实际上只会检测a是否是Pair的任意一个类型实例
    if (a instanceof Pair<T>) // Error
    Pair<String> p = (Pair<String>) a; // Warn

    Pair<String> stringp = ...;
    Pair<E> ep = ...;
    if (stringp.getClass() == ep.getClass()) {}
    // 这里一定相等, 因为两个getClass()返回值都是Pair.Class
  3. 不能创建参数化类型的数组
    1
    2
    3
    4
    5
    6
    7
    8
    var table = new Pair<String>[10]; // Error
    // 因为类型擦除以后, table的类型是Object[], 存储不正确的元素会抛出ArrayStoreException
    // 只是不允许创建, 但是声明依旧合法 即Pair<String>[]变量是合法的
    // 但是new Pair<String>[10]初始化会抛出异常
    // 可以声明通配符类型数组, 然后进行强制类型转换
    var table = (Pair<String>[]) new Pair<?>[10];
    // 结果不安全, 比如table[0]存储一个Pair<E>, 然后table[0].getFirst()调用一个String方法, 就会抛出ClassCastException
    // 可以直接使用ArrayList: ArrayList<Pair<String>>

Varargs警告

  • 如果向可变参数的方法传递一个泛型类型实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static <T> void addAll(Collection<T> coll, T... ts) {
for (T t : ts) {
coll.add(t);
}
}
// ts是一个数组, 包含所有提供的实参
Collection<Pair<String>> table = ...
Pair<String> p1 = ...
Pair<String> p2 = ...
addAll(table, p1, p2);
// 此时JVM必须创建一个Pair<String>类型数组, 违反规则
// 但是有所放松, 只会给出警告
// 使用@SuppressWarnings("unchecked") 去掉警告
// 或者Java 7还可以使用@SafeVarargs注解addAll方法
  • @SafeVarargs注解只能用于static, finalprivate的构造器或方法中, 其他方法都可能被覆盖, 会使这注解失去作用

不能实例化类型变量

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
public class Pair<T> {
private T first;
private T second;

public Pair() {
first = new T();
second = new T();
}
}
// 这个无参构造函数是不合法的, 因为类型擦除以后会变成new Object()
// Java 8之后, 可以使用构造器表达式解决这个问题
Pair<String> p = Pair.makePair(String::new);
// makePair()接收一个Supplier<T>, 一个函数式接口, 表示一个无参并且返回类型为T的函数
public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get, constr.get());
}

// 传统解决方法是调用反射Constructor.newInstance()构造泛型对象
first = T.class.getConstructor().newInstance(); // Error
// 因为T.class不合法, 会类型擦除为Object.class, 所以应该用如下方法:
public static <T> Pair<T> makePair(Class<T> cl) {
try {
return new Pair<>(cl.getConstructor.newInstance(), cl.getConstructor().newInstance());
} catch (Exception e) {
return null;
}
}
Pair<String> p = Pair.makePair(String.class);
// Class 类本身是泛型的, String.class 是Class<String>的唯一实例, 所以makePair()可以推断pair的类型

不能构造泛型数组

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
30
31
32
33
34
35
36
37
public static <T extends Comparable> T[] minmax(T... a) {
T[] mm = new T[2]; // Error
...
}
// 类型擦除, 所以总会构造一个Comparable[2]数组
// 如果数组只会作为类的私有实例字段, 可以将这个数组元素类型声明为擦除以后的类型, 再强制类型转换, 比如ArrayList
public class ArrayList<E> {
private Object[] elements;
...
@SuppressWarnings("unchecked")
public E get(int n) {
return (E) elements[n];
}
public void set(int n, E e) {
elements[n] = e;
}
}

// 但是minmax返回一个T[], 就无法使用
public static <T extends Comparable> minmax(T... a) {
var result = new Comparable[2];
...
return (T[]) result; // Warnings
}
String[] names = A.minmax("T", "A", "N");
// 编译器不会有警告, 但是Comparable[]转换为String[]会出现ClassCastException
// 所以最好提供一个构造器表达式
public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr, T... a) {
T[] result = constr.apply(2);
...
}
String[] names = A.minmax(String::new, "T", "A", "N");
// 或者利用反射
public static <T extends Comparable> T[] minmax(T... a) {
var result = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
...
}

泛型类的静态上下文中类型变量无效

  • 不能在静态方法或字段中引用类型变量
1
2
3
4
5
6
7
8
public class S<T> {
private static T si; // Error
public static T getSi() {
if (si == null) construct new instanceof T;
return si;
}
}
// 类型擦除以后, 只剩下S类, 只包含一个si字段, 所以非法

不能抛出或捕获泛型类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不能扩展Exception
public class Problem<T> extends Exception {...} // Error
// catch 子句不能使用类型变量
public static <T extends Throwable> void doWork(Class<T> t) {
try {...}
catch (T e) { // Error
...
}
}
// 但是在异常规范中允许使用类型变量
public static <T extends Throwable> void doWork(T t) throws T {
try {...}
catch (Throwable realCause) {
t.initCause(realCause);
throw t;
}
}

可以取消对检查型异常的检查

  • 正常必须对所有检查型异常提供一个处理器, 但是可以利用泛型取消这个检查
  • 只需要使用泛型类, 类型擦除和@SuppressWarnings("unchecked")
1
2
3
4
5
6
7
8
9
10
11
@SuppressWarnings("unchecked")
static <T extends Throwable> void throwAs(Throwable t) throws T {
throw (T) t;
}
Task.<RuntimeException>throwAs(e);
// 编译器就会认为e是一个非检查型异常
// 下面的代码会将所有的异常都转换为非检查型异常
try {...}
catch (Throwable t) {
Task.<RuntimeException>throwAs(t);
}

擦除以后可能会存在冲突

1
2
3
4
5
6
7
public class Pair<T> {
public boolean equals(T value) {
return first.equals(value) && second.equals(value);
}
}
// 实际上Pair有两个equals方法, boolean equals(String); boolean equals(Object);
// 只能重新命名这个方法
  • 假如两个接口类型是同一个接口的不同参数化, 一个类或者类型变量就不能同时作为这两个接口类型的子类
1
2
3
class E implements Comparable<E> {...}
class M extends E implements Comparable<M> {...} // Error
// 如果上述代码可行, 则M就会同时实现Comparable<E> 和Comparable<M>

泛型继承规则

  • 如果ME的子类, Pair<M>不会是Pair<E>的子类
1
Pair<E> b = new Pair<M>(1, 2); // 非法
  • 可以将参数化类型转换为一个原始类型

通配符类型

  • Pair<? extends E>允许类型参数变化, 表示任何Pair类型, 只要是E的子类

  • Pair<M>Pair<? extends E>的子类型

  • 可以使用? super M指定一个超类型限定, 限制为M的所有超类型

  • 带超类限定的通配符允许写入一个泛型对象, 带子类限定的通配符允许读取一个泛型对象

  • Comparable接口本身就是一个泛型类型

    1
    2
    3
    4
    5
    6
    public interface Comparable<T> {
    public int compareTo(T other);
    }
    // 类型变量指示了other参数的类型, String类实现了Comparable<String>
    public int compareTo(String other);
    public static <T extends Comparable<? super T>> pair<T> minmax(T[] a)
  • 无限定通配符Pair<?>

  • Pair<?>Pair本质区别在于可以使用任意的Object对象调用原始Pair类的setFirst()

  • 测试一个Pair是否包含Null引用, 不需要具体的类型

    1
    2
    3
    4
    5
    6
    public static boolean hasNulls(Pair<?> p) {
    return p.getFirst() == null || p.getSecond() == null;
    }
    // 将hasNulls转换为泛型方法, 可以避免使用通配符类型
    public static <T> boolean hasNulls(Pair<T> p);
    // 带有通配符版本的可读性更强
  • 交换Pair元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void swap(Pair<?> p);
    // 通配符不是一个类型变量, 所以不能作为类型编码
    ? t = p.getFirst(); // Error
    public static <T> void swapHelper(Pair<T> p) {
    T t = p.getFirst();
    p.setFirst(p.getSecond());
    p.setSecond(t);
    }
    // swapHelper是一个泛型方法, 但是swap不是, 它有一个固定的Pair<?>类型参数
    // 可以由swap调用swapHelper
    public static void swap(Pair<?> p) {swapHelper(p);}
    // 此时swapHelper()参数T捕获通配符, 并不知道通配符指示什么类型, 但是是一个明确的类型
    public static void minmaxB(M[] a, Pair<? super M> res) {
    minmaxB(a, res);
    PairAlg.swapHelper(res);
    }
    // 只有在编译器能够保证通配符表示单个确定的类型时才不会报错
    // ArrayList<Pair<T>>绝对不能捕获ArrayList<Pair<?>>中的通配符, 因为ArrayList可能包含两个Pair<?>, 而且指向了不同的类型

反射和泛型

泛型Class

  • Class类是泛型类, String.class实际上是Class<String>类的对象
  • 类型参数非常有用, 允许Class<T>的方法有更加特定的返回类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    T newInstance();
    // 返回这个类的实例, 由无参构造器获得, 返回类型声明为T, 避免强制类型转换
    T cast(Object obj);
    // 返回特定的对象, 给定对象的额实际类型是T的一个子类型, 会声明为T, 否则会抛出一个BadCastException
    T[] getEnumConstants();
    // 如果这个类不是一个Enum或者T类型枚举值的一个数组, 就会返回null
    Class<? super T> getSuperclass();
    // 返回这个类的超类, 如果T不是一个类, 或者T是Object, 则返回null
    Constructor<T> getConstructor(Class... parameterTypes);
    Constructor<T> getDeclaredConstructor(Class... parameterTypes);
    // 获得公共构造器, 或者有给定参数类型的构造器

使用Class<T>参数进行类型匹配

1
2
3
4
5
public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException {
return new Pair<>(c.newInstance(), c.newInstance());
}
// 调用makePair(E.class), E.class是一个Class<E>类型的对象
// makePair()的类型参数T 与E匹配, 编译器可以推断出这个方法返回一个Pair<E>