什么是泛型?
泛型是一种“代码模板”,可以用一套代码套用各自类型。一般约定泛型使用 T
声明,T
可以是任何 class
向上转型
Java
标准库中 ArraryList<T>
实现了 List<T>
接口,它可以向上转型为 List<T>
,即类型 ArrayList<T>
可以向上转型为 List<T>
,但是 T
不能变。
注意,
ArraryList<Integer>
不能向上转型为ArraryList<Number>
,它们两者没有任何继承关系
public class ArrayList<T> implements List<T> {
...
}
List<String> list = new ArrayList<String>();
类型推断
编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型
List<String> list = new ArrayList<>();
使用
var
声明变量时,编译器会将泛型类型自动推断为Object
类型
泛型类
定义泛型类
Java
标准库中集合框架中的接口和类均是泛型接口和类,使用这些接口和类是,把泛型参数替换为需要的 class
类型,例如 ArrayList<String>
// E 为泛型形参
public interface List<E> extends Collection<E> {
}
实现泛型接口的泛型类必须实现正确的泛型类型。如 ArrayList<E>
泛型类实现 List<E>
泛型接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
// 使用时指定具体类型
List<String> strList = new ArrayList<>();
泛型新参可以定义多种类型,需要定义多个类型时,只要区分不同的新参名即可
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
}
// 使用时分别指定具体类型
HashMap<String,String> map = new HashMap<>();
并不存在真正的泛型类
声明 ArrayList<String>
类时,系统并没有为 ArrayList<String>
生成新的 class
文件,并且也不会将它当成新的类来处理,ArrayList<String>
类的 Class
仍是 ArrayList.Class
,ArrayList<Number>
等其他类型同理都是 ArrayList.Class
List<String> strings = new ArrayList<>(16);
List<Integer> integers = new ArrayList<>(16);
final var stringsClass = strings.getClass();
log.debug(stringsClass.toString()); // class java.util.ArrayList
final var integersClass = integers.getClass();
log.debug(integersClass.toString()); // class java.util.ArrayList
log.debug(String.valueOf(stringsClass == integersClass)); // true
类型通配符(泛型约束)
Java
使用问号 ?
作为无限定通配符,它对应的元素类型可以匹配任意类型。如 List<?>
可以匹配任意 class
类型的 List
extend 关键字设定通配符的上限
使用 extend
关键字限定泛型类中 T
类型必须是指定类的子类
// 限定 T 类型必须是 Number 类型的子类
public class Pair<T extends Number> {
}
// T 类型本身就是 Number 类型
Pair<Number> numberPair = new Pair<>(1, 22.2);
// Integer 和 Double 都是 Number 类型的子类
Pair<Integer> integerPair = new Pair<>(22, 21);
Pair<Double> doublePair = new Pair<>(22.1, 212.2);
// T 类型非 Number 类型时编译失败
Pair<String> stringPair = new Pair<String>("", "");
指定通配符的上限就是让类支持类型型变。比如
Integer
是Number
的子类,那么Pair<Integer>
就相当于Pair<? extend Number>
的子类,可以将Pair<Integer>
赋值给Pair<? extend Number>
类型的变量,这种型变方式称为协变。 对于协变的泛型类来说,它只能调用泛型类型作为返回值类型的方法;而不能调用泛型类型作为参数的方法,就是说:协变只出不进
泛型方法
定义泛型方法
static int add(Pair<?> pair){
Number first = pair.getFirst();
Number last = pair.getLast();
return first.intValue() + last.intValue();
}
类型通配符(泛型约束)
extend 关键字设定通配符的上限
使用 extend
关键字限定泛型方法参数时,方法内部只能调用获取指定类的引用的方法,不能调用传入指定类的引用的方法。即只能读,不能写
@Data
@AllArgsConstructor
public class Pair<T> {
private T first;
private T last;
}
static int add(Pair<? extends Number> pair){
Number first = pair.getFirst();
Number last = pair.getLast();
// 编译失败
pair.setFirst(1);
return first.intValue() + last.intValue();
}
super 关键字设置通配符的下限
和 extend 通配符相反,方法内部只能调用传入指定类的引用的方法。即只能写,不能读
@Data
@AllArgsConstructor
public class Pair<T> {
private T first;
private T last;
}
static int add(Pair<? super Integer> pair){
// 编译失败
// 因为如果传入的是 Number 类型,编译器无法将 Number 类型转为 Integer 类型
Integer first = pair.getFirst();
// 因为如果传入的是 Number 类型是抽象类,无法直接实例化它
Number last = pair.getLast();
// 只能使用 Object 类型接收返回值
Object first = pair.getFirst();
return first.intValue() + last.intValue();
}
擦拭法
Java
语言的泛型实现方法是擦拭法:即虚拟机 JVM
本身对泛型一无所知,所有的工作都是编译器做的。编译器内部永远把类型 T
视为 Object
处理,但是在需要类型转换的时候,编译器会根据 T 的类型自动实现安全的类型强制转换
定义泛型类 Pair<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;
}
}
虚拟机 JVM
实际执行的代码:
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<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
虚拟机 JVM
实际执行的代码并没有泛型:
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
Java 泛型局限
- 局限一:泛型
T
不能是基本类型,例如int
。因为实际类型都是Object
,Object
类型无法持有基本类型 - 局限二:无法取得带泛型的
Class
。所有泛型实例,无论T
的类型是什么,getClass()
返回的都是同一个Class
实例,因为它们在编译后都是Pair<Object>
- 局限三:无法判断带泛型的类型
- 局限四:不能实例化
T
类型