什么是泛型?
泛型,很多资料里面解释为“参数化类型”;这种是解释比较抽象,初学者看到这里估计都打退堂鼓了。我们可以将泛型简单理解为一个容器,这个容器可以装水、装酒、装牛奶、装沙子等一切实物(暂时我们先认为可以装一切实物),该容器没有说明书来指定装什么,具体内容完全由使用者决定;但是,容器的使用基本规范是一个容器只允许装一种实物,不能同时装不同的实物;并且容器对里面装的实物的操作不取决于实物本身,比如容器会对里面的实物进行计数、排序、翻转,不会因为里面是液体或者固体而操作不同。
这样解释是不是容易理解一些。泛型就是这样的,在定义类、接口、方法的时候不指定特定类型,而让类、接口、方法的调用者决定具体使用哪种类型作为参数。在Java中,经常用T、E、K、V等形式的参数来表示泛型参数。
举个例子:
List arrayList = new ArrayList();
arrayList.add("dog");
arrayList.add(100);
for (int i=0; i<arrayList.size(); i++) {
String test = (String)arrayList .get(i));
}
这段代码编译是没有问题的,但是运行时会报错:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
泛型可以帮我们在编译阶段有效的检测出上面的类型转换问题。
List<String> arrayList = new ArrayList<String>();
arrayList.add("dog");
arrayList.add(100);
如果我们使用泛型,这段代码在第三行会提示错误:add (java.lang.String) in ArrayList cannot be applied to (int)
有了泛型以后:
- 代码更简洁,只需在定义类型时指定参数类型;
- 代码更健壮,在编译阶段会检查参数是否匹配,避免了运行时做强制类型转换,只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常。
泛型信息只存在于编译阶段(狗队在编译时不允许站猫),运行阶段就消失了(运行时的队列里没有猫的信息,连狗的信息也没有),这种现象被称为“类型擦除”。
Map<String, Cat> cat = new HashMap<>();
Map<String, Dog> dog = new HashMap<>();
System.out.println(cat.getClass());
// 输出:class java.util.HashMap
System.out.println(dog.getClass());
// 输出:class java.util.HashMap
都是输出class java.util.HashMap,也就是说代码在运行时并不知道到map的键上放的是猫还是狗。
试着想想:既然运行时泛型的信息被擦除了,而反射机制是在运行时确定类型信息的,那么利用反射机制,是不是就能够在键位为Cat的Map上放一只Dog呢?
Map<String,String> test = new HashMap<>();
test.put("one","cat");
try {
Method m = test.getClass().getDeclaredMethod("put", Object.class, Object.class);
m.invoke(test, "two", 2);
System.out.println(test);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out: {one=cat, two=2},竟然在键值都是String的Map里面成功插入int的值。
这是因为,Java的设计者在JDK 1.5时才引入了泛型,但为了照顾以前的设计,同时兼容非泛型的代码,不得不做出了一个折中的策略:编译时对泛型要求严格,运行时却把泛型擦除了。