【codejava】第八版:第十二章 泛型程序设计[001] [20180106]

时间:2023-01-01 11:45:34

12.1 为什么要使用泛型程序设计

generic programming意味着编写的代码可以被很多不同类型的对象所重用,例如,我们并不希望为聚集String和File而编写不同的类,实际上也不需要这样做,因为一个ArrayList可以聚集任何类型的对象,这是一个泛型程序设计的实例

    java SE 5.0之前 java泛型程序设计是用继承实现的 ArrayList只维护一个Obj引用的数组 这样的实现有两个问题
     1当获取一个值时必须进行强制类型转换,此外这里没有检查错误,可以向数据列表中添加任何类的对象 
        ArrayList list  =  new ArrayList();
        list. add( "lee");
         System. out.println((String)list. get( 0));
        list. add( new File( "..."));
对于这个调用 编译和运行都不会出错,然而在其他地方,如果将get的结果强制类型转换为String类型 就会产生一个错误

泛型提供了一个更好的解决方案 :类型参数(type parameters),ArrayList类有一个类型参数用来指示元素的类型
ArrayList <String > list  =  new ArrayList <String >();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象 编译器也可以很好的利用这个信息,当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String而不是Obj。
    编译器还知道list中add方法有一个类型为String的参数 这将比使用Obj类型的参数安全一些,现在,编译器可以进行检查,避免插入错误类型的对象
list. add( new File( "..."));
这时是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转转异常要好得多,类型参数的魅力在于,使得程序具有更好的可读性和安全性.

         泛型程序设计划分为3个熟练级别,基本级别是,仅仅使用泛型类--典型的是像ArrayList这样的集合-不必考虑他们的工作方式与原因,大多数应用程序员将会停留在这一级别上,知道出现了什么问题(书中到此没有详细讲明哪3个级别)

12.2 简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个类型变量的类
public  class Pair <T > {
  
     public Pair() {
         super();
     }
     public Pair(T first, T second) {
         super();
         this.first  = first;
         this.second  = second;
    }
     public T getFirst() {
         return first;
    }
     public T getSecond() {
         return second;
    }
     public  void setFirst(T first) {
         this.first  = first;
    }
     public  void setSecond(T second) {
         this.second  = second;
    }
     private T first;
     private T second;
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面,泛型类可以有多个类型变量,例如,可以定义pair类,其中第一个域和第二个域使用不同的类型;
public  class Pair <T, U >
类型变量使用大写形式,且比较短,这是很常见的,在java库中,使用变量 E表示集合元素的类型, KV分别表示表的关键字的键值对(Key-Value)的类型, T(需要时还可以用临近的字母U和S)表示“任意类型”
public  class PairTest1 {
    
     public  static  void main(String[] args) {
        String[] words  = { "Mary", "had", " a", " little", " lamb"};
        Pair <String > pair  = PairAlg.minmax(words);
        System.out.println( "min:" +pair.getFirst());
        System.out.println( "max:" +pair.getSecond());
    }
}
class PairAlg{
     public  static Pair <String > minmax(String[] arr){
         if (arr  == null  || arr.length  ==  0) {
             return null;
        }
        String max  = arr[ 0];
        String min  = arr[ 0];
         for( int i = 0; i <arr.length; i ++){
             if(max.compareTo(arr[i])  <  0){
                max  = arr[i];
            } if(min.compareTo(arr[i])  >  0){
                min  = arr[i];
            }
        }
         return  new Pair <String >(min, max);
    }
}

12.3 泛型的方法
class PairAlg{
     public  static  <T > T getMiddle(T[] arr){
         return arr[arr.length  /  2];
    }
}
这个方法是在普通类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(public static)的后面,返回类型的前面.泛型方法可以定义在普通类中,也可以定义在泛型类中. 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
            String[] words  =  { "Mary" , "had" , " a" , " little" , " lamb" };
        Integer[] nums  = { 256, 8, 9};
        System.out.println(PairAlg. <Integer >getMiddle(nums)); //注意尖括号位置
        System.out.println(PairAlg. <String >getMiddle(words)); //注意尖括号位置
在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型

12.4 类型变量的限定

有时,类或方法需要对类型变量加以约束,下面这个我们计算数据中的最小元素

public  static  <extends Comparable > T min(T[] arr){
         if (arr  == null  || arr.length  ==  0) {
             return null;
        }
        T smallest  = arr[ 0];
         for( int i = 0; i <arr.length; i ++){
             if(smallest.compareTo(arr[i])  >  0){
                smallest  = arr[i];
            }
        }
         return smallest;
    }

public static <extends Comparable> 

