今天看到这段代码,加深了对java中的泛型编程的理解:
public class BaseDao
private Class
@Autowired
private HibernateTemplate hibernateTemplate;
/**
* 通过反射获取子类确定的泛型类
*/
public BaseDao() {
Type genType = getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
entityClass = (Class) params[0];
}
/**
* 根据ID加载PO实例
*
* @param id
* @return 返回相应的持久化PO实例
*/
public T load(Serializable id) {
return (T) getHibernateTemplate().load(entityClass, id);
}
@Repository
public class UserDao extends BaseDao
private final String GET_USER_BY_USERNAME = "from User u where u.userName = ?";
private final String QUERY_USER_BY_USERNAME = "from User u where u.userName like ?";
/**
* 根据用户名查询User对象
*
* @param userName 用户名
* @return 对应userName的User对象,如果不存在,返回null。
*/
public User getUserByUserName(String userName) {
List
if (users.size() == 0) {
return null;
} else {
return users.get(0);
}
}
/**
* 根据用户名为模糊查询条件,查询出所有前缀匹配的User对象
*
* @param userName 用户名查询条件
* @return 用户名前缀匹配的所有User对象
*/
public List
return (List
}
}
我想大家对于上面的代码有兴趣以及有觉悟的地方应该是在红色的地方
百度之,搜API,算是理解了,便于大家理解:代码如下:
介绍
编辑
编辑本段深入泛型
原始代码
重构
泛型来实现
编辑本段高级应用
限制泛型
通配符泛型
编辑本段泛型方法
泛型的好处:
泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。
/******* 不使用泛型类型 *******/
List list1 = new ArrayList();
list1.add(8080); //编译器不检查值
String str1 = (String)list1.get(0); //需手动强制转换,如转换类型与原数据类型不一致将抛出ClassCastException异常
/******* 使用泛型类型 *******/
List
list2.add("value"); //[类型安全的写入数据] 编译器检查该值,该值必须是String类型才能通过编译
String str2 = list2.get(0); //[类型安全的读取数据] 不需要手动转换
泛型的类型擦除:
Java 中的泛型只存在于编译期,在将 Java 源文件编译完成 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。
这个过程就称为类型擦除(type erasure)。
List
List
System.out.println(list1.getClass() == list2.getClass()); // 输出结果: true
System.out.println(list1.getClass().getName()); // 输出结果: java.util.ArrayList
System.out.println(list2.getClass().getName()); // 输出结果: java.util.ArrayList
在以上代码中定义的 List
第二、第三条打印语句都输出 java.util.ArrayList,这都说明 List
来看一个简单的例子:
package test;
import java.util.List;
/**
* -----------------------------------------
* @描述 类型擦除
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class GenericsApp {
public void method(List
}
/*
* 编译出错,这两个方法不属于重载,由于类型的擦除,使得这两个方法的参数列表的参数均为List类型,
* 这就相当于同一个方法被声明了两次,编译自然无法通过了
*
public void method(List
}
*/
}
以此类为例,在 cmd 中 编译 GenericsApp.java 得到字节码(泛型已经擦除),然后再反编译这份字节码来看下源码中泛型是不是真的被擦除了:
从图中可以看出,经反编译后的源码中 method 方法的参数变成了 List 类型,说明泛型的类型是真的被擦除了,字节码文件中不存在泛型,也就是说,运行期间泛型并不存在,它在
编译完成之后就已经被擦除了。
泛型类型的子类型:
泛型类型跟其是否是泛型类型的子类型没有任何关系。
List
List
list1 = list2; // 编译出错
list2 = list1; // 编译出错
大家都知道,在 Java 中,Object 类是所有类的超类,自然而然的 Object 类是 String 类的超类,按理,将一个 String 类型的对象赋值给一个 Object 类型的对象是可行的,
但是泛型中并不存在这样的逻辑,用更通俗的话说,泛型类型跟其是否子类型没有任何关系。
泛型中的通配符(?):
由于泛型类型与其子类型存在不相关性,那么在不能确定泛型类型的时候,可以使用通配符(?),通配符(?)能匹配任意类型。
List> list;
List
List
list = list1;
list = list2;
限定通配符的上界:
ArrayList extends Number> collection = null;
collection = new ArrayList
collection = new ArrayList
collection = new ArrayList
collection = new ArrayList
collection = new ArrayList
collection = new ArrayList
? extends XX,XX 类是用来限定通配符的上界,XX 类是能匹配的最顶层的类,它只能匹配 XX 类以及 XX 类的子类。在以上代码中,Number 类的实现类有:
AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,因此以上代码均无错误。
限定通配符的下界:
ArrayList super Integer> collection = null;
collection = new ArrayList
collection = new ArrayList
collection = new ArrayList
? super XX,XX 类是用来限定通配符的下界,XX 类是能匹配的最底层的类,它只能匹配 XX 类以及 XX 类的超类,在以上代码中,Integer 类的超类有:
Number、Object,因此以上代码均能通过编译无误。
通过反射获得泛型的实际类型参数:
这个就有点难度了,上面已经说到,泛型的类型参数会在编译完成以后被擦除,那在运行期间还怎么来获得泛型的实际类型参数呢?这个是有点难度了吧?似乎不可能实现的样子,
其实不然,java.lang.Class 类从 Java 1.5 起(如果没记错的话),提供了一个 getGenericSuperclass() 方法来获取直接超类的泛型类型,这就使得获取泛型的实际类型参数成为
了可能,下面来看一段代码,这段代码很精辟,很有用,大家一定要学到手哈:
package test;
import java.lang.reflect.ParameterizedType;
/**
* -----------------------------------------
* @描述 泛型的实际类型参数
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Base
private Class
//代码块,也可将其放置到构造子中
{
entityClass =(Class
}
//泛型的实际类型参数的类全名
public String getEntityName(){
return entityClass.getName();
}
//泛型的实际类型参数的类名
public String getEntitySimpleName(){
return entityClass.getSimpleName();
}
//泛型的实际类型参数的Class
public Class
return entityClass;
}
}
以上代码的精华全在这句:(Class
实际上,这句话咋看起来很难看的明白,理解起来就更加的吃力了,下面容我来将这句复杂的代码拆分开来,理解起来可能会好些:
//代码块,也可将其放置到构造子中
{
//entityClass =(Class
try {
Class> clazz = getClass(); //获取实际运行的类的 Class
Type type = clazz.getGenericSuperclass(); //获取实际运行的类的直接超类的泛型类型
if(type instanceof ParameterizedType){ //如果该泛型类型是参数化类型
Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments();//获取泛型类型的实际类型参数集
entityClass = (Class
}
} catch (Exception e) {
e.printStackTrace();
}
}
注意,获取 Class 实例的时候是用 getClass(),而不是用 Base.class,获取 Class 的方式有三种,这是其中的两种,还有一种是 Class.forName("类全名"),如需了解反射的基础知识
请前往上一篇随笔 java 反射基础
那么,Base.class 与 getClass(),这两个方法来获取类的字节码的时候,有什么不一样的地方呢?当然有不一样的地方了,Base.class 是写死了的,它得到的永远是 Base 类的字节码,
而 getClass() 方法则不同,在上面代码注释中的第一、二行注释我用了“实际运行的类”6个字,这几个字很重要,一定要理解,如果无法理解,下面的你可能就看不懂了。
为了方便大家的理解,下面插加一个小例子来加以说明 类.class 与 getClass() 两种方法来获取类的字节码有什么区别:
package test;
/**
* -----------------------------------------
* @描述 超类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Father {
public Father (){
System.out.println("Father 类的构造子:");
System.out.println("Father.class :" + Father.class);
System.out.println("getClass() :" + getClass());
}
}
package test;
/**
* -----------------------------------------
* @描述 超类的子类(超类的实现类)
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Children extends Father{
}
package test;
/**
* -----------------------------------------
* @描述 测试类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
new Children(); //实际运行的类是Children(Father类的子类或者说是实现类)
}
}
后台打印输出的结果:
Father 类的构造子:
Father.class :class test.Father
getClass() :class test.Children
从打印出的结果看来,类.class 与 getClass() 的区别很明了了,getClass() 获取的是实际运行的类的字节码,它不一定是当前类的 Class,有可能是当前类的子类的 Class,具体是哪
个类的 Class,需要根据实际运行的类来确定,new 哪个类,getClass() 获取的就是哪个类的 Class,而 类.class 获取得到的 Class 永远只能是该类的 Class,这点是有很大的区别的。
这下“实际运行的类”能理解了吧,那么上面的那段被拆分的代码也就不难理解了,getClass() 理解了那 clazz.getGenericSuperclass() 也就没什么问题了吧,千万不要以为
clazz.getGenericSuperclass() 获取得到的是 Object 类那就成了,实际上假如当前运行的类是 Base 类的子类,那么 clazz.getGenericSuperclass() 获取得到的就是 Base 类。
再者就是最后一句,(Class
参数列表中只有一个参数,所以,第一个元素就是泛型 T 的实际参数类型。
其余的已经加了注释,看一下就明白了,这里不多解释,下面 Base 这个类是不是就直接能使用了呢?来看一下就知道了:
package test;
/**
* -----------------------------------------
* @描述 测试类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
Base
System.out.println(base.getEntityClass()); //打印输出 null
// System.out.println(base.getEntityName()); //抛出 NullPointerException 异常
// System.out.println(base.getEntitySimpleName()); //抛出 NullPointerException 异常
}
}
从打印的结果来看,Base 类并不能直接来使用,为什么会这样?原因很简单,由于 Base 类中的 clazz.getGenericSuperclass() 方法,如果随随便便的就确定 Base 类的泛型的类型
参数,则很可能无法通过 Base 类中的 if 判断,导致 entityClass 的值为 null,像这里的 Base
Base 类不能够直接来使用,而是应该通过其子类来使用,Base 应该用来作为一个基类,我们要用的是它的具体的子类,下面来看下代码,它的子类也不是随便写的:
package test;
/**
* -----------------------------------------
* @描述 Base类的实现类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Child extends Base
}
从上面代码来看,Base 的泛型类型参数就是 Base 的子类本身,这样一来,当使用 Base 类的子类 Child 类时,Base 类就能准确的获取到当前实际运行的类的 Class,来看下怎么使用
package test;
/**
* -----------------------------------------
* @描述 测试类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-25
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
Child child = new Child();
System.out.println(child.getEntityClass());
System.out.println(child.getEntityName());
System.out.println(child.getEntitySimpleName());
}
}
后台打印输出的结果:
class test.Child
test.Child
Child
好了,文章接近尾声了,如果你能理解透这个例子,你可以将这个思想运用到 DAO 层面上来,以 Base 类作为所有 DAO 实现类的基类,在 Base 类里面实现数据库的 CURD 等基本
操作,然后再使所有具体的 DAO 类来实现这个基类,那么,实现这个基类的所有的具体的 DAO 都不必再实现数据库的 CURD 等基本操作了,这无疑是一个很棒的做法。
(通过反射获得泛型的实际类型参数)补充:
泛型反射的关键是获取 ParameterizedType 接口,再调用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可获得实际绑定的类型。
由于去参数化(擦拭法),也只有在 超类(调用 getGenericSuperclass 方法) 或者成员变量(调用 getGenericType 方法)或者方法(调用 getGenericParameterTypes 方法)
像这些有方法返回 ParameterizedType 类型的时候才能反射成功。
上面只谈到超类如何反射,下面将变量和方法的两种反射补上:
通过方法,反射获得泛型的实际类型参数:
package test;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
/**
* -----------------------------------------
* @描述 测试类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-26
* -----------------------------------------
*/
public class Test {
public static void main(String[] args){
/**
* 泛型编译后会去参数化(擦拭法),因此无法直接用反射获取泛型的参数类型
* 可以把泛型用做一个方法的参数类型,方法可以保留参数的相关信息,这样就可以用反射先获取方法的信息
* 然后再进一步获取泛型参数的相关信息,这样就得到了泛型的实际参数类型
*/
try {
Class> clazz = Test.class; //取得 Class
Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
Type[] type = method.getGenericParameterTypes(); //取得泛型类型参数集
ParameterizedType ptype = (ParameterizedType)type[0];//将其转成参数化类型,因为在方法中泛型是参数,且Number是第一个类型参数
type = ptype.getActualTypeArguments(); //取得参数的实际类型
System.out.println(type[0]); //取出第一个元素
} catch (Exception e) {
e.printStackTrace();
}
}
//声明一个空的方法,并将泛型用做为方法的参数类型
public void applyCollection(Collection
}
}
后台打印输出的结果:
class java.lang.Number
通过字段变量,反射获得泛型的实际类型参数:
package test;www.2cto.com
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
/**
* -----------------------------------------
* @描述 测试类
* @作者 fancy
* @邮箱 [email protected]
* @日期 2012-8-26
* -----------------------------------------
*/
public class Test {
private Map
public static void main(String[] args){
try {
Class> clazz = Test.class; //取得 Class
Field field = clazz.getDeclaredField("collection"); //取得字段变量
Type type = field.getGenericType(); //取得泛型的类型
ParameterizedType ptype = (ParameterizedType)type; //转成参数化类型
System.out.println(ptype.getActualTypeArguments()[0]); //取出第一个参数的实际类型
System.out.println(ptype.getActualTypeArguments()[1]); //取出第二个参数的实际类型
} catch (Exception e) {
e.printStackTrace();
}
}
}
后台打印输出的结果:
class java.lang.String
class java.lang.Number