08 泛型程序设计

2019/03/28 posted in  Java核心技术

使用泛型机制编写的程序代码要比哪些杂乱地使用Object变量,然后在进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。

1 为什么要使用泛型程序设计

泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。

1.1 类型参数的好处

不使用泛型的问题:

  1. 当获取一个值时必须进行强制类型转换。
  2. 操作时没有错误检查。

泛型提供的解决方案类型参数(type parameters),使得程序具有更好的可读性和安全性。

如ArrayList,声明时即可知道数据类型,并且添加数据时有检查,可以避免插入错误类型的数据。

1.2 谁想成为泛型程序员

泛型程序员的任务就是预测出所用类的未来可能有的所有用途。
通配符类型(wildcard type),可以帮助库的构建者编写出尽可能灵活的方法。

2 定义简单泛型类

一个泛型类(generic class)就是一个或多个类型变量的类。

public class Pair<T> {
    
    private T first;
    private 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 void setFirst(T first) { this.first = first; }

    public T getSecond() { return second; }

    public void setSecond(T second) { this.second = second; }
}

Pair类引入了一个类型变量T,用<>括起来,并放在类型后面。泛型类可以有多个类型变量。
类定义中的 类型变量 指定 方法的返回类型 以及 域和局部变量的类型。

3 泛型方法

定义一个带有类型参数的简单方法:

class ArrayAlg {

    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

4 类型变量的限定

有时,类或者方法需要对类型变量加以约束。解决方案是将T限制为实现某个接口。

public static <T extends Comparable> T min(T[] a)...

一个类型变量或通配符可以有多个限定:

T extends Comparable & Serializable

5 泛型代码和虚拟机

虚拟机没有泛型类型对象--所有对象都属于普通类。

5.1 类型擦除

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。

5.2 翻译泛型表达式

当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。

5.3 翻译泛型方法

类型擦除也会出现在泛型方法中。

类型擦除与多态发生冲突。解决这个问题,编译器会生成一个桥方法(bridge method)。

Java泛型转换的事实:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有的类型参数都用它们的限定类型替换。
  • 桥方法被合成来保存多态。
  • 为保持类型安全性,必要时插入强制类型转换。

5.4 调用遗留代码

设计Java泛型类型时,主要目标是运行泛型代码和遗留代码之间能够互操作。

6 约束与局限性

Java泛型使用时要考虑一些限制,大多数限制都是由类型擦除引起的。
(todo暂时只有介绍,没有原因说明)

6.1 不能用基本类型实例化类型参数

没有Pair<double>,只有Pair<Double>。

原因是类型擦除之后,Pair类含有Object类型的域,而Object不能存储double值。

6.2 运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。

if (a instanceof Pair<String>) //error
Pair<String> p = (Pair<String>) a;//Warning

Pair<T>的gatClass将返回Pair.class

6.3 不能创建参数化类型的数组

Pair<String>[] table = new Pair<String>[10];//error

如果需要收集参数化类型对象,使用ArrayList<Pair<String>>。

6.4 Varargs警告

@SuppressWarnings("unchecked")和@SafeVarargs抑制警告。

6.5 不能实例化类型变量

不能使用像new T(...),new T[...]或T.class这样的表达式中的类型变量。

6.6 不能构造泛型数组

public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; ...}//error

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

public class Singleton<T>{
    private static T instance;  //error
    public static T getInstance(){...}  //error
}

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

public class Problem<T> extends Exception {/**...**/}//error

6.9 可以消除对受查异常的检查

6.10 注意擦除后的冲突

public boolean equals(T value){...}//error

方法擦除equals(T)就是equals(Object),与Object.equals方法冲突。

7 泛型类型的继承规则

无论S与T有什么关系,通常Pair<S>与Pair<T>没有什么联系。

8 通配符类型

8.1 通配符的概念

通配符类型中,运行类型参数变化。如Pair<? extends Employee>,表示任何泛型Pair类型。

8.2 通配符的超类型限定

通配符还可以指定一个超类型限定,? super Manager
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

public static <T extends Comparable<? super T>> T min(T[] a)

有可能被声明为使用类型T的对象,也有可能使用T的超类型。

8.3 无限定通配符

还可以使用无限定的通配符,如Pair<?>。

Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始Pair类的方法。它对于许多简单操作非常有用,比如hasNulls(Pair<?> p)。

8.4 通配符捕获

9 反射和泛型

9.1 泛型Class类

9.2 使用Class参数进行类型匹配

9.3 虚拟机中的泛型类型信息