2015 7 7 Java核心技术卷一 第12章 泛型程序设计

来源:互联网 发布:md5加密java代码解密 编辑:程序博客网 时间:2024/06/03 13:04
第12章 泛型程序设计
使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。

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

在java中增加泛型类之前,泛型程序设计是用继承实现的。ArrayList类只维护一个Object引用的数组:
public class ArrayList
{
    private Object[] elementData;
    ...
    public Object get(int i){...}
    public void add(Object o){...}
}
这样的实现有两个问题。当获取一个值时必须进行强制类型转换。
ArrayList files = new ArrayList();
String filename = (String)files.get(i);
此外这里没有错误检查。可以向数组列表中添加任何类的对象。
files.add(new File(". . ."));
对于这个调用,编译和运行都不会错。然而在其他地方,如果将get的结果强制转换为String类型,就会产生一个错误。

泛型提供了一个很好的解决方案:类型参数(type parameters)。ArrayList类有一个类型参数用来指示元素类型:
ArrayList<String> files = new ArrayList<String>();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象。

编译器也可以很好的利用这个信息。当调用get的时候,不需要进行强制转换,编译器就知道返回值类型为String,而不是Object:
String filenames = files.get(0);
编译器还知道ArrayList<String>中add方法有一个类型为String的参数。这将比使用Object类型的参数安全一些。现在,编译器可以进行检查,避免插入错误类型的对象。例如:
files.add(new File("..."));//can only add String objects to an ArrayList<String>
是无法通过编译的。出现编译错误比类在运行时出现类的强制类型转换异常要好得多。类型参数的魅力在于:使得程序具有更好的可读性和安全性。

12.2 定义简单泛型类
一个泛型类,就是具有一个或多个类型变量的类。
public class Pair<T>
{
    private T first;
    private T second;

    public Pair(){return first;}
    public Pair(T first,T second){this.first = first;this.second = second;}

    public getFirst(){return first;}
    public getSecond(){return second;}

    public void setFirst(T newValue){first = newValue} 
    public void setSecond(T newValue){second = newValue} 
}
Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义
Pair类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,V>{. . .}
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型。例如,
private T first; //use the type variable

注释:类型变量使用大写形式,且比较短,在Java库中,使用变量E表示集合的元素类型,K和V分别表示标的关键字和值的类型。T表示任意类型。

用具体的类型替换类型变量就可以实例化泛型类型,例如:
Pair<String>
可以将结果想象成带有构造器的普通类:
Pair<String>()
Pair<String>(String,String)
和方法:
String getFirst()
String getSecond()
void setFirst(String)
void setSecond(String)
换句话说,泛型类,可看作是普通类的工厂。

Pair.java--
package pair1;

public class Pair<T>
{
    private T first;
    private T second;

    public Pair(){return first;}
    public Pair(T first,T second){this.first = first;this.second = second;}

    public getFirst(){return first;}
    public getSecond(){return second;}

    public void setFirst(T newValue){first = newValue} 
    public void setSecond(T newValue){second = newValue} 
}
PairTest1.java--
public class PairTest1
{
   public static void main(String[] args)
   {
      String[] words = { "Mary", "had", "a", "little", "lamb" };//Mary比a小,因为是大写字母开头
      Pair<String> mm = ArrayAlg.minmax(words);
      System.out.println("min = " + mm.getFirst());
      System.out.println("max = " + mm.getSecond());
   }
}
class ArrayAlg
{
   /**
    * Gets the minimum and maximum of an array of strings.
    * @param a an array of strings
    * @return a pair with the min and max value, or null if a is null or empty
    */
   public static Pair<String> minmax(String[] a)
   {
      if (a == null || a.length == 0) return null;
      String min = a[0];
      String max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<>(min, max);
   }
}
运行结果:
min = Mary
max = little

12.3泛型方法
前面已经定义了一个泛型类。实际上,还可以定义一个带有参数的简单方法。
class ArrayAlg
{
    public static <T> T getMiddle(T... a)
    {
        return a[a.length/2];
    }
}
这个方法是在普通类中定义的,而不是在泛型类中定义的。然而这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意类型变量放在修饰符(public static)的后面,返回类型的前面
泛型方法可以定义在普通类中,也可以定义在泛型类中。

当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String middle = ArrayAlg.<String>getMiddle("John","Q","Public");
在这种情况下(实际也是大多数情况)下,方法调用中可以省略<String>类型参数。编译器有足够的信息能够推断出所调用的方法。它用names的类型(即String[])与泛型类型T[]进行匹配并推断出T一定是String。也就是说,可以调用
String middle =  ArrayAlg.getMiddle("John","Q","Public");

12.4 类型变量的限定
有时,类或方法需要对类型变量加以约束。下面是一个典型例子。我们要计算数组中的最小元素:
class ArrayAlg
{
    publi static <T> T min(T[] a)
    {
        if(a==null || a.length == 0) return null;
        T smallest = a[0];
        for(int i = 1; i< a.length; i++)
            if(smallest.compareTo(a[i] > 0) samllest = a[i];
        return smallest;
    }
}
但这里有一个问题,变量smalllest类型为T,这意味着它可以是任何一个类的对象。怎么才能确信T所属的类有compareTo方法呢?
解决这个问题的方案是将T限制为实现了Comparable接口。可以通过对类型变量T设置 限定(bound)实现这一点。
public static <T extends Comparable> T min(T[] a). . .
现在泛型的min方法只能被实现了Comparable接口的类(如String、Date等)的数组调用。
注意:这里虽然实现的是接口,但是用的关键字是extends

一个类型变量或通配符可以有多个限定,例如:
T extends Comparable & Serializable

程序清单12-2重新编写了一个泛型方法minmax。这个方法计算泛型数组的最大值和最小值,并返回Pair<T>
Pair.java
package pair2;

public class Pair<T>
{
   private T first;
   private T second;