    实际上Comparable接口本身就是一个泛型类型,目前,我们忽略其复杂性以及编译器产生的警告,在12.8节有介绍怎么适当地使用类型参数,或许大家会感到奇怪,在此为什么使用关键字extends,Comparable不是一个接口吗?

 <extends Bounding Type> 

表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口,选择关键字extends的原因是更接近子类的概念,并且JAVA的设计者也不打算在语言中再添加一个新的关键字 

    一个类型变量或通配符可以有多个限定,例如

 <extends Comparable & Serializable> 

public  class PairTest2 {
     public  static  void main(String[] args) {
        GregorianCalendar[] birthdays  = {
                 new GregorianCalendar( 1988, Calendar.JUNE,  15),
                 new GregorianCalendar( 1970, Calendar.APRIL,  6),
                 new GregorianCalendar( 1964, Calendar.FEBRUARY,  31),
                 new GregorianCalendar( 1990, Calendar.NOVEMBER,  23)
        };
        Pair <GregorianCalendar > gregorianCalendar  = minmax(birthdays);
        System.out.println( "min:" +gregorianCalendar.getFirst());
        System.out.println( "max:" +gregorianCalendar.getSecond());
    }
    
     public  static  <extends Comparable  & Serializable > Pair <T > minmax(T[] arr){
         if (arr  == null  || arr.length  ==  0) {
             return null;
        }
        T max  = arr[ 0];
        T min  = arr[ 0];
         for( int i = 0; i <arr.length; i ++){
             if(max.compareTo(arr[i])  <  0){
                max  = arr[i];
            } if(min.compareTo(arr[i])  >  0){
                min  = arr[i];
            }
        }
         return  new Pair <T >(min, max);
    }
}


12.5 泛型代码和虚拟机 



虚拟机没有泛型类型对象-所有对象都属于普通类,在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为1.0虚拟机上运行的类文件

    无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名. 擦除(erased)类型变量,并替换为限定类型(无限定的变量用Obj)
public  class Pair <T > {
  
     public Pair() {
         super();
     }
     public Pair(T first, T second) {
         super();
         this.first  = first;
         this.second  = second;
    }
     public T getFirst() {
         return first;
    }
     public T getSecond() {
         return second;
    }
     public  void setFirst(T first) {
         this.first  = first;
    }
     public  void setSecond(T second) {
         this.second  = second;
    }
     private T first;
     private T second;
}
上面的Pair<T>原始类型如下
public  class Pair {
  
     public Pair() {
         super();
     }
     public Pair(Object first, Object second) {
         super();
         this.first  = first;
         this.second  = second;
    }
     public Object getFirst() {
         return first;
    }
     public Object getSecond() {
         return second;
    }
     public  void setFirst(Object first) {
         this.first  = first;
    }
     public  void setSecond(Object second) {
         this.second  = second;
    }
     private Object first;
     private Object second;
}
     因为T是一个无限定的类型,所以直接用Obj来进行替换  的出来的结果是一个普通的类,就像泛型引入java语言之前已经实现的那样
     在程序中可以包含不同类型的Pair,例如Pair<String>,Pair<GregorianCalendar>,而擦除类型后就变成原始的Pair类型了

   原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Obj替换

12.5.1 翻译泛型表达式
      当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,例如,下面这个语句序列
       Pair <GregorianCalendar > gregorianCalendar  = minmax(birthdays);
       GregorianCalendar min  = gregorianCalendar. getFirst();
    擦除getFirst()返回类型后将返回Obj类型,编译器自动插入GregorianCalendar的强制类型转换,也就是说,编译器吧这个方法调用翻译为两条虚拟机指令:
    .对原始方法 Pair.getFirst()的调用
    .将返回的Obj类型强制转换为 gregorianCalendar

   当存取一个泛型域时也要插入强制类型转换,假设Pair类的fiirst域和second域都是公有的(也许这不是一种良好的编程习惯,但在java中是合法的).表达式:
       GregorianCalendar min  = gregorianCalendar. first();
  也会在结果字节码中插入强制类型转换.

12.5.2 翻译泛型方法 (12.5.2 & 12.5.3有点理解不了 所以都没有详细做笔记 以后再深入研究)
  类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法

是一个完整的方法族,而擦除类型之后,只剩下一个方法

12.5.3 调用遗留代码

12.6 约束和局限性

在下面几节中,将阐述使用java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的.
   
12.6.1 不能用基本类型实例化参数 没有Pair<double>只有Pair<Double>

12.6.2 运行时类型查询只适用于原始类型
    虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型( 暂时跳过这一章,用的少后面不好理解,以后攻破)