java 泛型 深入(一)

2014-11-24 09:12:31 · 作者: · 浏览: 0

泛型的好处:

泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。


/******* 不使用泛型类型 *******/
List list1 = new ArrayList();
list1.add(8080); //编译器不检查值
String str1 = (String)list1.get(0); //需手动强制转换,如转换类型与原数据类型不一致将抛出ClassCastException异常

/******* 使用泛型类型 *******/
List list2 = new ArrayList();
list2.add("value"); //[类型安全的写入数据] 编译器检查该值,该值必须是String类型才能通过编译
String str2 = list2.get(0); //[类型安全的读取数据] 不需要手动转换


泛型的类型擦除:

Java 中的泛型只存在于编译期,在将 Java 源文件编译完成 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。

这个过程就称为类型擦除(type erasure)。


List list1 = new ArrayList();
List list2 = new ArrayList();

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 和 List 等类型,在编译之后都会变成 List,而由泛型附加的类型信息对 JVM 来说是不可见的,所以第一条打印语句输出 true,

第二、第三条打印语句都输出 java.util.ArrayList,这都说明 List 和 List 的对象使用的都是同一份字节码,运行期间并不存在泛型。

来看一个简单的例子:


package test;

import java.util.List;
/**
* -----------------------------------------
* @描述 类型擦除
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-8-25


* -----------------------------------------
*/
public class GenericsApp {


public void method(List list){

}

/*
* 编译出错,这两个方法不属于重载,由于类型的擦除,使得这两个方法的参数列表的参数均为List类型,
* 这就相当于同一个方法被声明了两次,编译自然无法通过了
*
public void method(List list){

}
*/

}

以此类为例,在 cmd 中 编译 GenericsApp.java 得到字节码(泛型已经擦除),然后再反编译这份字节码来看下源码中泛型是不是真的被擦除了:

从图中可以看出,经反编译后的源码中 method 方法的参数变成了 List 类型,说明泛型的类型是真的被擦除了,字节码文件中不存在泛型,也就是说,运行期间泛型并不存在,它在

编译完成之后就已经被擦除了。


泛型类型的子类型:

泛型类型跟其是否是泛型类型的子类型没有任何关系。


List list1;
List list2;

list1 = list2; // 编译出错
list2 = list1; // 编译出错

大家都知道,在 Java 中,Object 类是所有类的超类,自然而然的 Object 类是 String 类的超类,按理,将一个 String 类型的对象赋值给一个 Object 类型的对象是可行的,

但是泛型中并不存在这样的逻辑,用更通俗的话说,泛型类型跟其是否子类型没有任何关系。


泛型中的通配符( ):

由于泛型类型与其子类型存在不相关性,那么在不能确定泛型类型的时候,可以使用通配符( ),通配符( )能匹配任意类型。


List< > list;
List list1 = null;
List list2 = null;

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() 方法来获取直接超类的