泛型

用途

例如一个只能包含String的ArrayList

ArrayList<String> list = new ArrayList<>();

简单泛型类定义

public class Pair<T> {
    private T first;
    private T second;

    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,并使用<>括起来,并放在类名的后面,泛型类可以有多个类型变量

例如public class Pair<T, U>{}

类级别类型变量可以指定方法的返回类型、域和局部变量的类型

private T first;
public T getSecond() {
	return second;
}

注释:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示 集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时还可以用临近的字 母U和S)表示“任意类型

泛型方法

在类的返回值前,修饰符之后 用<>定义类型参数

方法可以是静态方法也可以是普通方法。

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

public <G> void test(G a) {
	System.out.println(a);
}

大部分情况下,当调用泛型方法时,可以省略类型参数,编译器可以自动推断出所调用的方法。

但偶尔编译器也会报错,例如下面的泛型方法

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

当调用

    double d = getMiddle(1.1D,1L, 2, 3);

编译器将报错,因为double类型不等于long类型,编译器无法推断。

可以使用doublelong的共同父类接受

  Number d = getMiddle(1.1D,1L, 2, 3);

类型变量限定

例如获取一个数组的最小值,其元素必须实现了Comparable

public static <T extends Comparable> T min(T... a) {
    if (a == null || a.length == 0)
        return null;
    T smallest = a[0];
    for (int i = 1; i < a.length; i++) {
        if (smallest.compareTo(a[i]) > 0) {
            smallest = a[i];
        }
        return smallest;
    }
    return smallest;
}

上面的代码对类型变量T进行了限制,其必须实现了Comparable接口。

现在min方法只能被实现了Comparable接口的类(例如String,Date)数组调用

一个类型变量可以有多个限定,限定类型使用&分割

例如

T extends Comparable & Serializable

限定可以有多个接口超类,但至多有一个类,如果有一个超类限定,它必须时限定列表中的第一个。

约束与限制

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

没有 Pair<double> ,只有 Pair<Double> 。当然,其 原因是类型擦除.

当包装器类型(wrapper type)不能接受替换时,可以使用 独立的类和方法处理它们。

运行时类型查询只使用原始类型

例如a是Pair<String>的一个实例

a instanceof Pair<String>会报错

只能使用

a instanceof Pair在使用instanceof或类型转换时类型参数会被忽略,且查询只会参数原始类型

例如

Pair<String> a = ...;
Pair<Employee> b = ...;
if(a.getClass() == b.getClass) // equal

比较结果为true,因为调用getClass返回都是Pair.class

不能抛出和捕获泛型类实例

因为Exception不是泛型类所以下面定义是错误的

public class Problem<T> extends Exception{}

也不能在catch子句使用类型变量,例如下面的语句无法通过编译

public static <T extends Throwable> void doWork(String[] args) {
    try {

    }catch ( T e)
    {

    }
}

在声明或参数中可以使用类型变量,例如下面是合法的

public static <T extends Throwable> void doWork(T t) throws T {
    try {

    }catch (Throwable realCasue)
    {
        throw t;
    }
}

不能使用参数化数组

例如一下表达式时非法的。

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

提示:如果需要收集参数化类型的对象,最好直接使用 ArrayList: ArrayList <Pair<String>> , 这样既安全又有效。

不能实例化类型变量

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

必须向限免这样设计才可以

    public static <T> Pair<T> makePair(Class<T> cl) throws IllegalAccessException, InstantiationException {
        return new Pair<T>(cl.newInstance(), cl.newInstance());
    }

调用方式

Pair<String> p = Pair.makePair(String.class)

Class类本身就是泛型类,例如String.class是一个Class<String>的实例(事实上,它是唯一的实例),因此makePair方法可以推断pair的类型

不能构造泛型数组

下面是错误的,类型擦除让这个方法永远构造Object[]

public static <T> minmax(T[] a)
{
	T[] mm = new T[2];//ERROR
}

