JDK1.5新特性(六)……Generics

时间:2022-03-30 11:51:00

概述

Generics - This long-awaited enhancement to the type system allows a type or method to operate on objects of various types while providing compile-time type safety. It adds compile-time type safety to the Collections Framework and eliminates the drudgery of casting.

泛型的出现主要是为了解决集合类元素的类型规范,即将集合类参数化,传入规定的元素类型参数,规范其元素的类型,这样就避免了很多类型转换上的异常,下面我们由浅入深,慢慢来介绍

泛型的简单应用

虽然泛型的底层实现略有不爽,但是在表层的使用上还是很好理解的,至少,一些简单的使用就可以解决一大部分问题

下面我们来看一下

过去我们使用集合类来存储和获取对象时是这样做的

   1: public static void main(String[] args) {

   2:     // TODO Auto-generated method stub

   3:     

   4:     //过去使用集合类的方法

   5:     List list = new ArrayList();

   6:     list.add(1);

   7:     list.add(2);

   8:     list.add("1");

   9:     Integer retVal = (Integer)list.remove(2);

  10:     System.out.println(retVal);

  11: }

我们看到,过去必须手动的进行类型转换,这样就很容易出现ClassCastException异常

加入泛型之后,我们这样做

   1: public static void main(String[] args) {

   2:     // TODO Auto-generated method stub

   3:     

   4:     //JDK1.5之后使用集合类的方法

   5:     List<Integer> list = new ArrayList<Integer>();

   6:     list.add(1);

   7:     list.add(2);

   8:     list.add("1");

   9:     Integer retVal = list.remove(2);

  10:     System.out.println(retVal);

  11:     

  12: }

可以看到,List之后尖括号中存放了规定的元素类型,这样只能将Integer对象作为元素传入到List集合中,而当出现其他类型时,编译会出错,这样就有效的将运行时的异常转变为了编译时期的错误,提高了系统的安全性

了解泛型

涉及到的术语

上面的例子中

List和ArrayList被称为原始类型(raw type)

而List<Integer>和ArrayList<Integer>被称为参数化的类型(parameterized type)

其中Integer叫做实际类型参数

原始类型与参数化类型的兼容性

List<Integer> list = new ArrayList();//参数化类型可以接收一个原始类型对象

List l = new ArrayList<String>();//原始类型对象可以接收一个参数化类型对象

之所以会出现这种情况,我想与其底层实现不无关联,等下会提到

看一个例子

   1: public static void main(String[] args) {

   2:     // TODO Auto-generated method stub

   3:     

   4:     //JDK1.5之后使用集合类的方法

   5:     List<Integer> list = new ArrayList();

   6:     List l = new ArrayList<String>();

   7:     list.add(1);

   8:     list.add(2);

   9:     l.add(1);

  10:     Integer retVal = (Integer)l.remove(0);

  11:     System.out.println(retVal);

  12:     

  13: }

由这个例子可知,实际参数只定义在右边是没有什么意义的,在编译阶段,如果左边是原始类型,编译器是不会判断传入的类型的,如果左边是参数化类型,才会判断

参数化类型并不支持类型的继承关系

也就是说

List<Integer> l = new ArrayList<Object>();

List<Object>  l = new ArrayList<Integer>();

两种编译器都不支持,两个类型mismatch.

但是如果是这样定义的,就不会报错

List l = new ArrayList<Object>();

List<Integer> l = l;

为什么会是这样?原因是泛型的实现机制:

因为泛型的语法判断是在编译阶段,泛型的定义只保留在编译阶段,在真正的运行阶段,会将泛型的定义擦除,也就是说List<Integer> 与List<String>在底层其实是共用的一份字节码,所以在Java中的泛型实现跟C++中的模板是有本质区别的,那么为什么不保留泛型定义到运行阶段呢?这是因为Java是解释型语言,编译器生成的字节码文件是可以跨平台的,在不同的平台对应不同的JVM,JVM会将同一份字节码翻译成针对不同平台的二进制指令,那么如果将泛型保留到运行时期,JVM需要做大量的指令集的重构,这个工程非常的浩大,同时,也是为了兼容原来的原始类型,这是在设计历史上必须承担的后果,所以,这种的办法就是将泛型只保留在编译时期,让编译器判断语法正误,而在运行时期,进行类型擦除,当然,在泛型的实现上有很多不够优雅的地方,在业内也褒贬不一,我想这也是任何有历史的编程语言都需要承担的吧。

Java不支持定义参数化类型数组

Vector<Integer> vectorList[] = new Vector<Integer>[10]//这样会报错

泛型的定义

参数化类型其实就是将一种数据类型也作为一个参数传递给一个原始类型,那么我们也可以定义自己的带有泛型参数的东西

泛型类的定义

什么时候定义泛型类?

当类中要操作的某些参数的数据类型不确定时,JDK1.5之前是使用Object定义,例如Ojbect的toString方法

JDK1.5之后我们可以用泛型来定义,通常用单个的大写字母表示一个泛型

   1: public class MyGenericClass<T> {

   2:     private T x;

   3:     private T y;

   4:     

   5:     public T getX() {

   6:         return x;

   7:     }

   8:     public void setX(T x) {

   9:         this.x = x;

  10:     }

  11:     public T getY() {

  12:         return y;

  13:     }

  14:     public void setY(T y) {

  15:         this.y = y;

  16:     }

  17: }

   1: public class GenericsTest {

   2:  

   3:     /**

   4:      * @param args

   5:      */

   6:     public static void main(String[] args) {

   7:         // TODO Auto-generated method stub

   8:         MyGenericClass<String> mgc = new MyGenericClass<String>();

   9:         mgc.setX("x");

  10:         mgc.setY("y");

  11:         System.out.println(mgc.getX());

  12:         System.out.println(mgc.getY());

  13:     }

  14: }

