JDK1.5新特性之Java Generics

本文深入讲解Java泛型机制,包括直观理解、泛型类的定义与使用、泛型类与子类的关系等内容,帮助读者更好地掌握Java泛型,提升代码质量和可维护性。

1         直观印象

JDK1.5之前的版本中,对于一个Collection类库中的容器类实例,可将任意类型

对象加入其中(都被当作Object实例看待);从容器中取出的对象也只是一个Object实例,需要将其强制转型为期待的类型,这种强制转型的运行时正确性由程序员自行保证。

例如以下代码片断:

 

List intList = new ArrayList(); //创建一个List,准备存放一些Integer实例

intList.add(new Integer(0));

intList.add(“1”); //不小心加入了一个字符串;但在编译和运行时都不报错,只有仔细的代码走
                       //才能揪出

Integer i0 = (Integer)intList.get(0);

Integer i1 = (Integer)intList.get(1); //编译通过,直到运行时才抛ClassCastException

      

而在JDK1.5中,可以创建一个明确只能存放某种特定类型对象的容器类实例,例如如下代码:

 

List<Integer> intList = new ArrayList<Integer>(); //intList为存放Integer实例的List

intList.add(new Integer(0));

Integer i0 = intList.get(0); //无需(Integer)强制转型;List<Integer>get()返回的就是Integer
                                    //型对象

intList.add(“1”); //编译不通过,因为List<Integer>add()方法只接受Integer类型的参数

 

       List<Integer> intList = new ArrayList<Integer>();”就是最简单且最常用的Generic应用;显然,运用Generic后的代码更具可读性和健壮性。

2         Generic

JDK1.5Collection类库的大部分类都被改进为Generic类。以下是从JDK1.5源码中

截取的关于ListIterator接口定义的代码片断:

 

public interface List<E> {

       void add(E x);

       Iterator<E> iterator;

}

public interface Iterator<E> {

       E next();

       boolean hasNext();

}

 

List为例,“public interface List<E>”中的EList的类型参数,用户在使用List

时可为类型参数指定一个确定类型值(如List<Integer>)。类型值为Java编译器所用,确保用户代码类型安全。例如,编译器知道List<Integer>add()方法只接受Integer类型的参数,因此如果你在代码中将一个字符串传入add()将导致编译错误;编译器知道Iterator<Integer>next()方法返回一个Integer的实例,你在代码中也就无需对返回结果进行(Integer)强制转型。代码检验通过(语法正确且不会导致运行时类型安全问题)后,编译器对现有代码有一个转换工作。简单的说,就是去除代码中的类型值信息,在必要处添加转型代码。例如如下代码:

 

public String demo() {

       List<String> strList = new ArrayList<String>();

       strList.add(“Hello!”);

       return strList.get(0);

}

 

编译器在检验通过后,将其转换为:

 

public String demo() {

       List strList = new ArrayList(); //去除类型值<String>

       strList.add(“Hello!”);

       return (String)strList.get(0);  //添加转型动作代码(String)

}

 

       可见,类型值信息只为Java编译器在编译时所用,确保代码无类型安全问题;验证通过之后,即被去除。对于JVM而言,只有如JDK1.5之前版本一样的List,并无List<Integer>List<String>之分。这也就是Java Generics实现中关键技术Erasure的基本思想。以下代码在控制台输出的就是“true”。

 

List<String> strList = new ArrayList<String>();

List<Integer> intList = new ArrayList<Integer>();

System.out.println(strList.getClass() == intList.getClass());

 

       可以将Generic理解为:为提高Java代码类型安全性(在编译时确保,而非等到运行时才暴露),Java代码与Java编译器之间新增的一种约定规范。Java编译器在编译结果*.class文件中供JVM读取的部分里没有保留Generic的任何信息;JVM看不到Generic的存在。

       对于Generic类(设为GenericClass)的类型参数(设为T):

1)        由于对于JVM而言,只有一个GenericClass类,所以GenericClass类的静态字段和静态方法的定义中不能使用TT只能出现在GenericClass的非静态字段或非静态方法中。也即T是与GenericClass的实例相关的信息;

2)        T只在编译时被编译器理解,因此也就不能与运行时被JVM理解并执行其代表的操作的操作符(如instanceof new)联用。

 

class GenericClass<T> {

    T t1;

    public void method1(T t){

       t1 = new T(); //编译错误,T不能与new联用

       if (t1 instanceof T) {}; //编译错误,T不能与instanceof联用

    };

    static T t2; //编译错误,静态字段不能使用T

    public static void method2(T t){};//编译错误,静态方法不能使用T

}

 

       Generic类可以有多个类型参数,且类型参数命名一般为大写单字符。例如Collection类库中的Map声明为:

 

public interface Map<K,V> {

       ……;

}

3         Generic类和原(Raw)类

对每一个Generic类,用户在使用时可以不指定类型参数。例如,对于List<E>,用户

可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被称为参数化的Generic类(类型参数被赋值),而“List”称为原类。原类List的使用方式和效果与JDK1.5之前版本List的一样;使用原类也就失去了Generic带来的可读性和健壮性的增强。

       允许原类使用方式的存在显然是为了代码的向前兼容:即JDK1.5之前的代码在JDK1.5下仍然编译通过且正常运行。

       当你在JDK1.5中使用原类并向原类实例中添加对象时,编译器会产生警告,因为它无法保证待添加对象类型的正确性。编译通过是为了保证代码向前兼容,产生警告是提醒潜在的风险。

 

public void test () {

    List list = new ArrayList();

    list.add("tt");//JDK1.5编译器对此行产生警告

}

 

4         Generic类和子类

List<String> ls = new ArrayList<String>();

List<Object> lo = ls; //编译错误:Type mismatch: cannot convert from List<Dummy> to 
                                                           //List<Object>

 

以上第二行代码导致的编译错误“Type mismatch: cannot convert from List<Dummy> to

List<Object>”是不是有点出人意料?直观上看,就像StringObject的子类,因此‘Object o = “String”’合法一样,存放StringList是存放ObjectList的子类,因此第二行应该是合法的。反过来分析,如果第二行是合法的,那么如下会导致运行时异常的代码也是合法的:

 

lo.add(new Object); //会导致在ls中添加了非String对象

String s = ls.get(0); //ls.get(0)返回的实际上只是一个Object实例,会导致ClassCastException

 

       编译器显然不允许此种情形发生,因此不允许“List<Object> lo = ls”编译通过。

       因此,直观上的“存放StringList是存放ObjectList的子类”是错误的。更一般的说,设FooBar的子类,GGeneric类型声明,G<Foo>不是G<Bar>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值