zoukankan      html  css  js  c++  java
  • Java 泛型 详解

    一、什么是泛型

      本质而言,泛型指的是参数化的类型。参数化的类型的重要性是:它能让你创建类、接口和方法,由它们操作的数据类型被指定为一个参数。操作参数化类型的类、接口或方法被称为泛型,如泛型类或泛型方法。着重理解的是,通过对Object类型的引用,Java总是可以创建一般化的类、接口和方法,因为Object是所有其他类的超类,一个Object引用可以是任何类型的对象,因此,在泛型出现以前的代码,一般化的类、接口和方法是使用Object引用来操作不同类型的对象(可不可以说是多态?),这样做的问题是不能保证类型安全。泛型添加了它们正缺乏的类型安全,同时还简化了过程,因为不需要显示地在Object和实际操作的数据类型之间进行强制转换。利用泛型,所有的强制转换都是自动和隐含的,因此,泛型扩展了代码复用的能力,而且既安全又方便。

    下面是一个简单的泛型例子:

      

     1 package com.hujianjie.demo;
     2 
     3 //一个简单的泛型类
     4 class Gen<T> {
     5     T ob;
     6 
     7     Gen(T o) {
     8         this.ob = o;
     9     }
    10 
    11     T getOb() {
    12         return ob;
    13     }
    14 
    15     void showType() {
    16         System.out.println("Type of T is" + ob.getClass().getName());
    17     }
    18 }
    19 
    20 public class GenDemo {
    21     public static void main(String args[]) {
    22         // 定义一个Integer类型
    23         Gen<Integer> iob;
    24         iob = new Gen<Integer>(88);//此处的<Integer>不能省,因为定义的类型是Integer类型,所以new返回的引用也必须是Integer,否则就会发生编译错误,也即泛型安全
    25         iob.showType();
    26         System.out.println("value:" + iob.getOb());
    27         // 定义一个String 类型
    28         Gen<String> sob;
    29         sob = new Gen<String>("Generics Test!");
    30         sob.showType();
    31         System.out.println("value:" + sob.getOb());
    32     }
    33 
    34 }

    首先注意,类型Integer被放在Gen后面的一对尖括号内,在本例中,Integer作为类型变元被传递给Gen的类型参数T,这就创建了Gen的一个版本,在该版本中,所有对T的引用都被转换为对Integer的引用,因此,对于这个声明,ob成为Integer类型,且getob()的返回类型也是Integer类型。String类型也是类似分析。特别主意Java编译器并不创建实际的不同Gen版本或者任何其他的泛型类,尽管将其想象成这样是有帮助理解的,但实际上并不是这样发生的。相反,编译器删除所有的泛型类型信息,并进行必要的强制转换,使代码在行为上就好像创建了Gen的一个特定版本一样,因此,程序中只实际存在一个Gen版本。删除泛型类型信息的过程称为擦拭(erasure)。

    二、泛型的特点。

      1、泛型只使用对象。声明泛型类型的一个实例时,传递给类型参数类型变元必须是类类型,不能使用基本类型,如int或char。

      2、泛型类型的差异基于类型变元。理解泛型类型的关键在于:对某种特定版本的泛型类型的引用,与对同一种泛型类型的另一种版本的引用是类型不兼容的。例如:上面代码中的 iob=sob 是错误的,虽然iob和sob都属于Gen<T>类型,由于它们的类型参数不同,它们引用的也是不同的类型。这就是泛型添加的类型安全和错误预防的原理所在。

      3、泛型能在编译时捕捉类型不匹配错误,因此能够创建类型安全的代码。通过泛型,原来的运行时错误现在成了编译错误,这是一个重要的提高。

    三、泛型的一般形式

      声明泛型的语法: class class-name<type-param-list>{ . . . }

      声明一个泛型引用的语法: class-name<type-arg-list> var-name = new class-name <type-arg-list>(cons-arg-list);

    四、泛型的类型

      1、有界类型。前面简单的例子中,类型参数可以替换为任意的类类型,多数情况下这一点是好的,但是有时候需要对可以传递给类型参数的类型做出限制,于是出现了有界类型。在指定一个类型参数的时候,可以创建一个上界,声明所有的类型变元都必须从超类派生,实现方法是通过在指定类型参数时使用一个extends子句,如下所示:

      < T extends superclass >

    这行代码规定T只能由superclass或它的子类来替换。因此,superclass 提供了一个包括本身在内的上限。

    除了用类类型作为有界之外,还可以使用接口类型,实际上,可以将多个接口指定为界,而且,界可以包括一个类类型和多个接口,此时,必须首先指定类类型,当界包含一个接口类型时,只有实现这个接口的类型变元才是合法的,若指定的界包含一个类和一个或者多个接口,则应使用 & 运算符连接它们。例如:

      class Gen < T extends MyClass & MyInterface > { . . . }

    其中, T由称为MyClass 的类和称为MyInterface的接口界定,因此,任何传递给T的类型变元,都必须是MyClass 的一个子类,且实现MyInterface。

      2、通配符变元。通配符变元用 “?”指定,它代表一个未知类型

     1 package com.hujianjie.demo;
     2 
     3 /*
     4  * 定义一个有界泛型类
     5  */
     6 class Stats<T extends Number>{
     7     T[] nums;
     8     Stats(T [] o){
     9         nums=o;
    10     }
    11     double average(){
    12         double sum = 0.0;
    13         for(int i =0;i<nums.length;i++){
    14             sum+=nums[i].doubleValue();
    15         }
    16         return sum/nums.length;
    17     }
    18     /*
    19      * 这里使用通配符变元,如果是Stats<T>,两个比较的对象类型必须一致,否则,
    20      * 会出现泛型安全错误,所以使用通配符,在不同类型间进行比较
    21      */
    22     boolean sameAvg(Stats<?> ob){
    23         if(this.average()==ob.average())
    24             return true;
    25         return false;
    26     }
    27 }
    28 
    29 public class WildCardDemo {
    30 
    31     /**
    32      * @param args
    33      */
    34     public static void main(String[] args) {
    35 
    36         Integer inums[]={1,2,3,4,5};
    37         Stats<Integer> iob = new Stats<Integer>(inums);
    38         System.out.println("iob average is :"+iob.average());
    39         Double dnums[]={1.1,2.2,3.3,4.4,5.5};
    40         Stats<Double> dob = new Stats<Double>(dnums);
    41         System.out.println("dob average is :"+dob.average());
    42         Float fnums[]={1.0f,2.0f,3.0f,4.0f,5.0f};
    43         Stats<Float> fob = new Stats<Float>(fnums);
    44         System.out.println("dob average is :"+fob.average());
    45         if(iob.sameAvg(dob))
    46             System.out.println("iob and dob are the same!");
    47         else
    48             System.out.println("iob and dob are not the same!");
    49         if(fob.sameAvg(iob))
    50             System.out.println("iob and fob are the same!");
    51         else
    52             System.out.println("iob and fob are not the same!");
    53     }
    54 
    55 }

      3、有界通配符。通配符变元的界定方式与类型参数大体相同,当创建一个操作与类层次的泛型时,有界通配符尤其重要。一个有界通配符可以为类型变元指定一个上界或一个下界。如:在坐标系里,二维、三维、四维坐标,如果要限定在一个三维坐标中,则可以使用如下方式:showXYZ(Coords< ? extends ThreeD> ob).

      4、泛型构造函数。构造函数也可以是泛型,即使它们的类也不是泛型。

      5、泛型接口。泛型接口的指定方法与泛型类相似。

     1 package com.hujianjie.demo;
     2 /*
     3  * 定义一个泛型上界接口,所传对象必须实现Comparable接口,可以比较大小
     4  */
     5 interface MinMax<T extends Comparable<T> >{
     6     T min();
     7     T max();
     8 }
     9 /*
    10  * 定义实现接口类时注意:MyClass声明参数类型 T 后要将它传递给MinMax,因为MinMax
    11  * 需要一个实现Comparable的类型,实现类必须指定同样的界
    12  * 此界一旦建立,就无需在implements字句中再次指定。
    13  * class MyClass <T extends Comparable<T> > implements MinMax< T extends Comparable<T> >
    14  * 这样指定是错误的
    15  */
    16 class MyClass <T extends Comparable<T> > implements MinMax<T>{
    17     T [] array;
    18     MyClass (T []o){
    19         array=o;
    20     }
    21     public T min(){
    22         T minValue =array[0];
    23         for(int i=0;i<array.length;i++){
    24             if(minValue.compareTo(array[i])>0)
    25                 minValue=array[i];
    26         }
    27         return minValue;
    28     }
    29     public T max(){
    30         T maxValue = array[0];
    31         for(int i=0;i<array.length;i++){
    32             if(maxValue.compareTo(array[i])<0){
    33                 maxValue=array[i];
    34             }
    35         }
    36         
    37         return maxValue;
    38         
    39     }
    40 }
    41 
    42 public class GenIFDemo {
    43 
    44     /**
    45      * @param args
    46      */
    47     public static void main(String[] args) {
    48         Integer inums[]={3,6,2,8,7};
    49         Character chs[]={'b','r','c','d'};
    50         MyClass<Integer> iob = new MyClass<Integer>(inums);
    51         MyClass<Character> cob = new MyClass<Character>(chs);
    52         System.out.println("Max values in inums is "+iob.max());
    53         System.out.println("Min values in inums is "+iob.min());
    54         System.out.println("Max values in chs is "+cob.max());
    55         System.out.println("Min values in chs is "+cob.min());
    56     }
    57 
    58 }

    注意:如果一个类实现了一个泛型接口,则此类必须也是泛型,或者至少它接受传递给接口的类型参数。如果MyClass是一个没有引用泛型的类,只是实现了泛型接口如下声明,编译器会报错。

    1 class MyClass implements MinMax< T >{ . . . }

    另外,如果上面的情况稍作改变就不同,如果类实现一个泛型接口的特定类型,则此实现类不需要是泛型的

    1 class MyClass implements MinMax< Integer>{ . . . }

      6、泛型类层次。和非泛型类一样,泛型类也可以是类层次中的一部分,因此,泛型类可以作为超类或子类。泛型类层次与非泛型类层次的关键区别在于:泛型类层次中,全部子类必须将泛型超类需要的类型变元沿层次向上传递。这与构造函数变元必须沿层次向上传递的方式类似。

     1 package com.hujianjie.demo;
     2 class Gen <T>{
     3     T ob;
     4     Gen (T o){
     5         ob =o;
     6     }
     7     T getOb(){
     8         return ob;
     9     }
    10 }
    11 class Gen2<T> extends Gen<T>{
    12     Gen2(T o){
    13         super(o);
    14     }
    15 }
    16 
    17 public class HierDemo {
    18 
    19     /**
    20      * @param args
    21      */
    22     public static void main(String[] args) {
    23         Gen<Integer> iob = new Gen<Integer>(88);
    24         Gen2<Integer> iob2 = new Gen2<Integer>(99);
    25         Gen2<String> str2 = new Gen2<String>("Generics Test");
    26         if(iob instanceof Gen<?>)
    27             System.out.println("iob is instance of Gen");
    28         //iob 向下转型,将不是Gen2对象的类型
    29         if(iob instanceof Gen2<?>)
    30             System.out.println("iob is instance of Gen2");
    31         if(iob2 instanceof Gen<?>)
    32             System.out.println("iob2 is instance of Gen");
    33         if(iob2 instanceof Gen2<?>)
    34             System.out.println("iob2 is instance of Gen2");
    35         if(str2 instanceof Gen<?>)
    36             System.out.println("str2 is instance of Gen");
    37         if(str2 instanceof Gen2<?>)
    38             System.out.println("str2 is instance of Gen2");
    39         /*
    40          * 以下这些行不能被编译的原因是:它们试图比较iob2与Gen2的一个特定
    41          * 类型,本例中是Gen2<Integer>.记住,在运行时无法得到泛型类型信息,因此
    42          * 对instanceof来说,无法判断iob2是否为Gen2<Integer>的一个实例
    43          */
    44     //    if(iob2 instanceof Gen2<Integer>)
    45     //        System.out.println("str2 is instance of Gen2");
    46         
    47     }
    48 
    49 }

     结果:

    1 iob is instance of Gen
    2 iob2 is instance of Gen
    3 iob2 is instance of Gen2
    4 str2 is instance of Gen
    5 str2 is instance of Gen2

    五、泛型擦拭

      大体而言,擦拭的工作方式是这样的:当编译Java代码时,全部泛型类型信息被移去(擦拭)。这意味着使用它们的界定类型来替换类型参数,如果没有显式地指定界,则界定类型是Object,然后运用适当得强制转换(由类型变元决定),以维持与类型变元指定的类型的兼容。编译器也会强制这种类型兼容。对泛型来说,这种方法意味着在运行时不存在类型参数,它们仅是一种源代码机制。在编译时所有的类型参数都被擦拭掉了,在运行时,只有原始类型实际存在。

  • 相关阅读:
    Python 自学笔记(二)
    Python 自学笔记(一)
    java.net.MalformedURLException: unknown protocol: 异常
    选择排序精简理解
    JAVA基于File的基本的增删改查
    Oracle常用操作表结构的语句
    jQuery
    基于jquery的ajax方法封装
    javascript运算符——条件、逗号、赋值、()和void运算符 (转载)
    javascript 闭包
  • 原文地址:https://www.cnblogs.com/hoojjack/p/4749090.html
Copyright © 2011-2022 走看看