跳转至

第8章 泛型程序设计

定义简单泛型类

下面代码实现了 C++ 中的模板类 pair

class Pair<F extends Comparable<F>, S extends Comparable<S>> implements Comparable<Pair<F, S>> {
    public F first = null;
    public S second = null;

    public Pair(F f, S s) {
        first = f;
        second = s;
    }

    @Override
    public int compareTo(Pair<F, S> o) {
        int d = first.compareTo(o.first);
        return d != 0 ? d : second.compareTo(o.second);
    }
}

泛型方法

下面方法是一个泛型方法,返回任意类型数组的中间元素:

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

在大多数情况下,调用泛型方法时可以省略具体的类型参数,编译会自动推倒并匹配。

类型变量的限定

在定义泛型的位置可以可以对泛型进行限定,例如可以声明泛型必须是实现某个接口。

下面的例子是一个求出数组中最小和最大元素的泛型方法,其中限定了泛型只能是实现了 Comparable 接口的类:

class ArrayAlg {
    public static <T extends Comparable> Pair<T, T> minmax(T[] a) {
        if (a == null || a.length == 0)
            return null;
        T m = a[0], M = a[0];
        for (int i = 1; i < a.length; i++) {
            if (a[i].compareTo(m) < 0)
                m = a[i];
            else if (a[i].compareTo(M) > 0)
                M = a[i];
        }
        return new Pair<>(m, M);
    }
}

Java 泛型和 C++ 模板类的区别

  • Java 泛型可以对泛型的类型加以限制,而 C++ 不行。
  • Java 虚拟机没有泛型类型对象,类型变量会被“擦除”,并替换为限定类型(如果是无限定类型的变量则替换为 Object)。而 C++ 会为每个模板的实例化产生不同的类型,导致“模板代码膨胀”。
  • Java 中不能用基本类型实例化类型参数。

泛型的限制与局限性

  1. 不能用基本类型实例化类型参数 类型会被擦除,擦除之后的类型(例如 Object )不能存储 int 等基本类型。

  2. 运行时类型查询只适用于原始类型。 不能使用 instanceof 查询一个对象是否属于某个泛型类型,编译器会报错,如果使用强制类型转换来查询,则会得到一个警告。 对泛型变量使用 getClass 总是返回原始类型。

  3. 不能使用 new 实例化一个参数化类型的数组。

    例如下面代码就是非法的:

    var table = new Pair<String, String>[10]; // 错误
    
    !!! note "注意"
    
        声明 `Pair<String, String>[]` 是合法的,仅仅是不能用 Pair<String, String>[10] 初始化这个变量。
    

  4. 变长参数与泛型共用警告

    会导致堆污染,因此会得到一个警告。

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

    可以改成数组:

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

  5. 不能实例化类型变量

    不能在类似 new T(...) 的表达式中使用类型变量

    public Pair() {first = new T(); second = new U();} // 错误
    

    这同样是因为类型被擦除后 T() 将变成 Object()

    解决的办法是让调用者提供一个构造器表达式。

  6. 不能构造泛型数组。

    T[] mm = new T[2]; // 错误
    
  7. 不能使用带有类型变量的静态字段和方法。

    public class Singleton<T> {
        private static T singleInstance; // 错误
    
        public static T getSingleInstance() // 错误
        {}
    }
    
  8. 能不能抛出或捕获泛型类的实例。

    泛型类扩展 Throwable 是不合法的。

泛型类型的继承规则

  • 无论 ST 有什么关系,Pair<T, T>Pair<S, S> 之间都没有继承关系。
  • 泛型类可以扩展或实现其他的泛型类。例如 ArrayList<T> 实现了 List<T> 接口,这意味着一个 ArrayList<Manager> 可以转换为一个 List<Manger>,但是,即使 ManagerEmployee 的子类,但 ArrayList<Manager> 也不是一个 ArrayList<Employee> 或者 List<Employee>

通配符类型

  • ? extend Employee 表示 EmployeeEmployee 的子类。在类型参数中使用 ? extend 的类型可以使用其中的访问器方法,但不能使用更改器方法。
  • ? super Manager 表示 ManagerManager 的子类。在类型参数中使用 ? super 的类型可以使用其中的更改器方法,但不能使用访问器方法。

最后更新: 2023-12-17
创建日期: 2023-12-12