泛型类(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 T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
常见的做法是类型变量使用大写字母,而且很简短。Java库使用变量E表示集合的元素类型,K和V分别表示表的键和值的类型,T(必要时还可以使用U和S)表示任意类型。
可以用具体的类型转换变量来实例化(instantiate)泛型类型。如:Pair
换句话说,泛型类相当于普通类的工厂。
还可以定义一个带有类型参数的方法:
class ArrayAlg
{
public static <T> T getMiddle(T... a)
{
return a[a.length/2};
}
}
泛型方法可以在普通类中定义,也可以在泛型类中定义。
当调用一个泛型方法时,可以把具体类型包围在尖括号中,放在方法名前面:
String middle = ArrayAlg.
这种情况下,方法调用可以省略
类型参数。即
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");
有时,类或方法需要对类变量加以约束。如,计算数组中的最小元素:
class ArrayAlg
{
public static <T> 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;
}
}
此方法只能限制T只能是实现了Comparable的接口(包含一个方法compareTo的准接口)的类。可以通过对类型变量T设置一个限定(bound)来实现这一点:
public static < T extends Comparable> T min(T[] a)...
实际上Comparable接口本身就是一个泛型类型。
一个类型变量或通配符可以有多个限定,如:
T extends Comparable & Serializable
限定类型(bounding type)的子类型T(subtype)和限定类型和可以是类,也可以是接口。在Java继承中,可以根据需要拥有多个接口超类型,但最多有一个限定可以是类。
虚拟机没有泛型类型对象–所有对象都属于普通类。
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除(erased),并替换为其限定类型(无限定的变量替换为Object)。
编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。
当访问一个泛型字段时也要插入强制类型转换。
类型擦除也会出现在泛型方法中。通常认为类似
public static < T extends Comparable> T min(T[] a)
的泛型方法是整个一组方法,而擦除类型之后,只剩下一个方法:
public static Comparable min(Comparable[] a)
方法的擦除带来两个问题。如:
class DateInterval extends Pair<LocalDate>
{
public void setSecond(LocalDate second)
{
...
}
}
日期区间是一对LocalDate对象,这个类擦除后变成:
class DateInterval extends Pair
{
public void setSecond(LocalDate second)
{
...
}
}
还有另一个从Pair继承的setSecond方法,即:
public void setSecond(Object second)
我们希望setSecond调用具有多态性,类型擦除与多态发生了冲突。为解决这一问题,编译器在DateInterval类生成了一个桥方法(bridge method):
public void setSecond (Object second) { setSecond((LocalDate) second); }
对于Java泛型的转换,有以下几个事实:
使用Java泛型时有以下限制
通常,Pair
和Pair
没有任何关系。
泛型类可以扩展或实现其他的泛型类。
在通配符类型中,允许类型参数发生变化。例如,通配符类型
Pair extends Employee>
表示任何泛型Pair类型,它的类型参数是Employee的子类。
也可以通过通配符类型将Pair
传递给方法:
public static void printBuddies(Pair extends Empolyee> p)
通配符限定与类型变量限定十分类似,但通配符可以指定一个超类型限定(supertype bound)。如? super Manager
,这个通配符限制为Manager的所有超类型。
直观的将,带有超类型限定的通配符允许你写入一个泛型对象,而带有子类型限定的通配符允许你读取一个泛型对象。
还可以使用无限定的通配符,如Pair>
。与Pair的本质不同在于:可以用任意Object对象调用原始Pair类的方法。
通配符捕获只有在非常限定的情况下才是合法的。编译器必须能够保证通配符表示单个确定的类型。
Java泛型的突出特性之一是在虚拟机中擦除泛型类型。擦除的类仍然保留原先泛型的微弱记忆。如以下方法:
public static Comparable min(Comparable[] a)
擦除泛型方法后:
public static
可以使用反射API来确定:
狂神说Java
Java核心技术 卷I(第11版)
上一章:Java从零开始系列05:异常、断言和日志
下一章:Java从零开始系列07:集合