Java 泛型

Sang Shin, sang.shin@sun.com, Sun Microsystems, www.javapassion.cn


Java 2 平台标准版(J2SE 平台)5.0 在 Java 编程语言中引入了一些扩展。其中一个就是泛型的引入。泛型允许您在类型之上进行抽象。最常见的例子比如集合层级中的容器类型。在 J2SE 平台 5.0 发布前,在从集合中提取元素时,您必须将这个元素转换为该集合可存储元素的类型。这种方式既不方便,也不安全。编译器不会检查所转换的元素是否和集合类型保持一致,因此该转换会在运行时以失败告终。泛型为集合类型与编译器之间的通信提供了途径,这样便可在编译时对类型转换进行检查。知道集合的元素类型之后,编译器就能检查出您使用的集合是否一致,也能够对从集合中取出的值应用正确的转换。在本动手实验室中,您将学习如何通过在 NetBeans 4.0 软件的源编辑器中编写代码行来使用泛型,该编辑器会在发生编译错误时立即通知您。如果可能的话,也会介绍产生特定编译错误的原因。


预计时间:90 分钟


软件需求

开始之前,需要在您的计算机中安装以下软件。


变更记录


实验室练习


练习 1:使用支持类型参数的 Generic 类


在本练习中,您将通过向 Main.java 添加代码行来了解泛型的基本概念。也将解释为什么一些代码有效,而另一些导致编译错误。然后将从技术角度进行解释。

(1.1)在编译时通过泛型进行类型检查


0. 如果还没有启动 NetBeans IDE,请启动它。

1. 创建一个新的 NetBeans 项目


图 1.10:创建一个新项目
2. 根据代码 1.11 修改由 IDE 生成的 GenericExample1.java,查看是否遇到任何编译错误。仔细阅读该代码,需要特别注意粗体部分。
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class GenericsExample1 {
  
    public static void main(String[] args){
      
        // Notice the type declaration <Integer> for the variable ai.
        // It specifies that this is not just an arbitrary ArrayList,
        // but a ArrayList of Integer, denoted as ArrayList<Integer>.
      
        ArrayList<Integer> ai = new ArrayList<Integer>(10);
        ai.add(0, new Integer(20));
        ai.add(1, new Long(1234));
        ai.add(2, new String("xyz"));
        ai.add(3, new Object());
        Integer i = ai.get(0);
        String s = ai.get(0);
        Object o = ai.get(0);
      
        List<String> ls = new ArrayList<String>(10);
        ls.add(0, new String("abc"));
        ls.add(1, new Integer(2));
        ls.add(2, new Date());
      
        List<Object> lo = new ArrayList<Object>(10);
        lo.add(0, new Integer(20));
        lo.add(1, new Long(1234));
        lo.add(2, new String("xyz"));
        lo.add(3, new Object());
    }
  
}
代码 1.11:GenericExample1.java

3. 注意到一些代码行出现编译错误,如下图 1.12 所示。


图 1.12:编译错误

4. 阅读以下解释,理解为什么一些代码行出现编译错误,而另一些没有。
第 14 行:向一个 Integer 类型 ArrayList 添加一个 Integer 对象条目应该和期望的一样没什么问题。第 15 行、16 行、17 行(出现红 x 框的代码行):这三行代码引起这样的编译错误 —— cannot find symbol, symbol: method add(..)。生成这些编译器错误是因为 Long、String 和 Object 类型既不是 Integer 类型也不是 Integer 类型的子类型。在 J2SE 5.0 之前,ClassCastException 将在运行时(如果您正将提取出的对象转换为一个不兼容的类型)而不是编译时发生。而在这里通过使用泛型,在编译时检查出了问题。

第 18 行:请注意,您不必像在 J2SE 5.0 之前那样将类型转换 Integer 类型。这是因为编译器知道 ArrayList 是 <Integer> 只包含 Integer 类型,没必要将一个从 Integer 类型的 ArrayList 中获取的元素转换为 Integer 类型。
第 19 行(出现红 x 框的代码行):编译器生成编译错误是因为 String 不是 Integer 类型。

第 20 行:这行代码有效是因为 Integer 类型是 Object 类型(意味着 Integer 类型是 Object 类型的子类型)。

第 22 行:当创建一个 ArrayList 类的实例时,也会指定一个类型参数,<String>,告诉编译器 ArrayList 只用于保存 String 类型的对象。(String 是一个 final 类,所以它不含任何子类。)

