JDK1.5新特性之Java Generics
2007-11-26 15:37
330 查看
1 直观印象
在JDK1.5之前的版本中,对于一个Collection类库中的容器类实例,可将任意类型
对象加入其中(都被当作Object实例看待);从容器中取出的对象也只是一个Object实例,需要将其强制转型为期待的类型,这种强制转型的运行时正确性由程序员自行保证。
例如以下代码片断:
而在JDK1.5中,可以创建一个明确只能存放某种特定类型对象的容器类实例,例如如下代码:
“List<Integer> intList = new ArrayList<Integer>();”就是最简单且最常用的Generic应用;显然,运用Generic后的代码更具可读性和健壮性。
2 类Generic
JDK1.5中Collection类库的大部分类都被改进为Generic类。以下是从JDK1.5源码中
截取的关于List和Iterator接口定义的代码片断:
以List为例,“public interface List<E>”中的E是List的类型参数,用户在使用List
时可为类型参数指定一个确定类型值(如List<Integer>)。类型值为Java编译器所用,确保用户代码类型安全。例如,编译器知道List<Integer>的add()方法只接受Integer类型的参数,因此如果你在代码中将一个字符串传入add()将导致编译错误;编译器知道Iterator<Integer>的next()方法返回一个Integer的实例,你在代码中也就无需对返回结果进行(Integer)强制转型。代码检验通过(语法正确且不会导致运行时类型安全问题)后,编译器对现有代码有一个转换工作。简单的说,就是去除代码中的类型值信息,在必要处添加转型代码。例如如下代码:
编译器在检验通过后,将其转换为:
可见,类型值信息只为Java编译器在编译时所用,确保代码无类型安全问题;验证通过之后,即被去除。对于JVM而言,只有如JDK1.5之前版本一样的List,并无List<Integer>和List<String>之分。这也就是Java Generics实现中关键技术Erasure的基本思想。以下代码在控制台输出的就是“true”。
可以将Generic理解为:为提高Java代码类型安全性(在编译时确保,而非等到运行时才暴露),Java代码与Java编译器之间新增的一种约定规范。Java编译器在编译结果*.class文件中供JVM读取的部分里没有保留Generic的任何信息;JVM看不到Generic的存在。
对于Generic类(设为GenericClass)的类型参数(设为T):
1) 由于对于JVM而言,只有一个GenericClass类,所以GenericClass类的静态字段和静态方法的定义中不能使用T。T只能出现在GenericClass的非静态字段或非静态方法中。也即T是与GenericClass的实例相关的信息;
2) T只在编译时被编译器理解,因此也就不能与运行时被JVM理解并执行其代表的操作的操作符(如instanceof 和new)联用。
Generic类可以有多个类型参数,且类型参数命名一般为大写单字符。例如Collection类库中的Map声明为:
3 类和原(Raw)类Generic
对每一个Generic类,用户在使用时可以不指定类型参数。例如,对于List<E>,用户
可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被称为参数化的Generic类(类型参数被赋值),而“List”称为原类。原类List的使用方式和效果与JDK1.5之前版本List的一样;使用原类也就失去了Generic带来的可读性和健壮性的增强。
允许原类使用方式的存在显然是为了代码的向前兼容:即JDK1.5之前的代码在JDK1.5下仍然编译通过且正常运行。
当你在JDK1.5中使用原类并向原类实例中添加对象时,编译器会产生警告,因为它无法保证待添加对象类型的正确性。编译通过是为了保证代码向前兼容,产生警告是提醒潜在的风险。
4 类和子类Generic
以上第二行代码导致的编译错误“Type mismatch: cannot convert from List<Dummy> to
List<Object>”是不是有点出人意料?直观上看,就像String是Object的子类,因此‘Object o = “String”’合法一样,存放String的List是存放Object的List的子类,因此第二行应该是合法的。反过来分析,如果第二行是合法的,那么如下会导致运行时异常的代码也是合法的:
编译器显然不允许此种情形发生,因此不允许“List<Object> lo = ls”编译通过。
因此,直观上的“存放String的List是存放Object的List的子类”是错误的。更一般的说,设Foo是Bar的子类,G是Generic类型声明,G<Foo>不是G<Bar>的子类。
5 参数化的Generic类和数组
我们知道,如果T是S的子类,则T[]也是S[]的子类。因此,如下代码编译通过,只
在运行时于第三行代码处抛ArrayStoreException。
再分析如下代码:
就出现了“正确使用了Generic,但在运行时仍然出现ClassCastException”的情形。显然Java编译器不允许此种情形的发生。事实上,以上代码的第一行“List<String>[] wordLists = new ArrayList<String>[10];”就是编译不通过的,也就不存在接下来的代码。
更一般地说,不能创建参数化的Generic类的数组。
6 类型参数通配符?
由“Generic类和子类”节知,Collection<Object>不是存放其它类型对象的Collection(例
如Collection<String>)的基类(抽象),那么如何表示任一种参数化的Collection的呢?使用Collection<?>。?即代表任一类型参数值。例如,我们可以很容易写出下面的通用函数printCollection():
这样,既可以将Collection<String>的实例,也可以将Collection<Integer>的实例作为参数调用printCollection()方法。
然而,要注意一点,你不能往Collection<?>容器实例中加入任何非null元素,例如如下代码的第三行编译不通过:
很好理解:c中要存放的对象的具体类型不确定,编译器无法验证待添加对象类型的正确性,因此不能加入对象实例;而null可以看作是任一个类的实例,因而允许加入。
另外,尽管c中要存放的对象的类型不确定,但我们知道任何类都是Object子类,因此从c中取出的对象都统一作为Object实例。
更进一步,如果BaseClass代表某个可被继承的类的类名,那么Collection<? extends BaseClass>代表类型参数值为BaseClass或BaseClass某个子类的任一参数化Collection。对于Collection<? extends BaseClass>的实例c,因为c中要存放的对象具体类型不确定,不能往其加入非null对象,但从c中取出的对象都统一作为BaseClass实例。事实上,你可以把Collection<?>看作Collection<? extends Object>的简洁形式。
另一种情形:如果SubClass代表任一个类的类名,那么Collection<? super SubClass>代表类型参数值为SubClass或SubClass某个祖先类的任一参数化Collection。对于Collection<? super SubClass>的实例c,你可以将SubClass实例加入其中,但从中取出的对象都是Object实例。
7 方法Generic
我们可以定义Generic类,同样可以定义Generic方法,即将方法的一个或多个参数的类型参数化,如代码:
我们可以以如下方式调用fromArrayToCollection():
通过以上代码可以看出,我们在调用fromArrayToCollection()时,无需明确指定T为何种类型(与Generic类的使用方式不同),而是像调用一般method一样,直接提供参数值,编译器会根据提供的参数值自动为T赋类型值或提示编译错误(参数值不当)。
考虑如下函数sum()
我们也可以将其以Generic方法实现:
那么对于一个方法,当要求参数类型可变时,是采用Generic方法,还是采用类型参数通配符方式呢?一般而言,如果参数类型间或参数类型与返回值类型间存在某种依赖关系,则采取Generic方法,否则采取类型参数通配符方式。
这一原则在Collection类库的源代码中得到了很好的体现,例如Collection接口的containsAll()、addAll()和toArray()方法:
当然,根据需要,二者也可以结合使用,例如Collections中的copy()方法:
在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 |
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类型的参数 |
2 类Generic
JDK1.5中Collection类库的大部分类都被改进为Generic类。以下是从JDK1.5源码中
截取的关于List和Iterator接口定义的代码片断:
public interface List<E> { void add(E x); Iterator<E> iterator; } public interface Iterator<E> { E next(); boolean hasNext(); } |
时可为类型参数指定一个确定类型值(如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) } |
[align=left]List<String> strList = new ArrayList<String>();[/align] [align=left]List<Integer> intList = new ArrayList<Integer>();[/align] System.out.println(strList.getClass() == intList.getClass()); |
对于Generic类(设为GenericClass)的类型参数(设为T):
1) 由于对于JVM而言,只有一个GenericClass类,所以GenericClass类的静态字段和静态方法的定义中不能使用T。T只能出现在GenericClass的非静态字段或非静态方法中。也即T是与GenericClass的实例相关的信息;
2) T只在编译时被编译器理解,因此也就不能与运行时被JVM理解并执行其代表的操作的操作符(如instanceof 和new)联用。
[align=left]class GenericClass<T> {[/align] [align=left] T t1;[/align] [align=left] publicvoid method1(T t){[/align] [align=left] t1 = new T(); //编译错误,T不能与new联用[/align] [align=left] if (t1instanceof T) {}; //编译错误,T不能与instanceof联用[/align] [align=left] };[/align] [align=left] static T t2; //编译错误,静态字段不能使用T[/align] [align=left] publicstaticvoid method2(T t){};//编译错误,静态方法不能使用T[/align] } |
public interface Map<K,V> { ……; } |
对每一个Generic类,用户在使用时可以不指定类型参数。例如,对于List<E>,用户
可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被称为参数化的Generic类(类型参数被赋值),而“List”称为原类。原类List的使用方式和效果与JDK1.5之前版本List的一样;使用原类也就失去了Generic带来的可读性和健壮性的增强。
允许原类使用方式的存在显然是为了代码的向前兼容:即JDK1.5之前的代码在JDK1.5下仍然编译通过且正常运行。
当你在JDK1.5中使用原类并向原类实例中添加对象时,编译器会产生警告,因为它无法保证待添加对象类型的正确性。编译通过是为了保证代码向前兼容,产生警告是提醒潜在的风险。
[align=left]publicvoid test () {[/align] [align=left] List list = new ArrayList();[/align] [align=left] list.add("tt");//JDK1.5编译器对此行产生警告[/align] } |
List<String> ls = new ArrayList<String>(); List<Object> lo = ls; //编译错误:Type mismatch: cannot convert from List<Dummy> to //List<Object> |
List<Object>”是不是有点出人意料?直观上看,就像String是Object的子类,因此‘Object o = “String”’合法一样,存放String的List是存放Object的List的子类,因此第二行应该是合法的。反过来分析,如果第二行是合法的,那么如下会导致运行时异常的代码也是合法的:
lo.add(new Object); //会导致在ls中添加了非String对象 String s = ls.get(0); //ls.get(0)返回的实际上只是一个Object实例,会导致ClassCastException |
因此,直观上的“存放String的List是存放Object的List的子类”是错误的。更一般的说,设Foo是Bar的子类,G是Generic类型声明,G<Foo>不是G<Bar>的子类。
5 参数化的Generic类和数组
我们知道,如果T是S的子类,则T[]也是S[]的子类。因此,如下代码编译通过,只
在运行时于第三行代码处抛ArrayStoreException。
String[] words = new String[10]; Object[] objects = words; Objects[0] = new Object(); //编译通过,但运行时会抛ArrayStoreException |
List<String>[] wordLists = new ArrayList<String>[10]; ArrayList<Integer> integerList = new ArrayList<Integer>(); integerList.add(123); Object[] objects = wordLists; objects[0] = integerList;//运行时不出错,因为运行时ArrayList<String>和ArrayList<Integer>都 //为ArrayList String s = wordlists[0].get(0); //编译通过,运行时抛ClassCastException |
更一般地说,不能创建参数化的Generic类的数组。
6 类型参数通配符?
由“Generic类和子类”节知,Collection<Object>不是存放其它类型对象的Collection(例
如Collection<String>)的基类(抽象),那么如何表示任一种参数化的Collection的呢?使用Collection<?>。?即代表任一类型参数值。例如,我们可以很容易写出下面的通用函数printCollection():
public static void printCollection(Collection<?> c) { //如此遍历Collection的简洁方式也是JDK1.5新引入的 for (Object o : c) { System.out.println(o); } } |
然而,要注意一点,你不能往Collection<?>容器实例中加入任何非null元素,例如如下代码的第三行编译不通过:
public static void testAdd(Collection<?> c) { c.add(null); //编译通过 c.add(“test”); //编译错误 } |
另外,尽管c中要存放的对象的类型不确定,但我们知道任何类都是Object子类,因此从c中取出的对象都统一作为Object实例。
更进一步,如果BaseClass代表某个可被继承的类的类名,那么Collection<? extends BaseClass>代表类型参数值为BaseClass或BaseClass某个子类的任一参数化Collection。对于Collection<? extends BaseClass>的实例c,因为c中要存放的对象具体类型不确定,不能往其加入非null对象,但从c中取出的对象都统一作为BaseClass实例。事实上,你可以把Collection<?>看作Collection<? extends Object>的简洁形式。
另一种情形:如果SubClass代表任一个类的类名,那么Collection<? super SubClass>代表类型参数值为SubClass或SubClass某个祖先类的任一参数化Collection。对于Collection<? super SubClass>的实例c,你可以将SubClass实例加入其中,但从中取出的对象都是Object实例。
7 方法Generic
我们可以定义Generic类,同样可以定义Generic方法,即将方法的一个或多个参数的类型参数化,如代码:
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) { for (T o : a) { c.add(o); //合法。注意与Collection<?>的区别 } } |
Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromArrayToCollection(oa, co); //此时,T即为Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromArrayToCollection(sa, cs); //此时,T即为String fromArrayToCollection(sa, co); //此时,T即为Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromArrayToCollection(ia, cn); //此时,T即为Number fromArrayToCollection(fa, cn); //此时,T即为Number fromArrayToCollection(na, cn); //此时,T即为Number fromArrayToCollection(na, co); //此时,T即为Object fromArrayToCollection(na, cs); //编译错误 |
考虑如下函数sum()
[align=left]publicstaticlong sum(Collection<? extends Number> numbers) {[/align] [align=left] long sum = 0;[/align] [align=left] for (Number n : numbers) {[/align] [align=left] sum += n.longValue();[/align] [align=left] }[/align] [align=left] return sum;[/align] } |
[align=left]publicstatic <T extends Number> long sum(Collection<T> numbers) {[/align] [align=left] long sum = 0;[/align] [align=left] for (Number n : numbers) {[/align] [align=left] sum += n.longValue();[/align] [align=left] }[/align] [align=left] return sum;[/align] } |
这一原则在Collection类库的源代码中得到了很好的体现,例如Collection接口的containsAll()、addAll()和toArray()方法:
interface Collection<E> { public boolean containsAll(Collecion<?> c); //参数间类型以及参数与返回 //值间类型无依赖 <T> T[] toArray(T[] a); //参数a与返回值都是相同类的数组,有依赖 } |
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ……. } } |
相关文章推荐
- JDK1.5新特性之Java Generics
- Java高新技术-jdk1.5简单的新特性
- 黑马程序员--Java高新技术--JDK1.5新特性(下)
- 黑马程序员--Java基础加强(2)-- JDK1.5新特性之可变参数,增强for,自动拆装箱,枚举
- Java - 各版本特性,JDK1.5 - 9
- 黑马程序员--Java高新技术--JDK1.5新特性(上)
- Java——jdk1.5新特性
- java--加强之 jdk1.5简单新特性,枚举,注解
- java高薪技术——jdk1.5新特性和枚举
- (九)Java入门--多线程(6)多生产多消费--JDK1.5新特性
- 黑马程序员_java高新技术_JDK1.5新特性
- 黑马程序员 java_高新技术(一)_相关基础、JDK1.5新特性
- java--JDK1.5新特性
- java基础--jdk1.5特性,命名规范,…
- 黑马程序员_日记18_Java多线程(八)--生产者消费者问题JDK1.5特性
- java JDK1.5的重要特性
- 黑马程序员--Java高新技术--JDK1.5新特性(下)
- java基础--高薪技术--JDK1.5新特性
- Java基础加强_JDK1.5新特性
- (14)多线程与并发库之java5同步集合类的应用【包含jdk1.5新特性 ConcurrentHashMap】