泛型即广泛的类型,是类型参数化,处理的数据类型是不固定的,作为参数传入。
基本原理:Java编译器将泛型代码转换为普通的非泛型代码——将类型参数T、K擦除,替换为Object;并且插入必要的强制类型转换。Java虚拟机实际执行时,对泛型是不感知的。
泛型主要体现在开发环境和编译阶段,能使代码具有更好的安全性和可读性。(泛型替换Object和强制类型转换)
public class Node<T, K> { //泛型放在类名后
private T element;
private K value;
public Node(T element, K value) {
this.element = element;
this.value = value;
}
public T getElement() {
return element;
}
public K getValue() {
return value;
}
public static <U> int indexOf(U[] arr, U element) { //泛型放在返回值前
for (int i = 0; i < arr.length; i++) {
if (element.equals(arr[i])) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
Node<String, Integer> node = new Node<>("SZG", 99);
String str = node.getElement();
int value = node.getValue();
}
}
见上一代码块,泛型可以用来定义泛型类、泛型方法。同理,还可以用来定义泛型接口,如Comparable接口,及其在Integer中的实现
public interface Comparable<T> {
public int compareTo(T o);
}
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
类型参数限定
上界为某个具体类:<T extends Number>
,泛型T表示了Number及其子类;在进行类型擦除时,不会转换为Object而是转为Number;
上界为某个接口:<T extends Comparable>
,如以下代码块,可以使用compareTo方法。
public static <V extends Comparable<V>> V max(V[] arr) {
V max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i].compareTo(max) > 0) {
max = arr[i];
}
}
return max;
}
以某一泛型为上界,如<T extends E>
。
泛型将数据结构与算法同数据类型分离,使得同一套数据结构和算法能应用于各种数据类型,而且可以保证类型安全,提高可读性。
通配符参数类型限定
<T extends E>
——定义类型参数,声明了一个类型参数T;
<? extends E>
——实例化类型参数,实例化了泛型E,但是这个具体的类型是未知的,只知道它是E或E的子类型。
通配符的限制:可读不可写。即可以将元素以E的类型读出;但是不可以以E的子类型方式写入。原因是?并没有约定是E的哪种子类型,不能将E的子类型1写入E的子类型2; 而T extends E限定了元素必须为T,因此读写无阻。
总结:
① 通配符形式可以用类型参数替代;
② 通配符形式可以减少类型参数声明,形式上更简单,可读性也更好;在只读场景下建议使用通配符;
③ 如果参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,只能用类型参数;
④ 通配符和类型参数往往配合使用:定义必要的类型参数、使用通配符表达依赖;
<? super E>
——用于灵活写入。
局限性
① 不能以基本类型实例化类型参数,如果需要必须使用对应的包装类。
② 运行时类型信息不适用于泛型:如Node<String, Integer>.class是编译错误的,类型与泛型无关。
Node<String, Integer> node = new Node<>("SZG", 99);
Node<Integer, Integer> nodeInt = new Node<>(1,1);
System.out.println(Node.class == node.getClass()); // true,类型就是Node
System.out.println(Node.class == nodeInt.getClass()); // true
System.out.println(node instanceof Node); // true,可以是Node<?, ?> 但不允许写为Node<String, Integer>
③ 由于类型擦除的原因,要考虑到Java虚拟机识别到的代码,避免对接口实现两次,或者方法重复;
④ 不能以类型参数实例化对象:仍然是类型擦除的原因,虚拟机真正识别到的其实是父类或者Object,而不是直观理解的某一特定的类型T。但是可以使用反射(newInstance方法)。
⑤ 泛型类中声明的类型参数,不允许在静态方法或代码块中使用——等同于实例变量
⑥ 不支持创建泛型数组;如果需要存放泛型对象,可以使用原始类型数组或泛型容器。泛型容器因为类型擦除的原因,内部其实使用的是Object数组,要转换泛型容器为对应类型的数组,需要使用反射。