第 23 行:添加一个 String 条目到 String 类型的 ArrayList 中是有效的。
第 24 行、25 行(出现红 x 框的代码行):这两行代码引起这样的编译错误 —— cannot find symbol, symbol: method add(..)。生成这样的编译错误是因为 Integer 和 Date 既不是 String 类型也不是它的子类型。

第 27 行:当创建一个 ArrayList 类的实例时,可以指定一个类型参数 <Object>,来告诉编译器该 ArrayList 只用于保存 Object 类型或其子类型。
第 28 行、29 行、30 行、31 行:由于 Integer、Long、String 和 Object 类型都是 Object 类型或其子类型,将这些类型添加到 Object 的 ArrayList 应该是有效的。

5.  针对您自己的练习,通过临时编写一些代码做做实验,看看它们是否像您预期的那样运行。代码 1.14 提供了一段样例代码。
      //
      // Invoke various methods of a collection
      //
      List<Number> ln2 = new Vector<Number>(20);     // Right click this line and select Fix Imports (Alt+Shift+F)
      ln2.add(0, new Integer(3));
      ln2.add(1, new Long(1000L));
      String s2 = new String("passion");
      ln2.add(s2);
      Number n2 = ln2.get(0);
      Integer i2 = ln2.get(0);
      Boolean b2 = ln2.contains(new Integer(3));
      Boolean b3 = ln2.contains(s2);
      System.out.println("b2="+b2);
      System.out.println("b3="+b3);

      //
      //  Try to add an ArrayList of Integer to an ArrayList of Number
      //
      ArrayList<Integer> ai2 = new ArrayList<Integer> (10);
      ai2.add(new Integer(5));
     
      ln2.addAll(ai2);

      //
      //  Try to add an ArrayList of String to an ArrayList of Number
      //
      ArrayList<String> as2 = new ArrayList<String>(10);
      as2.add(new String("adventure"));

      ln2.addAll(as2);

      for (Number n: ln2){
          System.out.println("number "+ n);
      }  
代码 1.14:样例代码

                                                                                                              返回练习顶部



结束语


在本练习中,全面展示了集合类 ArrayList<E> 的泛型定义。您也了解到 <E> 为泛型化的 ArrayList 类指定正规的类型参数。您也了解到如何通过替换正规类型参数 <E> 来调用泛型版的 ArrayList 类,而这一替换是通过具体的类型参数来实现的,如 <Integer>、<String> 或 <Object>。


                                                                                                                  返回顶部


练习 2:泛型和子类型

在本练习中,您将了解泛型的一个显著的行为,您也许要花些时间来适应它。最基本地,您将了解为什么 Java 编译器不允许以下代码而生成编译错误。

ArrayList<Object> ao = new ArrayList<Integer>();


(2.1)子类型实验


1. 创建一个新的 NetBeans 项目
2. 根据代码 2.11 修改由 IDE 生成的 GenericsSubtyping.java。仔细阅读该代码,需要特别注意粗体部分。
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class GenericsSubtyping {
   
    public static void main(String[] args){
       
        // These should work
        ArrayList<Integer> ai = new ArrayList<Integer>();
        ArrayList<String> as = new ArrayList<String>();
        ArrayList<Object> ao1 = new ArrayList<Object>();
       
// There is no inheritance between type arguments
        ArrayList<Object> ao2 = new ArrayList<String>();
        ArrayList<Object> ao3 = new ArrayList<Integer>();
       
       
// There is still  inheritance relationship between classes
        List<String> ls = new ArrayList<String>();
        List<Object> lo = new ArrayList<String>();
       
       
// There is still  inheritance relationship between elements in a collection object
        List<Number> ln1 = new Vector<Number>();
        List<Number> ln2 = new Vector<Integer>();
        List<Number> ln3 = new ArrayList<Long>();
       
    }
   
}
代码 2.11:GenericsSubtyping.java

3. 注意到一些代码行出现了编译错误,如下图 2.12 所示。图

4. 通过阅读以下解释,理解为什么有些代码行出现编译错误而另一些没有。
第 9 行:当创建一个 ArrayList 类的实例时,您也指定了一个类型参数 <Integer>,来告诉编译器 ArrayList 只用于保存 Integer 类型或其子类型的对象。我们将其称为 ArrayList<Integer>,即 Integer 类型的 ArrayList。第 9 行、10 行、11 行:我们在练习 2 中了解到,当创建一个 ArrayList<E> 类的实例时,也会指定一个类型参数来告诉编译器 ArrayList 只用于保存特定类型或其子类型的对象,例如,第 9 行中的 ArrayList<Integer>,  第 10 行中的 ArrayList<String> 和第 11 行中的 ArrayList<Object>,换言之,ArrayList<Object> 能保存 Object 类型或其子类型的对象。
第 12 行、13 行(出现红 x 框的代码行):这两行代码引起编译错误,例如,第 12 行  incompatible types, found: java.util.ArrayList<java.lang.String> required: java.util.ArrayList<java.lang.Object>。


