官方文档:
https://docs.oracle.com/javase/tutorial/java/generics/index.html
博客:
https://www.cnblogs.com/Blue-Keroro/p/8875898.html
https://cloud.tencent.com/developer/article/1033693
什么是泛型?
generic type
泛型使类型在定义类、接口和方法时成为参数。与方法声明中使用的更为常见的形式化参数非常相似。区别在于,形式参数的输入是值,而类型参数的输入是类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型有什么作用?
1、Java编译器对泛型代码使用更强的类型检查
如果代码违反类型安全,则会出现编译错误。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException
。修复编译时错误比修复运行时错误更容易,因为运行时错误很难找到。
2、不需要进行繁琐的类型转换
在没有泛型的情况下:在集合中存储对象以及对集合中的对象进行处理时,需要进行类型转换。
如下面例子所示,如果在运行时,类型转换出错,就会抛出ClassCastException
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // require cast
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
会出错的代码:
3、使程序员能够实现通用算法。
通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,这些算法可以自定义,并且类型安全且易于阅读。
类型擦除
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
1、编译时用泛型的边界(比如:List<? extends T>
表示上界为T和List <? super T>
表示下界为T)或Object(无边界的情况)替换所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
2、必要时插入类型强制转换以保持类型安全。
3、类型擦除确保没有为参数化类型创建新类;因此,泛型不会产生运行时开销。
4、生成桥接方法以在扩展泛型类型中保留多态性。
桥接方法的解释:https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
举例:
public class Solution {
public static void main(String[] args) {
MyNode mn = new MyNode(5);
Node n = mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello"); // Causes a ClassCastException to be thrown.
Integer x = mn.data;
}
public static class Node<T> {
public T data;
public Node(T data) {
this.data = data;
}
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public static class MyNode extends Node<Integer> {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
}
运行之后报错:
报错在:n.setData("Hello");
这句话
Node和MyNode编译后,类型擦除,代码如下:
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
// Bridge method generated by the compiler
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
而main方法中的代码编译后如下:
public static void main(String[] args) {
MyNode mn = new MyNode(5);
Node n = (MyNode)mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello"); // Causes a ClassCastException to be thrown.
Integer x = (String)mn.data;
}
可以看到MyNode编译后,其实public void setData(Integer data)
方法并没有重写父类的public void setData(Object data)
方法,参数不一样。
为了保留多态性,生成一个桥接方法,强制完成类型转换后调用子类的方法。但是强制类型转换可能会报错,所以出现上面代码的问题。