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


           概述

           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就好了。

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

  • 相关阅读:
    无线安全课堂:手把手教会你搭建伪AP接入点
    转载——开阔自己的视野,勇敢的接触新知识
    关于系统架构的一些总结
    MessageBox.Show()如何换行
    不患寡而患不均
    由CHAR(2)引发的BUG
    DataRow.RowState 属性
    C# 使用TimeSpan计算两个时间差
    利用反射调出其他项目的界面
    DB2 中将date类型的转换成timestamp
  • 原文地址:https://www.cnblogs.com/kgrdomore/p/5823733.html
Copyright © 2020-2023  润新知