<学习要点>  正如明确指出的那样,您不能把一个 ArrayList<String> 实例赋值给一个 ArrayList<Object> 类型的变量。您也不能将一个 ArrayList<Integer> 实例赋值给一个 ArrayList<Object> 类型的变量。换言之,String 类型的 ArrayList 不是 Object 类型的 ArrayList,Integer 类型的 ArrayList 也不是 Object 类型的 ArrayList。这和我们对 00 概念的理解在直觉上是相反的,但这是我们要记住的泛型的一个非常重要的方面。Gilad Bracha(JSR-14 规范的带头人)在他的 Java Programming Language (Chapter 3: Generics and Subtyping) [2] 中解释了为什么这样的赋值在泛型中不被允许。从根本上讲,如果允许这样的分配,将有可能在运行时抛出 ClassCastException,而这和泛型的"类型安全"原则相左。让我们来看一段示例代码。
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
lo.add(new Object()); // 3
String s = ls.get(0); // 4: attempts to assign an Object to a String! ClassCastException would have to be thrown!

第 1 行肯定是合法的。但第 2 行在泛型中是不合法的。如果允许它的话,在运行时会在第 4 行抛出ClassCastException 。[2]


5.  针对您自己的练习,通过临时编写一些代码做实验,看看它们是否像您期望的那样运行。代码 1.14 是一段样例代码。
      //
      // Assignment of collection with parameter types
      //       
      List<Object> lo5 = new ArrayList<Integer>();
      List<Object> lo6 = new Vector<Integer>(5);
      Collection<Object> co1 = new Vector<String>();           // Right-click this line and select Fix Imports first    
      Collection<Object> co2 = new ArrayList<Integer> (10);  
      Collection<Integer> ci1 = new ArrayList<Integer> (10);
代码 1.14:实验代码


NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/GenericsSubtyping您可以打开并运行它。
                                                                                                              返回练习顶部


结束语

在本练习中,您了解了 ArrayList<String> 不是 一个 ArrayList<Object> 和为什么这成为问题的所在。

                                                                                                                        返回顶部



练习 3:通配符


在本练习中,您将了解通配符的概念和 何时使用它们。通配符类型等同于说该类型是未知的,因而 Collection<?> 等同于一个未知类型的集合。


(3.1)通配符


1. 创建一个新的 NetBeans 项目
2. 根据代码 3.11 修改由 IDE 生成的 GenericsWildcard.java。仔细阅读该代码,需要特别注意粗体部分。
import java.util.ArrayList;
import java.util.Collection;

public class GenericsWildcard {
   
    static void printCollection(Collection<Object> c){
        for (Object o: c)
            System.out.println(o);
    }
   
    public static void main(String[] args){
        // TODO code application logic here
       
        ArrayList<Integer> a = new ArrayList<Integer>(10);
        printCollection(a);
       
    }
   
}
代码 3.11:GenericsWildcard.java

3. 注意到一些代码行出现了编译错误,如下图 3.12 所示。


图 3.12:编译错误

4. 通过阅读以下解释,理解为什么一些代码行出现编译错误,而另一些没有。

第 15 行(出现红 x 框的代码行):同样,此行出现编译错误的原因是因为您将一个   ArrayList<Integer> 实例传入一个需要 Collection<Object> 的地方。到现在,您得特别注意 ArrayList<Integer> 不是 ArrayList<Object> 的子类,ArrayList<Integer> 也不是 Collection<Object> 的子类。这和您在练习 2 中遇到的编译错误是一样的。上面图 40 中您遇到的编译错误和下面代码 41 所产生的效果是一样的。
Collection<Object> c = new ArrayList<Integer> (10);                 // Compilation error


5. 一种解决办法是按照以下办法更改 printCollection(..)方法。请注意 Integer 类型在调用者和被调用者间是相匹配的。
import java.util.ArrayList;
import java.util.Collection;

public class GenericsWildcard {
   
    static void printCollection(Collection<Integer> c){
        for (Object o: c)
            System.out.println(o);
    }
   
    public static void main(String[] args){

        ArrayList<Integer> a = new ArrayList<Integer>(10);
        printCollection(a);
       
    }
   
}
代码 3.14:使用兼容类型

