泛型机制详解
什么是泛型?
泛型( generics)是JDK5引入的一个新特性,定义接口、类或方法的时候,可以不预先指定具体的类型,而是使用时指定具体类型,实现参数化类型,实现多种数据类型复用相同的代码。
泛型的优点
- 编译时更强的类型检查
Java编译器对泛型参数会执行检查,泛型参数指定传入的对象类型,只能传入对应对象类型或其衍生类型,泛型参数指定类型后不可变。
public class GenericExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList; // 编译器报错
}
}
- 消除强制类型转换
//没有泛型强制转换的代码
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
//重写为使用泛型,代码不需要强制转换
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);
泛型的使用
泛型有三种使用方式:泛型类、泛型接口、泛型方法。类或接口声明了泛型类型,即引入了类型变量,该类型变量在类或接口中任何位置使用。方法声明了泛型类型,即类型变量作用域仅限于声明它的方法,运行泛型使用在静态和非静态方法,以及泛型类构造函数,类型变量命名约定(无实际意义)包括以下几类:
E- Element(被Java集合框架广泛使用)K- Key 键N- Number 数字T- Type 类型V- Value 值S,U,V等 - 第2、3、4类
泛型类使用
简单泛型类
public class Box<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
多元泛型类
public class OrderedPair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
实例化泛型类
Box<String> box = new Box<String>();
OrderedPair<String,String> op = new OrderedPair<String,String>("key","value");
泛型接口使用
简单泛型接口:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
不指定类型实现泛型接口:
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
指定类型实现泛型接口:
public class OrderedPairImpl<K, V> implements Pair<String, String> {
private String key;
private String value;
public OrderedPair(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() { return key; }
public String getValue() { return value; }
}
泛型方法使用
普通泛型方法:
public class GenericsExample {
public static <T> void example(T obj) {
System.out.println(obj.getClass().toString());
}
public static void main(String[] args) {
example(123);
example("xyz");
}
}
可变参数列表泛型方法:
public class GenericsExample {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
Collections.addAll(result, args);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("123");
System.out.println(ls); // [123]
ls = makeList("abc", "xyz");
System.out.println(ls); // [abc, xyz]
}
}
类型擦除
泛型( generics)是JDK5引入的一个新特性,为兼容之前版本,泛型实现用的是伪泛型,即语法支持泛型,执行编译时会进行类型擦除,会将所有的类型变量T表示替换为具体类型。类型擦除可以保证不会为参数化类型创建新类,可以减少虚拟机运行时的开销。类型擦除原则是:
- 泛型中所有类型变量替换为
Object,如果指定了类型边界,则用其类型边界替换,生成的字节码仅包含普通的类、接口和方法。 - 擦除类型参数声明,删除
<>的内容,比如T get()会变成Object get(),List<String>会变成List,如果有必要,编译时也会插入类型转换以保证类型安全。 - 生成桥方法以保留扩展泛型类型的多态性。
什么是桥方法
桥方法用于继承泛型类时保留拓展多态性,为编译器自动创建的一个方法。
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); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
执行类型擦除后,方法签名不匹配;Node.setData(T) 方法变为 Node.setData(Object)。作为一个结果,MyNode.setData(Integer) 方法不会覆盖 Node.setData(Object) 方法。为了解决这个问题,并在类型擦除后保留泛型类型的多态性,Java编译器生成了一个桥接 确保子类型按预期工作的方法。
class MyNode extends Node {
// 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);
}
// ...
}
泛型缺陷
泛型参数不能使用基本类型
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
}
Pair<int, char> p = new Pair<>(8, 'a'); //编译错误
Pair<Integer, Character> p = new Pair<>(8, 'a'); //正确用法
无法创建泛型的实例
public static <E> void append(List<E> list) {
E elem = new E(); // 编译错误
list.add(elem);
}
//通过反射创建泛型的对象
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance();
list.add(elem);
}
List<String> ls = new ArrayList<>();
append(ls, String.class);
不允许使用泛型的静态字段
类的静态变量是由类的所有非静态对象共享的类级变量。因此,不允许使用参数类型的静态字段。
public class MobileDevice<T> {
private static T os;
}
不能使用 Instance of 和 getClass() 进行类型判断
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // 编译错误
}
}
不能重载泛型类型擦除后为相同原始类型的方法
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
//编译错误
}
什么是通配符
使用泛型时,问号?称为通配符,表示未知类型。
上边界通配符、下边界通配符
使用泛型时,可以对传入类型的实参进行上下边界限制,规定传入的具体类型是某种类型的子类(上边界)或某种类型的父类(下边界)。
上边界通配符
上限通配符使用通配符?,后跟extends关键字,最后跟其上限类,表示泛型传入必须是指定上限类的子类。
public static void process(List<? extends Fruit & Meat> list) {
}
下边界通配符
下边界通配符使用通配符?,后跟super关键字,最后跟其下限类,表示泛型传入必须是指定下限类的父类。
public static void addNumbers(List<? super Integer> list) {
}
无界通配符
无界通配符类型使用通配符?指定,比如List<?>,称为未知类型的列表,无界通配符可以接受任何类型的数据,主要应用在不依赖具体类型参数的场景。
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
注意:
List<Object>和 List<?>并不相同,List<Object>可以添加任何Object及其子类,但List<?>只能添加null值,添加其他元素会报错。