   public Pair() { first = null; second = null; }
   public Pair(T first, T second) { this.first = first;  this.second = second; }

   public T getFirst() { return first; }
   public T getSecond() { return second; }

   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }
}

PairTest2 .java

package pair2;
import java.util.*;
/**
 * @version 1.01 2012-01-26
 * @author Cay Horstmann
 */
public class PairTest2
{
   public static void main(String[] args)
   {
      GregorianCalendar[] birthdays =
         {
            new GregorianCalendar(1906, Calendar.DECEMBER, 9), // G. Hopper
            new GregorianCalendar(1815, Calendar.DECEMBER, 10), // A. Lovelace
            new GregorianCalendar(1903, Calendar.DECEMBER, 3), // J. von Neumann
            new GregorianCalendar(1910, Calendar.JUNE, 22), // K. Zuse
         };
      Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays);
      System.out.println("min = " + mm.getFirst().getTime());
      System.out.println("max = " + mm.getSecond().getTime());
   }
}
class ArrayAlg
{
   /**
      Gets the minimum and maximum of an array of objects of type T.
      @param a an array of objects of type T
      @return a pair with the min and max value, or null if a is
      null or empty
   */
   public static <T extends Comparable> Pair<T> minmax(T[] a)
   {
      if (a == null || a.length == 0) return null;
      T min = a[0];
      T max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<>(min, max);
   }
}
运行结果:
min = Sun Dec 10 00:00:00 CST 1815
max = Wed Jun 22 00:00:00 CST 1910

泛型类型的继承规则
在使用泛型类时,需要了解一些有关继承和子类型的准则。考虑一个类和一个子类,如Employee和Manager.Pair<Manager>是Pair<Employee>的一个子类吗?答案是不是。
无论S与T有什么联系,通常Pair<S>与Pair<T>没有什么联系


12.8通配符类型
固定的泛型类型系统使用起来没有那么令人愉快,Java设计者发明了一种巧妙的解决方案:通配符类型。例如,通配符类型Pair<? extends Employee>
表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair<Manager>,但不是Pair<String>.
假设要编写一个打印雇员对的方法,像这样:
public static void printBuddies(Pair<Employee> p)
{
    Employee first = p.getFirst();
    Employee second = p.getSecond();
    System.out.println(first.getName()+" and"+second.getName() + " are buddies.");
}
正如前面讲到的,不能将Pair<Manager>传递给这个方法,这一点很受限制。解决的方法很简单:使用通配符类型:
public static void printBuddies(Pair<? extends Employee> p)
类型Pair<Manager>是Pair<? extends Employee>的子类型。


12.8.1通配符的超类型限定
通配符限定与类型变量限定十分类似,但是还有一个附加能力,即可以指定一个 超类型限定,如下所示:
? super Manager
这个通配符限制为Manager的所有Manager的所有超类型。
带有超类型限定的通配符行为与12.8节介绍的相反。可以为方法提供参数,但不能使用返回值。例如,
Pair<? super Manager> 有方法
void setFirst(? super Manager)
?super Manager getFirst()
编译器不知道setFirst方法的确切类型,但是可以用任意Manager对象(或子类型,例如Executive)调用它,而不能用Employee对象调用。然而,如果调用getFirst,返回的对象类型就不会得到保证。只能把它赋给一个Object。

下面有一个经理数组,并且把奖金最高和最低的经理放在一个Pair对象中。
public static void minmaxBonus(Manager[] a,Pair<? super Manager> result)
{    
    if(a == null || a.length==0) return;
    Manager min = a[0];
    Manager max = a[0];
    for(int i = 1; i < a.length; i++)
    {
        if(min.getBonus()>a[i].getBonus()) min = a[i];
        if(max.getBonus()<a[i].getBonus()) max = a[i];
   }
    result.setFirst(mix);
    result.setSecond(max);
}
直观地说,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

12.8.3通配符捕获
要求编写一个交换一个pair元素的方法:
public static void swap(Pair<?> p)
通配符不是类型变量,因此不能在编写代码中使用“?”作为一种类型。也就是说,下述代码是非法的。
? t = p.getFirst();//ERROR
p.setFirst(p.getSecond);
p.setSecond(t);
这是一个问题,因为在交换的时候必须临时保存第一个元素。幸运的是,这个问题有一个有趣的解决方案。我们可以写一个辅助方法
swapHelper,如下所示
public static<T> void swapHelper(Pair<T> p)
{
    T t = p.getFirst();
    p.setFirst(p.getSecond());
    p.setSecond(t);
}
注意,swapHelper是一个泛型方法,而swap不是,它具有固定的Pair<?>类型的参数。
现在可以由swap调用swapHelper:
public static void swap(Pair<?> p){swapHelper(p);}
在这种情况下,swapHelper方法的参数T捕获通配符。它不知道是哪种类型的通配符,但是这是一个明确的类型,并且<T>swapHelper
只有在T指出类型时才有明确的含义。
0 0