6. 现在假设您不仅要传递一个 Integer 集合,也要传递任意类型的集合,如 Long、Float 或 String 等等。如何对 printCollection()进行更改呢?正如在之前步骤中那样,不能使用 Collection<Object>。在这里未知类型 <?>(或者称之为通配符)就要登场了。为和任意类型集合相协调,您可以使用未知类型 <?>,如以下代码 3.15 所示。
import java.util.ArrayList;
import java.util.Collection;

public class GenericsWildcard {
   
    static void printCollection(Collection<?> c){
        for (Object o: c)
            System.out.println(o);
    }
   
    public static void main(String[] args){
        // TODO code application logic here
       
        ArrayList<Integer> a = new ArrayList<Integer>(10);
        printCollection(a);
        ArrayList<Long> l = new ArrayList<Long>(10);
        printCollection(l);
        ArrayList<String> s = new ArrayList<String>(10);
        printCollection(s);
    }
   
}
代码 3.15:使用通配符

以上代码 3.15 和以下代码 3.16 产生的效果是一样的。
    Collection<?> c = new ArrayList<Integer> (10);                           // This code works
    c = new ArrayList<Long> (10);                                                  // This code works
    c = new ArrayList<String> (10);                                                 // This code works
代码 3.16:对未知类型的用法

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/GenericsWildcard您可以打开并运行它。                                                                                                              返回练习顶部

(3.2)绑定的通配符


现在我们要介绍 绑定的 Wildcard。在很多情况下我们都想要定义一个限定于特定类或其子类的类型参数。不妨说,您想要对 printCollection() 方法进行限定,从而只获取 Number 类型及其子类型,而不是获取任意类型。换言之,您想要编译器在 printCollection()方法接收到 String 类型的集合时生成编译错误,而在接收到 Number、Integer 或 Long 类型时不产生编译错误。这就是绑定的通配符<? 扩展 Number> 集合的用处。1. 创建一个新的 NetBeans 项目
2. 根据代码 3.21 修改由 IDE 生成的 GenericsBoundedWildcard.java。仔细阅读该代码,需要特别注意粗体部分。
import java.util.ArrayList;
import java.util.Collection;

public class GenericsBoundedWildcard {
  
    static void printCollection(Collection<? extends Number> c){      // Bounded wildcard
        for (Object o: c)
            System.out.println(o);
    }
  
    public static void main(String[] args){
      
        ArrayList<Integer> a = new ArrayList<Integer>(10);
        printCollection(a);
        ArrayList<Long> l = new ArrayList<Long>(10);
        printCollection(l);
        ArrayList<String> s = new ArrayList<String>(10);
        printCollection(s);                                                    // Now compile error should occur
      
    }
  
}
代码 3.21:GenericsBoundedWildcard.java

3. 注意到一些代码行出现编译错误,如下图 3.22 所示。


图 3.22:编译错误

以上代码和以下代码 3.23 产生的效果一样。
    Collection<? extends Number> c = new ArrayList<Integer> (10); // This code works                      
    c = new ArrayList<Long> (10);                                                  // This code works
    c = new ArrayList<String> (10);                                                 // Compilation error
代码 3.23:绑定通配符的用法

4. 现在是您自己实验时间。编写您自己的代码,看看它们是否像您期望的那样运行。以下代码 3.24 包含了样例代码,您可以使用它来做实验。
        Collection<? extends Number> c3;
        c3 = new Vector<Integer>();
        c3.add(new Integer(3));
        c3.add(new Long(4L));
        c3 = new Vector<String>();
        c3 = new Vector<Long>();
        c3 = new ArrayList<Date>();
       
        Collection<? extends Object> c4;
        c4 = new Vector<Integer>();
        c4 = new Vector<String>();
        c4 = new Vector<Long>();
        c4 = new ArrayList<Date>();
       
        Collection<?> c5;
        c5 = new Vector<Integer>();
        c5 = new Vector<String>();
        c5 = new Vector<Long>();
        c5 = new ArrayList<Date>();
代码 3.24:使用通配符作为类型参数的实验代码


NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/GenericsBoundedWildcard您可以打开并运行它。
                                                                                                              返回练习顶部

结束语


在本练习中,您了解了如何检索 ThreadGroup 中的信息。                                                                                                                    返回顶部



练习 4:定义您自己的泛型类


我们都熟悉了在 Java 中定义一个类,就好像 “public class Foo extends Frame implements ActionListener”。这在泛型中也是一样,除了添加入一项可选的类型参数设置。同样,类型参数也被置于 < 和 >中间。一个泛型类可以有多个类型参数,其类型参数以逗号隔开。
到目前为止,您已经使用了已经在 J2SE 5.0 SDK 中提供的泛型类。在本练习中,您将创建自己的泛型类并将该类用于代码中。
  1. 创建一个泛型类 Pair<F, S>
  2. 使用通配符
  3. 创建另一个泛型类,PairExtended<F, S, T>
  4. 将另一个泛型类型用作参数

