文章目录
一、为什么使用泛型(generics)
1、官方说明
官方地址:https://docs.oracle.com/javase/1.5.0/docs/guide/language/generics.html
2、通俗说明
在 JDK5.0 以前,如果一个方法返回值是 Object,一个集合里装的是 Object,那么获取返回值或元素只能强转,如果有类型转换错误,在编译器无法觉察,这就大大加大程序的错误几率,必须通过不断的测试来保障质量,如果在编译期就可以解决岂不美哉?
二、泛型的定义
泛型是一种类型约束,于 J2SE5.0 中引入,简而言之,泛型在定义类,接口和方法时使类型(类和接口)成为参数。与方法声明中使用的更熟悉的形式参数非常相似,类型参数为您提供了一种使用不同输入重复使用相同代码的方法。区别在于形式参数的输入是值,而类型参数的输入是类型。
JDK 是在编译期对类型进行检查,提供了编译时类型的安全性。它为集合框架增加了编译时类型的安全性,并消除了繁重的类型转换工作。
三、泛型的使用规则
- 不允许子类型化,也就是不允许将指定泛型类型的子类赋值给当前类型
- 泛型允许使用通配符
- 定义通用方法和类型推断
1、泛型中的通配符
- 无界通配符 :
<?>
- 上界通配符:
<? extends xxx>
- 下界通配符:
<? super xxx>
2、通用方法
通用方法是指方法参数的类型是泛型,static 和非 static 的方法都可以使用,还有就是构造方法也可以使用。
public class Util {
// 定义通用方法,指定参数类型为泛型
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
// 定义通用类,指定泛型
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
3、类型推断
类型推断是 Java 编译器查看每个方法调用和相应声明以确定使调用适用的类型参数的能力。推理算法确定参数的类型,以及确定结果是否已分配或返回的类型(如果有)。最后,推理算法尝试找到与所有参数一起使用的最具体的类型。
四、泛型类型擦除
在编译期JDK会对泛型进行类型擦除,为什么要做类型擦除呢?
- 兼容JDK1.5以前的代码
- 类型擦除可以确保不为参数化类型创建新的类
- 类型擦除后,在运行时不会产生额外的开销
在类型擦除过程中,Java 编译器将擦除所有类型参数,如果类型参数是有界的,则将每个参数替换为其第一个边界;如果类型参数是无界的,则将其替换为 Object。
1、通用类型的擦除
// 编译前
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
// ...
}
//编译后,编译器将通用泛型替换为Object
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
// ...
}
// 编译前
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
// ...
}
//编译后,编译器会将第一个边界类型 Comparable 替换 T
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() {
return data;
}
// ...
}
2、类型擦除与桥接方法
类型檫除在有一些情况下会产生意想不到的问题,为了解决这个问题,java 编译器采用桥接方
法的方式。先看个官方案例
// 编译前
public 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 class MyNode extends Node<Integer> {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
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");
Integer x = mn.data;
//java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
System.out.println(x);
}
//编译后类型檫除
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);
}
}
五、泛型的使用限制
- 不能用简单类型来实例化泛型实例
- 不能直接创建类型参数实例
- 不能声明静态属性为泛型的类型参数
- 不能对参数化类型使用cast或instanceof
- 不能创建数组泛型
- 不能create、catch、throw参数化类型对象
- 重载的方法里不能有两个相同的原始类型的方法