最近看Spring 源代码的时候遇到了一个关于泛型的问题,于是就请教我的老大帮我
解释了一下,然后又自己看了一点资料,感觉对泛型已经有了一定的理解了,就先
把这些想法记录下来,供有需要的人参考同时也为了能够加深理解
如果你在看JDK1.5的源码的时候就会发现,java.util.HashMap这个类里面出现了
这种代码:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
public HashMap(Map<? extends K, ? extends V> m) {.}
}
里面出现了大量的角号和K,V,T...等不明所以的代码,其实这就是泛型的应用。
泛型的一些概念:
List<E> , List< String > , List
List<E>是参数话类型 parameterized type ,
E是(formal)类型参数 type parameter,
String 实际类型参数 actual type argument,
List是自然类型 raw type
为什么会出现泛型:
原来的JDK中
当我不知道user到底是个什么对象的时候只能这样写:
static Object maskNull(Object o){
return key == null ? NULL_KEY :key;
}
如果我在外面使用的时候:
User u = ...;
User user = (User)map.maskNull(u);
这里用到了向上转型,把map.maskNull(u)的返回对象强制转型为了User,强制转为什么类型完全有程序员控制,所以我在这里这样写也是可以的
Department d = (Department)map.maskNull(u);
这个错误在编译的时候是不会有任何错误提示的,必须到了runtime才能知道返回的类型和Department是不匹配的
但是如果使用了泛型以后我可以写成这样
static <T> T maskNull(T key) {
return key == null ? (T)NULL_KEY : key;
}
User u = ...;
User user = map.maskNull(u);
当我再写成Department d = map.maskNull(u);
的时候就会编译失败,因为方法要求参数和返回值必须是 T 类型的,当然这里的T只是暂时的一个声明,类似于去占一个坑,当我需要是User类型的时候,我就用 User去替换掉它,这样编译的时候就要求传进的和返回的必须是一致的类型,所以我们刚才的那种写法就不行了,这样编译器就使用自身的机制保证类型安全type-safe
但是泛型还存在另外一个重要的概念bridge method
先讲一下JDK对泛型的支持:上面的讲解虽然说泛型很有用,但是泛型在runtime是没有任何作用的,只是为了编译期的方便,所以当过了编译期之后还是要转换为普通代码的执行方式的,也就是说过了编译期,带角号的部分会被抹掉,变成了原始的方式
static Object maskNull(Object o){...}
那么现在看下面的代码:
class A<T>{
T setX(T t){...}
}
class B extends A<String>{
String setX(String s){...}
}
很明显,当B的方法void String setX(String s)重载了A的方法,但是按照我们刚才讲的理论A被编译成了
class A{
Object setX(Object t){...}
}
也就是说经过编译期之后类B的方法就不重载了A的方法,这是不被允许的,所以载编译的时候同样有两外一件事情发生:JDK会帮你添加一个帮助方法来重载A的方法,于是B就变成了:
class B extends A{
String setX(String s){...}
Object setX(Object o){
return setX((String)o);
}
}
大家看到多了个重载A类方法的一个方法:
Object setX(Object o){...}
这个方法就是所谓的bredge method
这个工作是在编译期完成的。所以如果你遍历A上的方法的时候如果发现多了方法千万不要感觉以外
注意这个方法的重载对EL表达式和TPL的运行不存在影响,因为不管用的哪个方法,经过TPL的转型之后结果都是一样的