(4.1)创建一个泛型类,Pair<F, S>


1. 创建一个新的 NetBeans 项目
2. 根据代码 4.11 修改由 IDE 生成的 MyOwnGenericClass.java。请注意该代码创建了一个 Pair<Number, String> 的对象实例。您将在接下来的步骤中,定义 Pair<F, S>
public class MyOwnGenericClass {
   
    public static void main(String[] args){
       
        // Create an instance of Pair <F, S> class.  Let's call it p1.
        Number n1 = new Integer(5);
        String s1 = new String("Sun");
        Pair<Number,String> p1 = new Pair<Number,String>(n1, s1);

        // The following line of code should generate compile error
        // Pair<Number,String> p2 = new Pair<Number,String>(new Integer(4), new Integer(3));

        System.out.println("first of p1 (right after creation)= " + p1.getFirst());
        System.out.println("second of p2  (right after creation)= " + p1.getSecond());
       
        // Set internal variables of p1.
        p1.setFirst(new Long(6L));
        p1.setSecond(new String("rises"));
        System.out.println("first of p1(after setting values)= " + p1.getFirst());
        System.out.println("second of p1 (after setting values)= " + p1.getSecond());
    }
   
}
代码 4.11:MyOwnGenericClass.java

3. 根据代码 4.12 编写 Pair.java
public class Pair<F, S> {
    F first;  S second;
   
    public Pair(F f, S s){
        first = f;  second = s;
    }
   
    public void setFirst(F f){
        first = f;
    }
   
    public F getFirst(){
        return first;
    }
   
    public void setSecond(S s){
        second = s;
    }
   
    public S getSecond(){
        return second;
    }
}
代码 4.12:Pair.java

4. 生成和运行项目
first of p1 (right after creation)= 5
second of p2  (right after creation)= Sun
first of p1(after setting values)= 6
second of p1 (after setting values)= rises
图 4.14:运行 MyOwnGenericClass 应用程序的结果

                                                                                                              返回练习顶部

(4.2)使用通配符


1. 根据代码 4.21 修改 MyOwnGenericClass.java。需要添加的代码段突出显示为蓝色粗体。
public class MyOwnGenericClass {
   
