官方文档:
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

  1. List list = new ArrayList();
  2. list.add("hello");
  3. String s = (String) list.get(0); // require cast
  1. List<String> list = new ArrayList<String>();
  2. list.add("hello");
  3. String s = list.get(0); // no cast

会出错的代码:
image.png
3、使程序员能够实现通用算法。
通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,这些算法可以自定义,并且类型安全且易于阅读。

类型擦除

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
1、编译时用泛型的边界(比如:List<? extends T>表示上界为T和List <? super T>表示下界为T)或Object(无边界的情况)替换所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
2、必要时插入类型强制转换以保持类型安全。
3、类型擦除确保没有为参数化类型创建新类;因此,泛型不会产生运行时开销。
4、生成桥接方法以在扩展泛型类型中保留多态性。

桥接方法的解释:https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
举例:

  1. public class Solution {
  2. public static void main(String[] args) {
  3. MyNode mn = new MyNode(5);
  4. Node n = mn; // A raw type - compiler throws an unchecked warning
  5. n.setData("Hello"); // Causes a ClassCastException to be thrown.
  6. Integer x = mn.data;
  7. }
  8. public static class Node<T> {
  9. public T data;
  10. public Node(T data) {
  11. this.data = data;
  12. }
  13. public void setData(T data) {
  14. System.out.println("Node.setData");
  15. this.data = data;
  16. }
  17. }
  18. public static class MyNode extends Node<Integer> {
  19. public MyNode(Integer data) {
  20. super(data);
  21. }
  22. public void setData(Integer data) {
  23. System.out.println("MyNode.setData");
  24. super.setData(data);
  25. }
  26. }
  27. }

运行之后报错:
报错在:n.setData("Hello");这句话
image.png
Node和MyNode编译后,类型擦除,代码如下:

  1. public class Node {
  2. public Object data;
  3. public Node(Object data) { this.data = data; }
  4. public void setData(Object data) {
  5. System.out.println("Node.setData");
  6. this.data = data;
  7. }
  8. }
  9. public class MyNode extends Node {
  10. public MyNode(Integer data) { super(data); }
  11. // Bridge method generated by the compiler
  12. public void setData(Object data) {
  13. setData((Integer) data);
  14. }
  15. public void setData(Integer data) {
  16. System.out.println("MyNode.setData");
  17. super.setData(data);
  18. }
  19. }

而main方法中的代码编译后如下:

  1. public static void main(String[] args) {
  2. MyNode mn = new MyNode(5);
  3. Node n = (MyNode)mn; // A raw type - compiler throws an unchecked warning
  4. n.setData("Hello"); // Causes a ClassCastException to be thrown.
  5. Integer x = (String)mn.data;
  6. }

可以看到MyNode编译后,其实public void setData(Integer data)方法并没有重写父类的public void setData(Object data)方法,参数不一样。
为了保留多态性,生成一个桥接方法,强制完成类型转换后调用子类的方法。但是强制类型转换可能会报错,所以出现上面代码的问题。