编写泛型
类的泛型
//定义
class Box<T> {
private T element;
public Box(T element) {
this.element = element;
}
public T get() {
return this.element;
}
}
//使用
Box<String> b1 = new Box<>("");
String s = b1.get();
方法的泛型
// 类的泛型
class Box<T> {
private T element;
public Box(T element) {
this.element = element;
}
public T get() {
return this.element;
}
// 成员方法的泛型
public <K, V> V m1() {
return null;
}
// 静态方法的泛型
public static <K> Box<K> valueOf(K element) {
return new Box<>(element);
}
}
Box<String> b1 = Box.valueOf(""); //不用指定泛型,能推导出
b1.<Integer, String>m1().toLowerCase(); //需要指定泛型,不能推导出
多个泛型类型
//定义
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}
//使用
Pair<String, Integer> p = new Pair<>("test", 123);
擦拭法(泛型原理)
原理
Java实现泛型的方式是擦拭法:虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
Java使用擦拭法实现泛型,导致了:
- 编译器把类型
<T>
视为Object
; - 编译器根据
<T>
实现安全的强制转型。
编译器看到的代码:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
编译器转换后,虚拟机看到的代码:
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
泛型的局限
局限一:<T>
不能是基本类型,例如int
,因为实际类型是Object
,Object
类型无法持有基本类型:
Pair<int> p = new Pair<>(1, 2); // compile error!
局限二:无法取得带泛型的Class
:
Pair<String> p1 = new Pair<>("Hello", "world");
Pair<Integer> p2 = new Pair<>(123, 456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2); // true,c1和c2都是Pair.class
System.out.println(c1==Pair.class); // true
局限三:无法判断带泛型的类型:
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
// 没有 Pair<String>.class,只有 Pair.class
if (p instanceof Pair<String>) {
}
局限四:不能实例化T
类型:
public class Pair<T> {
private T first;
private T last;
public Pair() {
// Compile error:
first = new T();
last = new T();
}
}
上述代码无法通过编译,因为构造方法的两行语句:
first = new T();
last = new T();
擦拭后实际上变成了:
first = new Object();
last = new Object();
这样一来,创建new Pair()
和创建new Pair()
就全部成了Object
,显然编译器要阻止这种类型不对的代码。
要实例化T
类型,我们必须借助额外的Class
参数:
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}
}
不恰当的覆写方法
有些时候,一个看似正确定义的方法会无法通过编译。例如:
public class Pair<T> {
//'equals(T)' in 'Pair' clashes with 'equals(Object)' in 'java.lang.Object'; both methods have same erasure, yet neither overrides the other
public boolean equals(T t) {
return this == t;
}
}
这是因为,定义的equals(T t)
方法实际上会被擦拭成equals(Object t)
,而这个方法是继承自Object
的,编译器会阻止一个实际上会变成覆写的泛型方法定义。
泛型继承
一个类可以继承自一个泛型类。
//父类,是泛型类
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
//子类,不是泛型类
class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}
使用的时候,因为子类IntPair
并没有泛型类型,所以,正常使用即可:
IntPair ip = new IntPair(1, 2);
我们无法获取Pair
的T
类型,即给定一个变量Pair p
,无法从p
中获取到Integer
类型。
在继承了泛型类型的情况下,子类可以获取父类的泛型类型。例如:IntPair
可以获取到父类的泛型类型Integer
。
Class<IntPair> clazz = IntPair.class;//获取子类Class
Type t = clazz.getGenericSuperclass();//获取父类Type:Pair<java.lang.Integer>
if (t instanceof ParameterizedType) {//判断t是参数化类型
ParameterizedType pt = (ParameterizedType) t; //强制转换
Type rawType = pt.getRawType();//泛型擦除后的Type:class Pair
Type ownerType = pt.getOwnerType();//如果我是内部类,则返回外部类Type:null
Type[] types = pt.getActualTypeArguments();//泛型类包含的实际类[class java.lang.Integer]
}
extends通配符
介绍
下面会报错:
List<Integer> integerList = new ArrayList<>();
//不兼容的类型: java.util.List<java.lang.Integer>无法转换为java.util.List<java.lang.Number>
List<Number> numberList = integerList;
这样就不会报错:
List<Integer> integerList = new ArrayList<>();
//泛型类型 是Number或其子类
List<? extends Number> list = integerList;
这种使用<? extends Number>
的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T
的上界限定在Number
了。
注意
List<? extends Integer> list = new ArrayList<>();
list.add(1);//error,因为内部类型不一定是Integer
list.add(null);//ok,只能传入null
因此,类型List
表明了该变量只会读取List
的元素,不会修改List
的元素(因为无法调用add(? extends Integer)
、remove(? extends Integer)
这些方法。换句话说,这里只会对List
进行只读(恶意调用set(null)
除外)。
super通配符
介绍
下面会报错:
List<Number> numberList = new ArrayList<>();
//不兼容的类型: java.util.List<java.lang.Number>无法转换为java.util.List<java.lang.Integer>
List<Integer> integerList = numberList;
这样就不会报错:
List<Number> numberList = new ArrayList<>();
//泛型类型 是Integer或其父类
List<? super Integer> list = numberList;
这种使用<? super Integer>
的泛型定义称之为下界通配符(Lower Bounds Wildcards),即把泛型类型T
的下界限定在Integer
了。
注意
List<? super Number> list = new ArrayList<>();
list.add(1);//ok,因为内部类型一定是Number父类,可以传入Integer
Number n = list.get(0);//error,内部类型不一定是Number
Object o = list.get(0);//ok,只能用Object接收
换句话说,使用<? super Integer>
通配符作为类型,表示变量只能写,不能读。
对比extends和super通配符
作为方法参数,List<? extends T>
类型和List<? super T>
类型的区别在于:
List<? extends T>
允许调用读方法T get()
获取T
的引用,但不允许调用写方法set(T)
传入T
的引用(传入null
除外);List<? super T>
允许调用写方法set(T)
传入T
的引用,但不允许调用读方法T get()
获取T
的引用(获取Object
除外)。
使用例子:Collections
类定义的copy()
方法:
public class Collections {
// 把src的每个元素复制到dest中:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i);
dest.add(t);
}
}
}
无限定通配符
(1)无限定通配符(Unbounded Wildcard Type),即只定义一个?
:
void sample(List<?> p) {}
(2)因为<?>
通配符既没有extends
,也没有super
,因此:
- 不允许调用
set(T)
方法并传入引用(null
除外); - 不允许调用
T get()
方法并获取T
引用(只能获取Object
引用)。
(3)<?>
通配符有一个独特的特点,就是:Pair<?>
是所有Pair<T>
的超类:
List<Integer> integerList = new ArrayList<>();
List<?> list = integerList;// 安全地向上转型