    public static void main(String[] args){
       
        // Create an instance of Pair <F, S> class.  Let's call it p1.
        Number n1 = new Integer(5);
        String s1 = new String("Sun");
        Pair<Number,String> p1 = new Pair<Number,String>(n1, s1);
        System.out.println("first of p1 (right after creation)= " + p1.getFirst());
        System.out.println("second of p2  (right after creation)= " + p1.getSecond());
       
        // Set internal variables of p1.
        p1.setFirst(new Long(6L));
        p1.setSecond(new String("rises"));
        System.out.println("first of p1(after setting values)= " + p1.getFirst());
        System.out.println("second of p1 (after setting values)= " + p1.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard type arguments.
        Number n2 = new Integer(15);
        String s2 = new String("again");
        Pair<?, ?> p2 = new Pair<Number, String>(n2, s2);
        System.out.println("first of p2 = " + p2.getFirst());
        System.out.println("second of p2 = " + p2.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard with bounded type arguments.
        Number n3 = new Integer(25);
        String s3 = new String("and again!");
        Pair<? extends String, ?> p3 = new Pair<String, String>(s3, s3);
        System.out.println("first of p3 = " + p3.getFirst());
        System.out.println("second of p3 = " + p3.getSecond());
    }
   
}
代码 4.21:MyOwnGenericClass.java

2. 生成和运行项目
first of p1 (right after creation)= 5
second of p2  (right after creation)= Sun
first of p1(after setting values)= 6
second of p1 (after setting values)= rises
first of p2 = 15
second of p2 = again
first of p3 = and again!
second of p3 = and again!
图 4.24:运行 MyOwnGenericClass1 应用程序的结果


                                                                                                              返回练习顶部

(4.3)创建另一个泛型类,PairExtended<F, S, T>


现在将要定义另一个泛型类,PairExtended<F, S, T>,它扩展了您在之前步骤中创建的 Pair<F, S>。1. 根据代码 4.32 编写 PairExtended.java。仔细阅读该代码,需要特别注意粗体部分。
public class PairExtended <F, S, T> extends Pair<F, S> {
   
    T third;
   
    /** Creates a new instance of PairExtended */
    PairExtended(F f, S s, T t){
        super(f, s);
        third = t;
    }
   
    public T getThird(){
        return third;
    }
}
代码 4.32:PrintStringsThread.java

2. 根据代码 4.33 修改 MyOwnGenericClass.java。需要添加的代码段突出显示为蓝色粗体。
public class MyOwnGenericClass {
   
    public static void main(String[] args){
       
        // Create an instance of Pair <F, S> class.  Let's call it p1.
        Number n1 = new Integer(5);
        String s1 = new String("Sun");
        Pair<Number,String> p1 = new Pair<Number,String>(n1, s1);
        System.out.println("first of p1 (right after creation)= " + p1.getFirst());
        System.out.println("second of p2  (right after creation)= " + p1.getSecond());
       
        // Set internal variables of p1.
        p1.setFirst(new Long(6L));
        p1.setSecond(new String("rises"));
        System.out.println("first of p1(after setting values)= " + p1.getFirst());
        System.out.println("second of p1 (after setting values)= " + p1.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard type arguments.
        Number n2 = new Integer(15);
        String s2 = new String("again");
        Pair<?, ?> p2 = new Pair<Number, String>(n2, s2);
        System.out.println("first of p2 = " + p2.getFirst());
        System.out.println("second of p2 = " + p2.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard with bounded type arguments.
        Number n3 = new Integer(25);
        String s3 = new String("and again!");
        Pair<? extends String, ?> p3 = new Pair<String, String>(s3, s3);
        System.out.println("first of p3 = " + p3.getFirst());
        System.out.println("second of p3 = " + p3.getSecond());
       
        // Create an instance of PairExtended<F, S, T> class with concrete type arguments,
        // <Number, String, Integer>
        Number n4 = new Long(3000L);
        String s4 = new String("james");
        Integer i4 = new Integer(7);
        PairExtended<Number, String, Integer> pe4
                = new PairExtended<Number, String, Integer>(n4, s4, i4);
        System.out.println("first of PairExtended = " + pe4.getFirst());
        System.out.println("second of PairExtended = " + pe4.getSecond());
        System.out.println("third of PairExtended = " + pe4.getThird());
    }
   
}
代码 4.33:TwoStrings.java

3. 生成和运行项目
first of p1 (right after creation)= 5
second of p2  (right after creation)= Sun
first of p1(after setting values)= 6
second of p1 (after setting values)= rises
first of p2 = 15
second of p2 = again
first of p3 = and again!
second of p3 = and again!
first of PairExtended = 3000
second of PairExtended = james
third of PairExtended = 7
图 4.34:运行 MyOwnGenericClass 应用程序的结果

                                                                                                              返回练习顶部


(4.4)将另一个泛型类型用作参数


将新代码段添加到 Main.java,如以下代码 54 所示。需要添加的代码段突出显示为粗体。这段代码包含了几行这样的代码,用具体的类型参数调用 PairExtended<F,S,T> 泛型类。这一次,第三个类型参数被设定给另一个集合类 ArrayList<Integer> 的实例。
1. 根据代码 4.41 修改 MyOwnGenericClass.java。需要添加的代码段突出显示为蓝色粗体。
import java.util.ArrayList;

public class MyOwnGenericClass {
   
    public static void main(String[] args){
       
        // Create an instance of Pair <F, S> class.  Let's call it p1.
        Number n1 = new Integer(5);
        String s1 = new String("Sun");
        Pair<Number,String> p1 = new Pair<Number,String>(n1, s1);
        System.out.println("first of p1 (right after creation)= " + p1.getFirst());
        System.out.println("second of p2  (right after creation)= " + p1.getSecond());
       
        // Set internal variables of p1.
        p1.setFirst(new Long(6L));
        p1.setSecond(new String("rises"));
        System.out.println("first of p1(after setting values)= " + p1.getFirst());
        System.out.println("second of p1 (after setting values)= " + p1.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard type arguments.
        Number n2 = new Integer(15);
        String s2 = new String("again");
        Pair<?, ?> p2 = new Pair<Number, String>(n2, s2);
        System.out.println("first of p2 = " + p2.getFirst());
        System.out.println("second of p2 = " + p2.getSecond());
       
        // Create an instance of Pair <F, S> class using wildcard with bounded type arguments.
        Number n3 = new Integer(25);
        String s3 = new String("and again!");
        Pair<? extends String, ?> p3 = new Pair<String, String>(s3, s3);
        System.out.println("first of p3 = " + p3.getFirst());
        System.out.println("second of p3 = " + p3.getSecond());
       
        // Create an instance of PairExtended<F, S, T> class with concrete type arguments,
        // <Number, String, Integer>
        Number n4 = new Long(3000L);
        String s4 = new String("james");
        Integer i4 = new Integer(7);
        PairExtended<Number, String, Integer> pe4
                = new PairExtended<Number, String, Integer>(n4, s4, i4);
        System.out.println("first of PairExtended = " + pe4.getFirst());
        System.out.println("second of PairExtended = " + pe4.getSecond());
        System.out.println("third of PairExtended = " + pe4.getThird());
       
        // Create an instance of PairExtended<F. S, T> class with
        // with ArrayList<E> as a third type argument.
        ArrayList<Integer> ar4 = new ArrayList<Integer>();       // Right-click this line and select Fix Imports first
        ar4.add(6000);
        ar4.add(7000);
        PairExtended<Number, String, ArrayList<Integer>> pe5
                = new PairExtended<Number, String, ArrayList<Integer>>(n4, s4, ar4);
        System.out.println("first of PairExtended with ArrayList = " + pe5.getFirst());
        System.out.println("second of PairExtended with ArrayList = " + pe5.getSecond());
        System.out.println("third of PairExtended with ArrayList = " + pe5.getThird());
    }
   
}
代码 4.41:MyOwnGenericClass.java

2. 生成和运行项目
first of p1 (right after creation)= 5
second of p2  (right after creation)= Sun
first of p1(after setting values)= 6
second of p1 (after setting values)= rises
first of p2 = 15
second of p2 = again
first of p3 = and again!
second of p3 = and again!
first of PairExtended = 3000
second of PairExtended = james
third of PairExtended = 7
first of PairExtended with ArrayList = 3000
second of PairExtended with ArrayList = james
third of PairExtended with ArrayList = [6000, 7000]
代码 4.42:结果

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/MyOwnGenericClass您可以打开并运行它。

结束语


在本练习中,您了解了如何创建您自己的泛型类和如何使用它。                                                                                                                    返回顶部

练习 5:类型擦除


Java 编译器将泛型实现为一个名称为 类型擦除 的前端转换。您可以(大概)将其当作是一个源到源的转换,在这一过程中,代码的泛型版本被转换为无泛型版本。基本上,类型擦除消灭(或擦除)了所有的泛型类型信息。尖括号间的所有类型信息都被抛出,所以,例如像 List<String> 的参数化类型被转换为一个原生类型 List,类似的 List<Date> 也被转换为 List。所有余下的对类型变量的使用都被替换为该类型变量(通常为对象)的上级绑定。而且,只要结果代码类型不正确,就会将其转换为合适的类型。
关于类型擦除的两个要点的


(5.1)类型擦除示例 1


1. 创建一个新的 NetBeans 项目
2. 根据代码 5.11 修改由 IDE 生成的 TypeErasure.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class TypeErasure {
   
    static void printCollection(Collection<? extends Number> c){
        for (Object o: c)
            System.out.println(o);
    }
   
    public static void main(String[] args){
       
        // Display class information of the various ArrayList instances
        ArrayList<Integer> ai = new ArrayList<Integer>();
        System.out.println("Class of ArrayList<Integer> = " + ai.getClass());
        List<Integer> li = new ArrayList<Integer>();
        System.out.println("Class of List<Integer> = " + li.getClass());
        ArrayList<String> as = new ArrayList<String>();
        System.out.println("Class of ArrayList<String> = " + as.getClass());
        ArrayList ar = new ArrayList();
        System.out.println("Class of ArrayList  = " + ar.getClass());
       
        // Check if two ArrayList instances with different type parameters
        // (one with Integer and the other with String)share the same class (bytecode).
        //
        Boolean b1 = (ai.getClass()== as.getClass());
        System.out.println("Do ArrayList<Integer> and ArrayList<String> share same class? " + b1);
       
        // Check if two ArrayList instances with different type parameters
        // (one with Integer and the other with raw type)share the same class (bytecode).
        //
        Boolean b2 = (ai.getClass()== ar.getClass());
        System.out.println("Do ArrayList<Integer> and ArrayList (raw type)share same class? " + b2);
       
    }
   
}
代码 5.11:TypeErasure.java

3. 生成并运行项目
Class of ArrayList<Integer> = class java.util.ArrayList
Class of List<Integer> = class java.util.ArrayList
Class of ArrayList<String> = class java.util.ArrayList
Class of ArrayList  = class java.util.ArrayList
Do ArrayList<Integer> and ArrayList<String> share same class? true
Do ArrayList<Integer> and ArrayList (raw type)share same class? true
图 5.15:运行 TypeErasure 应用程序的结果

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/TypeErasure您可以打开并运行它。
                                                                                                              返回练习顶部

(5.2)类型擦除示例 2

1. 创建一个新的 NetBeans 项目

2. 根据代码 5.21 修改由 IDE 生成的 TypeErasure2.java
import java.util.List;
import java.util.Vector;

public class TypeErasure2 {
   
    public static void main(String[] args){
        //
        // Get class and type information of a collection class
        //
        List<Number> ln5 = new Vector<Number>(20);
        Class c3  = ln5.getClass();                                       // Right-click this line and select Fix Imports first
        System.out.println("Class of List<Number>  =" + c3);
       
        Class [] c4 = c3.getInterfaces();                                // Right-click this line and select Fix Imports first
        for (Class c: c4){
            System.out.println("Interface = " + c);
        }
       
        Class c5 = c3.getSuperclass();
        System.out.println("Superclass = " + c5);
    }
   
}
代码 5.21:TypeErasure2.java

3. 生成和运行项目
Class of List<Number>  =class java.util.Vector
Interface = interface java.util.List
Interface = interface java.util.RandomAccess
Interface = interface java.lang.Cloneable
Interface = interface java.io.Serializable
Superclass = class java.util.AbstractList
图 5.25:运行 TypeErasure2 应用程序的结果

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/TypeErasure2您可以打开并运行它。


                                                                                                              返回练习顶部

结束语


 在本练习中,您了解了类型擦除的概念。


                                                                                                                    返回顶部

练习 6:与非泛型代码互操作


在本练习中,您将了解如何将泛型和非泛型代码一起使用。也将了解未检查异常。

(6.1)调度一次性任务


1. 创建一个新的 NetBeans 项目
2. 根据代码 6.11 修改由 IDE 生成的 GenericsInteroperability.java。仔细阅读该代码,需要特别注意粗体部分。
import java.util.LinkedList;
import java.util.List;

public class GenericsInteroperability {
   
    public static void main(String[] args){
       
        List<String> ls = new LinkedList<String>();
        List lraw = ls;
        lraw.add(new Integer(4));
        String s = ls.iterator().next();
    }
   
}
代码 6.11:GenericsInteroperability.java

3. 编译文件
Compiling 1 source file to C:\javase5generics\samples\GenericsInteroperability\build\classes
Note: C:\javase5generics\samples\GenericsInteroperability\src\GenericsInteroperability.java uses unchecked or unsafe operations.
Note:Recompile with -Xlint:unchecked for details.
图 6.12:警告消息

4. 运行项目

图 6.13:运行 UnGenericsInteroperability 应用程序的结果

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于 <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/GenericsInteroperabilityWarning您可以打开并运行它。4. 根据代码 6.14 修改  GenericsInteroperability.java。需要删除(或者被注释掉)的代码段突出显示为红色粗休,而需要添加的代码段则突出显示为蓝色粗体。
import java.util.LinkedList;
import java.util.List;

public class GenericsInteroperability {
   
    public static void main(String[] args){
       
        List<String> ls = new LinkedList<String>();
        //List lraw = ls;
        //lraw.add(new Integer(4));
        List<String> ls2 = ls;
        ls2.add(new Integer(4));        // Compile error
        String s = ls.iterator().next();
    }
   
}
代码 6.14:GenericsInteroperability.java

5. 您应该会遇到编译错误,这意味着您能够在编译时检查出类型不匹配问题,而不是在运行时。(如下图 6.15 所示)


图 6.15:编译时间类型不匹配检查

NetBeans 项目:针对此练习的解决方案作为可运行的 NetBeans 项目包含在动手实验室的压缩文件中。项目文件位于  <LAB_UNZIPPED_DIRECTORY>/javase5generics/samples/GenericsInteroperabilityCompileError


                                                                                                              返回练习顶部

结束语


在本练习中,您了解了如何将泛型和非泛型代码一起使用。也了解了何时会发生未检查的异常。                                                                                                                    返回顶部

课外练习(针对 Sang Shin“Java EE 编程在线课程”的学习者)


1. 课外练习是根据以下要求修改 练习 5 中完成的 MyOwnGenericClass NetBeans 项目。(可以通过复制 MyOwnGenericClass 项目来创建一个新项目。您可以将该项目命名为任意名称,此处我将使用 MyMyOwnGenericClass。)

2. 将以下文件以 JavaIntro-javase5generics 作为 主题 发送到 javaintro1homework@sun.com




原文链接:http://www.javapassion.com/handsonlabs/javase5generics/index.html