如果数组仅作为类的私有域,可以将数组声明为Object [],并在需要时进行类型转换。

同理,以下方法返回一T[]数组最终只能是返回了一个Object []数组

public static <T> T[] minmax(T[] a)
{
    Object[] mm = new Object[2];
    return (T[])mm;//编译警告
}

当调用String[] ss = minmax(“a”,”vv”);时,将产生ClassCastException异常

这种情况下只能使用反射来构建数组

public static <T> T[] minmax(T[] a)
{
   T[] mm = (T[])Array.newInstance(a.getClass().getComponentType(),2);
   return mm;
}

注意只是不能直接构造泛型数组,对原数组进行转换或者操作都是可以的。

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

静态域或方法不能引用类型变量,以下代码是无效的

public class Singleton <T> {
    public static T getSingleInstance(){
        return singleInstance;
    }
    private static T singleInstance;
}

类型擦除冲突

类型擦除后方法与Object的方法冲突

当泛型类型被擦除时,无法创建引发冲突的条件,例如在泛型类中无法定义equals方法,以下代码时非法的

public class Pair<T> {
	public boolean equals(T obj) {
        return super.equals(obj);
    }
}

当擦除Pair的泛型类型时equalsObject.equals方法冲突,当然我们只能重命名解决冲突。

类或类型变量不能同时成为两个接口类型的之类

例如下述代码时非法的

class Calendar implements Comparable<Calendar> {
}
class GregorianCalendar extends Calendar   implements Comparable<GregorianCalendar>  {} //ERROR

GregorianCalendar 同时实现了 Comparable<Calendar> Comparable<GregorianCalendar>,这是同一个接口的不同参数化,这中使用方法是非法的。

泛型继承规则

类及其之类EmployeeManager

Pair<Manager>Pair<Employee>的子类吗?

不是

下面的代码是错误的

Pair<Employee> result = Pair<Manager> a;

通配符

例如

Pair<? extend Employee>

表示任何泛型Pair类型,它的类型参数都是Employee的子类。

使用方式

public static void printBuddies(Pair<? extend Employee> p)
public <U> Class<? extends U> asSubclass(Class<U> clazz) {}

通配符超类限定

限定类型变量为指定类的超类

Pair <? super Employee>

无限定通配符

例如

Pair<?>

通配符捕获

例如有一个交换方法

public static void swap(Pair<?> p)

我们无法通过?知道p的类型参数的具体类型,所幸,可以写一个辅助方法

public static <T> void swapHepler(Pair<T> p)

让后在swap内调用

public static void swap(Pair<?> p)
{
	swapHepler(p)
}

反射与泛型

Class是一个泛型类,String.class实际上是一个Class<String>的对象(事实上,是单例对象)

Class<T>使用了类型参数

T newInstance()
T cast(Object obj)
T[] getEnumConstants()
Class<? super T> getSuperclass()
Constructor<T> getConstructor
Constructor<T> getDeclaredConstructor

Constructor<T>也使用了类型参数

T newInstance(Object ... initargs)

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

例如通过Class <T>生成Pair实例

public static <T> Pair<T> makePair(Class <T> c)
{
    return new Pair<T>(c.newInstance,c.newInstance);
}

获取泛型信息的API

例如泛型方法

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

通过反射API可以确定

  1. 有一个名为T的类型参数
  2. 有一个子类型限定,其自身又是一个泛型类型
  3. 这个限定类型有一个通配符参数
  4. 通配符参数有一个超类型限定
  5. 这个泛型方法有一个泛型数组参数

为了表示泛型类型声明,Java提供了一个新接口Type

  • Class类 具体类型
  • TypeVariable接口 类型信息 (T extends Comparable<? super T>)
  • WildcardType接口 通配符(? super T)
  • ParameterizedType 接口 描述泛型类或接口类型 (Comparable <? super T>)
  • GenericArrayType 接口 描述泛型数组 (T[])
Last Updated:
Contributors: himcs, himcs