泛型全面分析和应用(一)

       概述

       Java SE5的重大变化之一就是提出了泛型的概念。泛型实现了参数化类型的概念,使代码可以应用于多种类型中。有很多原因促成了泛型的出现,而最主要的原因,就是为了构建容器类。我们很多时候在写代码的过程中,经常是遇到了同一个方法如果传入或者传出的参数类型过于单一,同样的功能的方法我们要写两次甚至是多次,原因就是由于参数类型不能适应其他类型的应用。那么为了解决这个问题,Java设计者也是煞费苦心。

 

  • 一个泛型的小例子
  • 泛型中使用注意的方法特性
  • 擦除与其解决方法
  • 泛型技术实例

 

一、一个泛型的小例子

 


      在Java SE5之前,为了写一个通用的方法,也许得这样子。用Object来作为一个标准,反正大家都是Object的子类。

 1 package Generic;
 2 
 3 public class Holder {
 4 
 5     private Object a;
 6     public Holder(Object a){
 7         this.a = a;
 8     }
 9     public void set(Object a){
10         this.a = a;
11     }
12     public Object get(){
13         return a;
14     }
15     public static void main(String args[]){
16         Holder h1 = new Holder(new String());
17         h1.set("ss");
18         Holder h2 = new Holder(new Integer(1));
19         h2.set(“asdf”);
20     }
21 }

       大家发现这个问题没有,既然Hodler h2的set方法的传入参数是一个Object,那么这个参数就可以是String,Integer,Double,h2其实是想保存的一个Integer类型的数据,但由于Object的泛用性,导致了一个String类型的数据保存了进去。这么来说,这就是一个潜在的隐患了。

       在有些情况下,我们确实希望容器能够同时持有多种类型的对象。但是,通常来说,我们只会使用容器来存储一种类型的对象。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且有编译器来保证类型的正确性。因此,与其使用Object,我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类的时候,再用实际的类型代替此类型参数。

 1 package Generic;
 2 
 3 public class Holder2<T> {
 4 
 5     private T a;
 6     public Holder2(T a){
 7         this.a = a;
 8     }
 9     public void set(T a){
10         this.a = a;
11     }
12     public T get(){
13         return a;
14     }15 }

 

 

二、泛型中使用注意的方法特性

 


 

      由于Object的包容性,导致了参数可能的混乱。所以为了解决这个问题,泛型的提出是一个很好的方案。下面就来介绍下泛型的一些用法和特点,这些特性大都由于泛型的擦除机制所导致,擦除机制是指在运行过程中任何和类型有关的信息都会被擦除,所有在运行中集合的具体信息都被擦除成它们的原生类型。下面通过一个例子来了解:

 

 1 public class TestF {
 2 
 3     public static void main(String[] args) {
 4         Class a1 = new ArrayList<Integer>().getClass();
 5         Class a2 = new ArrayList<String>().getClass();
 6         System.out.println(a1 == a2);
 7        //结果是true
 8     }
 9 
10 }

 

      ArrayList<Integer>和ArrayList<String>的class其实在擦除后都是ArrayList。

 

1.不能有继承的关系泛型的

 

Vector<Object> v = new Vector<String>(); 
//Type mismatch: cannot convert from Vector<String> to Vector<Object>

     

       不能有继承的关系泛型。声明了一个Vector<Object>与Vector<String>这对组合和Object与String这对组合是完全不同的。Object和String这对的确有关系,Object是String的父类,从java的多态Object obj = new String()这个语句是没有关系的。但是Vector<Object>和Vector<String>的组合就不是继承关系,在编译时,其实、Vector<Object> v =new Vector<String>实际上是Vector c = new Vector()这种效果,其实就是java泛型的擦除效果

      其实从另外一个角度去看,Vector<Object>其实就是告诉编译器,Vector里面要放的是Object,但是在实例化的时候却放入了String,虽然是继承关系,但是还是不同的两个class。当涉及到了集合,看问题的角度提升到 集合 的层面去观察,里面的泛型类型就一定要一致了(在没有使用通配符?的情况下要一致,使用了?后面详细讲)

 

2.不能有声明数组类型的泛型

         为什么不能有声明数组类型的泛型呢?我看了一下Java SE的官方文档 http://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html
         里面有两段代码很清楚的阐释了这个问题。

 

 1 // Not really allowed.
 2 List<String>[] lsa = new List<String>[10];
 3 Object o = lsa;
 4 Object[] oa = (Object[]) o;
 5 List<Integer> li = new ArrayList<Integer>();
 6 li.add(new Integer(3));
 7 // Unsound, but passes run time store check
 8 oa[1] = li;
 9 
