泛型
用途
例如一个只能包含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
类型,编译器无法推断。
可以使用double
和long
的共同父类接受
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
的泛型类型时equals
与Object.equals
方法冲突,当然我们只能重命名解决冲突。
类或类型变量不能同时成为两个接口类型的之类
例如下述代码时非法的
class Calendar implements Comparable<Calendar> {
}
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> {} //ERROR
GregorianCalendar
同时实现了 Comparable<Calendar>
和 Comparable<GregorianCalendar>
,这是同一个接口的不同参数化,这中使用方法是非法的。
泛型继承规则
类及其之类Employee
和Manager
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可以确定
- 有一个名为T的类型参数
- 有一个子类型限定,其自身又是一个泛型类型
- 这个限定类型有一个通配符参数
- 通配符参数有一个超类型限定
- 这个泛型方法有一个泛型数组参数
为了表示泛型类型声明,Java提供了一个新接口Type
。
- Class类 具体类型
- TypeVariable接口 类型信息 (
T extends Comparable<? super T>
) - WildcardType接口 通配符(? super T)
- ParameterizedType 接口 描述泛型类或接口类型 (
Comparable <? super T>
) - GenericArrayType 接口 描述泛型数组 (
T[]
)