共计 4547 个字符,预计需要花费 12 分钟才能阅读完成。
导读 | Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 |
Java 泛型 (generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
简单理解就是:泛型指定编译时的类型,减少运行时由于对象类型不匹配引发的异常。其主要用途是提高我们的代码的复用率。
我们 Java 标准库中的 ArrayList 就是泛型使用的典型应用:
public class ArrayList extends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable {
......
public ArrayList(Collection extends E> c) {elementData = c.toArray();
if ((size = elementData.length) != 0) {// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
public void sort(Comparator super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {throw new ConcurrentModificationException();
}
modCount++;
}
.....
public E get(int index) {rangeCheck(index);
return elementData(index);
}
public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
}
- 源码中,ArrayList 中的 E 称为类型参数变量,而整个 ArrayList 我们称为泛型类型。我们可以指定除基本类型之外的任何类型,如:ArrayList。
- 源码中 Collection 中? 通配符类型 表示类型的上界,表示参数化类型的可能是 T 或是 T 的子类。
- 源码中 Comparator 表示类型下界(Java Core 中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至 Object。
在定义泛型类型 Generic 的时候,也可以使用 extends 通配符来限定 T 的类型:
public class Generic {...}
现在,我们只能定义:
Generic p1 = null;
Generic p2 = new Generic(1, 2);
Generic p3 = null;
因为 Number、Integer 和 Double 都符合。
非 Number 类型将无法通过编译:
Generic p1 = null; // compile error!
Generic
因为 String、Object 都不符合,因为它们不是 Number 类型或 Number 的子类。
我们看一个例子:
public class Test {static class Food {}
static class Fruit extends Food { }
static class Apple extends Fruit { }
static class Orange extends Fruit { }
public void testExtend() {List extends Fruit> list = new ArrayList();
// 无法安全添加任何具有实际意义的元素, 报错,extends 为上界通配符, 只能取值, 不能放.
// 因为 Fruit 的子类不只有 Apple 还有 Orange, 这里不能确定具体的泛型到底是 Apple 还是 Orange,所以放入任何一种类型都会报错
//list.add(new Apple());
//list.add(new Orange());
// 可以添加 null,因为 null 可以表示任何类型
list.add(null);
// 可以正常获取,用 java 多态
Food foot = list.get(0);
Apple apple = (Apple) list.get(0);
}
public void testSuper() {List super Fruit> list = new ArrayList();
//super 为下界通配符,可以存放元素,但是也只能存放当前类或者子类的实例,以当前的例子来讲,list.add(new Fruit());
list.add(new Apple());
// 无法确定 Fruit 的父类是否只有 Food 一个(Object 是超级父类)
// 因此放入 Food 的实例编译不通过,只能放自己的实例 或者根据 java 多态的特性放子类实例
//list.add(new Food());
//List super Fruit> list2 = new ArrayList();
//Fruit fruit = list.get(0); // 不能确定返回类型
}
}
在 testExtend 方法中,因为泛型中用的是 extends,在向 list 中存放元素的时候,我们并不能确定 List 中的元素的具体类型,即可能是 Apple 也可能是 Orange。因此调用 add 方法时,不论传入 new Apple()还是 new Orange(),都会出现编译错误。
理解了 extends 之后,再看 super 就很容易理解了,即我们不能确定 testSuper 方法的参数中的泛型是 Fruit 的哪个父类,因此在调用 get 方法时只能返回 Object 类型。结合 extends 可见,在获取泛型元素时,使用 extends 获取到的是泛型中的上边界的类型(本例子中为 Fruit), 范围更小。
- 在使用泛型时,存取元素时用 super。
- 获取元素时,用 extends。
有了上面的结论我们看下 Java 标准库的 Collections 类定义的 copy()方法,这个 copy()方法的定义就完美地展示了 extends 和 super 的意图:
- copy()方法内部不会读取 dest,因为不能调用 dest.get()来获取 T 的引用;
- copy()方法内部也不会修改 src,因为不能调用 src.add(T)。
public class Collections {
// 把 src 的每个元素复制到 dest 中:
public static void copy(List super T> dest, List extends T> src) {for (int i=0; i
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java 的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除
我们看一个示例:
public class Test2 {public static void main(String[] args) {Map map = new HashMap();
Animal animal = new Animal();
animal.setVegetarian(true);
animal.setEats("fish");
map.put("cat", animal);
String json = new Gson().toJson(map);
System.out.println(json);
Map jsonToMap = fromJson(json);
System.out.println(jsonToMap);
Animal animal1 = jsonToMap.get("cat");
System.out.println(animal1.getEats());
}
public static T fromJson(String str) {return new Gson().fromJson(str, new TypeToken () {}.getType());
}
}
上的代码运行会提示如下异常:
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.uaf.rabbitmq.producer.Animal
at com.uaf.rabbitmq.producer.Test2.main(Test2.java:30)
异常原因主要是这句:new Gson().fromJson(str, new TypeToken() {}.getType());
这句在实际执行的时候,List 中的 T 并未传入实际的泛型参数,导致 Gson 按照 LinkedTreeMap 来解析 JSON,以致发生了错误; 这就是一个在编译期泛型类型擦除所导致的问题;
解决这个问题我们需要修改 fromJson 方法
public class Test2 {public static void main(String[] args) {Map map = new HashMap();
Animal animal = new Animal();
animal.setVegetarian(true);
animal.setEats("fish");
map.put("cat", animal);
String json = new Gson().toJson(map);
System.out.println(json);
Map jsonToMap = fromJson(json,
new TypeToken
在 Gson 中提供了 TypeToken 解决泛型运行时类型擦除问题,TypeToken 这个类来帮助我们捕获像 Map 这样的泛型信息。上文创建了一个匿名内部类,这样 Java 编译器就会把泛型信息编译到这个匿名内部类里,然后在运行时就可以被 getType()方法用反射 API 提取到。