10 // Run-time error: ClassCastException.
11 String s = lsa[1].get(0);

 

      假设泛型数组允许被建立,如果此时有一个List<String>的数组,但是经过一系类转换后,居然把List<Integer>给赋给了 List<String>数组,这是明显的错误,但是由于java泛型的擦除机制,程序代码没有报错,此时已经有问题了。

       解决这个问题,就是不要声明数组类型的泛型,用通配符声明。

 1 // OK, array of unbounded wildcard type.
 2 List<?>[] lsa = new List<?>[10];
 3 Object o = lsa;
 4 Object[] oa = (Object[]) o;
 5 List<Integer> li = new ArrayList<Integer>();
 6 li.add(new Integer(3));
 7 // Correct.
 8 oa[1] = li;
 9 // Run time error, but cast is explicit.
10 String s = (String) lsa[1].get(0);

        由于通配符,程序一开始不知道List<?>具体是什么类型的的数组,这个时候我们将List<Integer>放入进去,编译器还好能接受。最后一句,故意从List<Integer>去拿String,这是报了java.lang.ClassCastException的错误。

 

3.泛型类不能有static修饰方法

      这个更加不用说了,泛型编译后其实是擦除了很多信息,很多时候都没有确定好参数类型,到了运行调用的时候才进行的添加的。而static是静态关键字,被修饰的变量和方法都是在运行一开始就被加载到了虚拟机中,无需要再new对象,一直通过对象引用就好了。这两种状态是不一致的,所以不能在泛型类上面加上static。

 

4.泛型参数会由于参数的合集类型、个数、顺序一样而导致方法不能重载。

1     public static void applyVecotr(Vector<Integer> v,Integer i){
2         //泛型里的参数是不能作为区分参数去overload(重载)
3     }
4     public static void applyVecotr(Vector<String> v,Integer i){
5         //泛型里的参数是不能作为区分参数去overload(重载)
6     }

      此时报错 Erasure of method applyVecotr(Vector<Integer>, Integer) is the same as another method in type GenericTest

 

5.通配符?和泛型类型参数代表符号<T>、<K>、<V>等的关系和区别

     在一个代码中通配符?用于泛型代表任何的类型,而<T>、<K>、<V>也是一样功能。但是<T>、<K>、<V>等符号有一个好处,就是可以运用到在整个方法甚至整个类中去,用来区分多个泛型。表示<T>所代表的符号都是同一个类型的参数,而<K>是另外的一种参数。但是对于泛型参数则需要在方法或者类的开头声明,而通配符?则不需要的。

1 public class DiffGenericParameter<K,V> {
2     
3     private K k;//代表K为key值
4     private V v;//代表V为value值
5     public DiffGenericParameter(K k,V v){
6         this.v = v;
7         this.k = k;
8     };
9 }

 

三、擦除与其解决方法


      在《java编程思想》中对擦除的核心动机做出了解释:它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。在理想情况下,当所有事物都可以同时被泛化时,我们就可以专注于此。在现实中,即使程序员只编写泛化代码,他们也必须处理在Java SE5之前编写的非泛型类库。那些类库的作者可能从没有想过要泛化它们的代码,或者可能刚刚开始接触泛型。

      因此Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义;而且还要支持迁移兼容性,使得类库按照它们自己的步调变为泛型的,并且当某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。通过允许非泛型代码与泛型代码共存,擦除使得这种向着泛型的迁移成为可能。

      尽管如此,但是我们还是可以通过反射得到一些蛛丝马迹的。通过反射的getGenericParameterTypes去拿到泛型中的参数类型。

 1 package Generic;
 2 
 3 import java.lang.*;
 4 import java.util.*;
 5 
 6 public class GenericTest {
 7 
 8     public static void main(String[] args) throws Exception {
 9         
10         /**
11          * 通过反射去获取方法的泛型参数化类型,通过其他方式是无法知道泛型的参数类型,因为在泛型中
12          * 编译后去掉了泛型化。
13          */
14         Method method = GenericTest.class.getMethod("applyVector", Vector.class,Map.class);
15         Object[] ParameterTypes = method.getParameterTypes();
16         //System.out.println(ParameterTypes);
17         /*for(Object ParameterType : ParameterTypes ){
18             System.out.println(ParameterType);
19         }*/
20         ParameterizedType type = (ParameterizedType) method.getGenericParameterTypes()[0];
21         System.out.println("type.getRawType()  = "+type.getRawType());
22         System.out.println("type.getActualTypeArguments = "+type.getActualTypeArguments()[0]);
23     }
24     
25     public static void applyVector(Vector<Date> v, Map<String,String> map){
26         
27     }
28 
29 }

   得到的结果是:

