欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  Java

泛型与反射的使用总结之泛型篇

程序员文章站 2022-04-01 11:44:05
...
在我看来,JDK5.0绝对是一个很具有里程碑意义的版本,在这个版本中,提供了非常多的很有价值的新特性,泛型就是其中之一,并且对反射机制进行了增强,而且5.0版本还把以前集合框架进行了重构全部添加了泛型支持。
从5.0发布到现在差不多快有10年时间了,关于这方面的知识介绍网上可以查到很多,书上也都有讲到。大象现在再写这些东西,一是将自己的经历体会总结出来作一个积累,另外一点是希望能够给刚接触这方面的童鞋一点帮助。
泛型最大的好处就是类型检查,尤其是对集合非常有用,另外在底层代码设计中很有用处,它实现了重用的功能。泛型有两种定义方式,一个是泛型类,另一个是泛型方法。
那到底什么是泛型呢?简单点讲(可能不严谨),就是用到了类型参数这样的类型变量,不管是类、接口还是方法,都可以说是用到了泛型。请看例子:
泛型类
public class Person<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
T就是类型变量,是一个参数化类型,用尖括号(<>)括起来,放在类名的后面。泛型类的类型变量可以定义多个。类型变量一般都使用一个大写字母表示,比如本例的Person<T>,JDK中的List<E>,Map<K,V>等等。
用具体的类替换类型变量就可以实例化泛型类:Person<Man> person = new Person<Man>();
像这样实例化是错误的:Person<T> person = new Person<T>(); //ERROR
泛型方法

public <T> T get(String key, Object params) {
return (T) getSqlSession().selectOne(key, params);
}

这是我在SSM3示例的MyBatisDao这个类里面定义的一个方法,此方法就是一个泛型方法。<T>就是类型变量,而get前面的T是返回类型。其实这个方法是存在类型安全问题的,如果我在RoleService里面调用这个方法,将返回类型T写成User,编译器是不会有任何警告信息的。
但如果我改写一下,将MyBatisDao加上泛型,public class MyBatisDao<T> extends SqlSessionDaoSupport
这时User的返回类型就出现编译错误了:

泛型与反射的使用总结之泛型篇

编译器根据MyBatisDao<Role>这个Role类型变量就会推断出它里面定义的get方法应该返回Role类型。不过这样改过之后MyBatisDao就变成泛型类了,而get方法也不再是泛型方法。那么泛型方法能不能有安全检查呢?有,但是需要一些编程技巧,关键还是跟你写的泛型方法有关系,后面提到的类型参数的限定可以对泛型加以约束,解决一些安全检查的问题。
类型参数的限定
对于像<T>这样的类型变量所代表的范围有时太大了点,有时不方便使用。比如现在需要实现了java.io.Serializable接口的泛型类,那么这应该如何做呢?JDK那帮专家们为我们设计了一种叫做“有限制的通配符类型”来解决这个问题。一般我们称为上限和下限,他们一般写成下面这样:
上限:<T extends Serializable>或者<? extends T>
下限:<? super T>
问号(?)叫做无限制的通配符,它可以表示任何类型。有时候使用类型变量不是那么的方便,通配符类型就很好的解决了这个问题。
<T extends Serializable>的含义是,T为实现了Serializable接口的类,T为绑定类型(Serializable)的子类型,T和绑定类型可以是接口也可以是类。如果想再加个实现了Comparable接口的限定,只需要这样写:<T extends Serializable & Comparable>这样写有点不严谨,因为Comparable接口是一个泛型接口可以接收泛型参数,现在我们不讨论这么复杂的情况。
<? super T>可以这样理解,任何T类型变量的超类型,还包括T本身,因为T可以看成是它本身的一个超类型。
那为什么说extends是上限,而extends是下限呢?通过前面两个解释就应该可以看出来,extends Serializable或者extends T说明类型变量必须是Serializable的子类和T变量的子类型,这是不是相当于限制了类型变量的上限了?同理就可以理解下限的含义了。
说了这么多关于上限和下限的东西,那他们到底有什么用?和怎么用呢?简单来讲,extends限定的类型参数可以从泛型对象读取,super限定的类型参数可以向泛型对象写入。这样说可能有些童鞋要晕了,这到底说的神马东西呢?
让我们换个方式来讲,关于泛型的上限与下限已经总结出来一个公式:PECS
PECS表示producer-extends,consumer-super
上面这个就是说如果参数化类型表示一个生产者,就用<? extends T>,如果它表示一个消费者,就用<? super T>。再结合上面的说明一起来理解,是不是清楚了呢?如果还不是很理解,大象再附上一小段代码来体会下其中的区别。
public void add(List<? extends T> list){
for(T t : list){
add(t);
}
}
public void add(T t){};
public void add(T t, List<? super T> list){
list.add(t);
}
泛型的擦除
泛型主要是在编译期有效,即编译的时候检查类型安全,现在写代码一般都会用Eclipse或IntelliJ,这些集成开发工具都能做到即时编译,哪里有错马上会出现红色的错误标识。因此如果出现类型转换错误,会很明显的看到结果。但是,在程序的运行阶段,JVM是不认识泛型是神马东西的,所有有泛型的类,接口,方法都会被擦除掉泛型,变成原生类型(raw type),即Person<T>变为Person,List<? extends T> list变成List list等等。
下面就是之前的Person类用javap反编译后的结果,所有的类型变量T都被擦除掉了,因为T是一个无限定类型所以用Object替换。而且add方法中的<? extends T>和<? super T>也被去掉了。
public class com.bolo.Person extends java.lang.Object{
private java.lang.Object t;
public com.bolo.Person();
public java.lang.Object getT();
public void setT(java.lang.Object);
public void add(java.util.List);
public void add(java.lang.Object);
public void add(java.lang.Object, java.util.List);
}
因此针对泛型擦除这一特点,我们需要注意这样几点:
1、JVM里面没有泛型,只有普通的类和方法。
2、所有的类型参数都用限定类型或者无限定类型用Object来替代。
3、请谨慎处理方法重载,错误的使用重载不会实现想要的多态。
4、为保证类型安全,必要时请使用强制类型转换。
泛型的限制
基本类型不能作为类型参数。Person<int>就是错误的,只能用Person<Integer>
类型检查只能用原始类型。if(t instanceof Person) 如果写成if(t instanceof Person<String>)马上会出现编译错误
不能实例化类型变量。这样写是错误的:T t = new T()
不能实例化参数化类型的数组。Person<Integer>[] p = new Person<Integer>[5] //ERROR
不能定义静态实例变量和静态方法。如果你想这样写:private static T a 那对不起,编译器马上会给你一个错误提示。
其实关于泛型的限制完全可以不用讲,现在编译器都很强大,只要你这样做了,马上会给你显示一个错误。
最后说下泛型对于集合的用处来说是最大的,集合是一个容器,有了泛型就更方便重用。而我们使用最频繁的集合就是List列表,还有一个容器就是数组,大象在这里强烈建议大家多用List,尽量或最好不要用数组。其一是List有类型安全性检查,其二是数组的功能List都提供了并且更丰富,其三List对gc进行了优化。如果使用数组,特别是操作对象数组,如果经验不足,没有释放数组里面的对象引用,则很容易造成内存泄漏的问题。



更多泛型与反射的使用总结之泛型篇相关文章请关注PHP中文网!

相关标签: 泛型 反射