泛型方法的定义

当类中不同方法操作的数据类型不同时,我们可以将泛型定义在方法上

   1: public class MyGenericClass<T> {

   2:     private T x;

   3:     private T y;

   4:     

   5:     public T getX() {

   6:         return x;

   7:     }

   8:     public void setX(T x) {

   9:         this.x = x;

  10:     }

  11:     public T getY() {

  12:         return y;

  13:     }

  14:     public void setY(T y) {

  15:         this.y = y;

  16:     }

  17:     

  18:     public static <E extends Comparable<E>> E max(E a,E b){

  19:         if(a.compareTo(b) > 0)

  20:             return a;

  21:         else

  22:             return b;

  23:     }

  24: }

   1: public class GenericsTest {

   2:  

   3:     /**

   4:      * @param args

   5:      */

   6:     public static void main(String[] args) {

   7:         // TODO Auto-generated method stub

   8:         MyGenericClass<String> mgc = new MyGenericClass<String>();

   9:         mgc.setX("x");

  10:         mgc.setY("y");

  11:         System.out.println(mgc.getX());

  12:         System.out.println(mgc.getY());

  13:         

  14:         //调用静态方法max

  15:         String max = MyGenericClass.max("123", "456");

  16:         System.out.println(max);

  17:     }

  18: }

上面的例子中,max静态方法定义的泛型E与泛型类中定义的泛型T不同,只在max方法上适用,至于extends范围限定,等下会提到

?通配符

?通配符不管是在泛型的定义或者使用上,都应用非常广泛

它表示不确定的泛型类型

例如:

   1: public class GenericsTest2 {

   2:  

   3:     /**

   4:      * @param args

   5:      */

   6:     public static void main(String[] args) {

   7:         // TODO Auto-generated method stub

   8:         print(new ArrayList<String>());

   9:     }

  10:     private static void print(Collection<?> col){

  11:         Iterator<?> it = col.iterator();

  12:         while(it.hasNext()){

  13:             System.out.println(it.next());

  14:         }

  15:     }

  16: }

这样的代码,当我们不确定要传入的实际类型的时候,可以用?作为占位符,表示该类型不确定,那么它的弊端也是显而易见的,就是不能调用类型的特有方法

比如

System.out.println(it.next().length())//这样是不允许的,因为传入的参数类型可能是数组类型

? i = it.next();//这样是不允许的

这也就引出了?与T在定义时的区别

?与T都表示不确定的参数类型,他们有同样的弊端,就是不能调用类型的特有方法

但是T至少可以用作引用,比如 T i = it.next();,这样是可以的,但是定义?的时候就不行了

泛型限定

<? extends E>:可以接收E类型或者E的子类型,即设置上限

<? super E>:可以接收E类型或者E的父类型,即设置下限

   1: public class GenericsTest2 {

   2:  

   3:     /**

   4:      * @param args

   5:      */

   6:     public static void main(String[] args) {

   7:         

   8:         //String并不是Number的子类,所以并不能作为参数传递过去

   9:         print(new ArrayList<String>());

  10:     }

  11:     private static void print(Collection<? extends Number> col){

  12:         Iterator<?> it = col.iterator();

  13:         while(it.hasNext()){

  14:             System.out.println(it.next());

  15:         }

  16:     }

  17: }

上面的例子中,由于print方法定义了?通配符的范围,规定该泛型必须为Number或者Number的子类,String并不是Number的子类,所以并不能作为参数传递过去

通过反射获取泛型的实际类型参数

由于泛型的类型擦除机制,我们在运行过程中是无法从集合类本身获取类型参数的,所以只能从带有这些泛型类参数的方法中发射获取

   1: public class GetParameterdTypeByReflect {

   2:  

   3:     /**

   4:      * @param args

   5:      * @throws NoSuchMethodException 

   6:      * @throws SecurityException 

   7:      */

   8:     public static void main(String[] args) throws SecurityException, NoSuchMethodException {

   9:         

  10:         //获取参数列表中带有泛型类的方法对象

  11:         Method method = GetParameterdTypeByReflect.class.getMethod("applyMethod", ArrayList.class,Collection.class);

  12:         

  13:         //得到方法参数中带有泛型的参数类型

  14:         Type[] types = method.getGenericParameterTypes();

  15:         

  16:         //遍历这些类型

  17:         for(Type type : types){

  18:             //将Type转为ParameterizedType(参数化类型)

  19:             ParameterizedType pType = (ParameterizedType)type;

  20:             //打印参数化类型名称

  21:             System.out.println(pType);

  22:             //打印参数化类型的原始类型

  23:             System.out.println(pType.getRawType());

  24:             //打印参数化类型的实际类型参数列表

  25:             for(int i = 0 ; i < pType.getActualTypeArguments().length ; i ++){

  26:                 System.out.println(pType.getActualTypeArguments()[i]);

  27:             }            

  28:         }

  29:     }

  30:     

  31:     public static void applyMethod(ArrayList<String> arrayList,Collection<?> col){

  32:         

  33:     }

  34: }