type.getRawType()  = class java.util.Vector
type.getActualTypeArguments = class java.util.Date

      由于有了这个反射能得到泛型的参数类型后,还是挺兴奋的。但是我们不能经常去使用反射,这毕竟还是一个耗时耗力的事情。所以在泛型的很多代码编写上就有新的尝试了。

  • 泛型的定义加上了上边界和下边界
  • 加入Class类型的代码

   1.泛型的定义加上了上边界和下边界 

      因为擦除移除了类型信息,如果使用无界泛型参数调用的方法只是那些可以用Object调用的方法。如果能够将某个参数限制为某个类型子集,我们就能调用这个类型子集去调用方法了。所以在Java泛型中用了extends这个关键字,但是此时extends和在普通情况下面所具有的意义是完全不同的。

     

 1 package Generic;
 2 
 3 import java.awt.Color;
 4 
 5 interface HasColor{java.awt.Color getColor();}
 6 
 7 class Colored<T extends HasColor>{
 8     T item;
 9     Colored(T item){ this.item = item;}
10     T getItem(){
11         return item;
12     }
13     java.awt.Color color(){return item.getColor();}
14 }
15 
16 class Dimension{public int x , y, z;}
17 
18 //this won't work -- class must be first, then interface;
19 //calss coloredDimension<T extends HasColor & Dimension>
20 
21 class ColoredDimension<T extends Dimension & HasColor>{
22     T item;
23     ColoredDimension(T item){this.item = item;}
24     T getItem(){return item;}
25     java.awt.Color getColor(){return item.getColor();}
26     int getX(){return item.x;}
27     int getY(){return item.y;}
28     int getZ(){return item.z;}
29 }
30 
31 interface Weight{int weight();}
32 
33 class Solid<T extends Dimension & HasColor & Weight>{
34     T item;
35     Solid(T item){this.item = item;}
36     T getItem(){return item;}
37     java.awt.Color color(){return item.getColor();}
38     int getX(){return item.x;}
39     int getY(){return item.y;}
40     int getZ(){return item.z;}
41     int weight(){return item.weight();}
42 }
43 
44 class Bounded extends Dimension implements HasColor ,Weight {
45 
46     @Override
47     public int weight() {
48         // TODO Auto-generated method stub
49         return 0;
50     }
51 
52     @Override
53     public Color getColor() {
54         // TODO Auto-generated method stub
55         return null;
56     }
57     
58 }
59 
60 public class BasicBounds {
61  
62     public static void main(String[] args) {
63         Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
64         System.out.println(solid.weight());
65         System.out.println(solid.color());
66         System.out.println(solid.getY());
67     }
68 
69 }

     所以你会发现,原来extends在泛型使用中不仅仅可以被用来限定上边界class,还可以用来限定上边界interface。而且,在此之后我们可以使用class或者interface中的类型变量或者是方法,这样子我们就可以很好的利用Java提供的这个机制去构建一些泛型关系。

 

2.加入Class类型的代码

 

     既然被擦除了,那就在调用的时候再将这个类型传入就好了。这个也是挺不错的方法之一。

 1 package Generic;
 2 
 3 class ClassAsFactory<T>{
 4     T x;
 5     public ClassAsFactory(Class<T> kind){
 6         try {
 7             x= kind.newInstance();
 8         } catch (InstantiationException e) {
 9             // TODO Auto-generated catch block
10             e.printStackTrace();
11         } catch (IllegalAccessException e) {
12             // TODO Auto-generated catch block
13             e.printStackTrace();
14         }
15     }
16 }
17 
18 class Employee{}
19 
20 public class InstantiateGenericType {
21 
22     public static void main(String[] args) {
23         ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
24         System.out.print("ClassAsFactory<Employee> succeed");
25         ClassAsFactory<Integer>  fi = new ClassAsFactory<Integer>(Integer.class);
26 
27     }
28 }

       发现了没有,在new实例化这个泛型的时候,我们再将这个实例化的类型传入,接着再将这个newInstance就好了。

       下一篇,将介绍泛型用于程序设计中的实例,不错的实例。

 

posted @ 2016-08-31 12:11  墨迹修  阅读(2635)  评论(1编辑  收藏  举报