Java 泛型编程

本文采用部分意译

原文请看:http://java.sun.com/docs/books/tutorial/java/generics/index.html 

 

--------------------

 

属性:Java内置
作用:是一些问题在编译期间被发现,从而提高软件的可靠性

 

一、介绍
介绍不使用泛型的缺点。
二、泛型类型
介绍泛型类型的声明、类型变量、类型参数和type arguments,并介绍了类型参数的命名规范。
三、带泛型的方法和构造函数
四、限定类型参数
五、子类
六、通配符
七、类型擦除
介绍类型擦除,原始类型和未审核警告。

 


一、介绍
在软件开发过程中,bugs被发现得越早越好。因此,我们要想办法尽量在编译时发现bugs,而不是运行时。
Java 泛型编程为我们提供了一种在编译时多发现一些bugs的方法。
在Java 集合框架中大量使用了泛型编程。
 
先看一个不用泛型的类Box,它提供了设置与获取object的方法:

  1.     public class Box {
  2.         private Object object;
  3.         public void add(Object object) {
  4.             this.object = object;
  5.         }
  6.         public Object get() {
  7.             return object;
  8.         }
  9.     }

 

由于object是Object类型的,你可以在object中存储任何类型。
下面是一个BoxDemo,在这里它使用Box来存取整型数据:

  1. public class BoxDemo1 {
  2.     public static void main(String[] args) {
  3.         // 只把整型数据放到这个Box中!
  4.         Box integerBox = new Box();
  5.         integerBox.add(new Integer(10));
  6.         Integer someInteger = (Integer)integerBox.get();
  7.         System.out.println(someInteger);
  8.     }
  9. }

 

在这个程序中你要用Box来存取整型数据,你要告知其他程序员这一约束的方法之一是写一行注释。
但这样做,编译器不会知道,另外粗心的程序员也不会看到,他很可能写出一下代码:

 

  1. public class BoxDemo2 {
  2.     public static void main(String[] args) {
  3.         // 只把整型数据放到这个Box中!
  4.         Box integerBox = new Box();
  5.         // 想象这是一个大程序的一部分
  6.         // 它由另一个程序员来维护。 
  7.         integerBox.add("10"); // 注意现在传入的类型为String
  8.         // ... 这是另外一部分,很可能
  9.         // 由另一个程序员来编写
  10.         Integer someInteger = (Integer)integerBox.get();
  11.         System.out.println(someInteger);
  12.     }
  13. }

这段代码可以通过编译,但运行时会出以下异常:

 

Exception in thread "main"
         java.lang.ClassCastException: 
            java.lang.String cannot be cast to java.lang.Integer
            at BoxDemo2.main(BoxDemo2.java:6)

 

如果Box类用泛型实现,这样的错误就可以在编译时发现,而不是运行时。

 

二、泛型类型
现在我们用泛型来实现Box类。
首先我们进行泛型类型声明:把"public class Box" 改成 "public class Box"。
(注意:泛型类型声明对于接口同样适用。)
T 叫做类型变量,这样声明之后,T 就可以在Box类中使用了。在这种情况下,我们也可以把 T 叫做Box类的正式类型参数。
代码如下:

  1. /**
  2.  * Box 类的泛型版本 
  3.  */
  4. public class Box {
  5.     private T t; // T 表示 "Type"          
  6.     public void add(T t) {
  7.         this.t = t;
  8.     }
  9.     public T get() {
  10.         return t;
  11.     }
  12. }

 

 要引用这样一个带泛型的类,必须使用泛型类型调用:

  1.     Box integerBox;

 

(为什么要叫“调用”呢? 思考下方法调用,如:add(8)。方法调用有圆括号,而泛型类型调用有尖括号,形式上有点像啦。)
一个泛型类型调用通常也叫做参数化类型。
那如何实例化这个类呢?

  1.     integerBox = new Box();

 

实例化后,你就可以使用这个类的实例方法了:

  1. public class BoxDemo3 {
  2.     public static void main(String[] args) {
  3.         Box integerBox = new Box();
  4.         integerBox.add(new Integer(10));
  5.         Integer someInteger = integerBox.get(); // 没有进行类型转换!
  6.         System.out.println(someInteger);
  7.     }
  8. }

 


这样,当你想向add()方法传入一些与 T(本例为Integer)类型不兼容的类型,如String类型时,就会出现编译错误:

BoxDemo3.java:5: add(java.lang.Integer) in Box   
    cannot be applied to (java.lang.String)
        integerBox.add("10");
                  ^
    1 error


泛型类型声明中使用的T并不是实际存在的一种类型,它也不是类名Box的一部分。实际上,在编译过程中,所有的泛型信息都会被去掉,这在后面的类型擦除中会介绍。
在泛型类型声明中可以使用多个符号,但各个符号不能相同。如,你可以声明Box,而声明Box就会报错。

 

类型参数的命名规范:
E - Element (在Java集合框架中被大量使用)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

 

三、带泛型的方法和构造函数
类型参数也可以被声明在方法和构造函数的签名当中,使它们成为带泛型的方法和构造函数。
这与声明泛型类型没什么不同,只不过类型的作用域不同罢了。

  1. /**
  2.  * 此版本引进带泛型的方法。
  3.  */
  4. public class Box {
  5.     private T t;          
  6.     public void add(T t) {
  7.         this.t = t;
  8.     }
  9.     public T get() {
  10.         return t;
  11.     }
  12.     public  void inspect(U u){
  13.         System.out.println("T: " + t.getClass().getName());
  14.         System.out.println("U: " + u.getClass().getName());
  15.     }
  16.     public static void main(String[] args) {
  17.         Box integerBox = new Box();
  18.         integerBox.add(new Integer(10));
  19.         integerBox.inspect("some text");
  20.     }
  21. }


程序的输出是:

T: java.lang.Integer
U: java.lang.String


传入不同的类型,输出就会相应的改变。
带泛型的方法更为实际的用途可能会像下面这段代码:

  1. public static  void fillBoxes(U u, List> boxes) {
  2.         for (Box box : boxes) {
  3.             box.add(u);
  4.         }
  5.     }

为了使用这个方法,你的代码可能会是这样:

  1. Crayon red = ...;
  2.     List> crayonBoxes = ...;

调用这个方法的完整格式是:

  1. Box.fillBoxes(red, crayonBoxes);

另一种较为简便的调用格式是:

  1. Box.fillBoxes(red, crayonBoxes); // 编译器推断出 U 是 Crayon 类型

这一特性叫做类型推断,它可以使你像调用普通方法那样调用带泛型的方法。

 

四、限定类型参数
有时候你可能想限制可以被传入的类型。比如一个操作数字的方法,它只想接受Number以及Number的子类的实例。这时候就可以用限定类型参数了。
声明一个限定类型参数的方法:写出类型参数名+extend(对于后面的上界是类用extend,若是接口用implement)+上界。

 

  1. /**
  2.  * 此版本引进一个限定类型参数
  3.  */
  4. public class Box {
  5.     private T t;          
  6.     public void add(T t) {
  7.         this.t = t;
  8.     }
  9.     public T get() {
  10.         return t;
  11.     }
  12.     public extends Number> void inspect(U u){
  13.         System.out.println("T: " + t.getClass().getName());
  14.         System.out.println("U: " + u.getClass().getName());
  15.     }
  16.     public static void main(String[] args) {
  17.         Box integerBox = new Box();
  18.         integerBox.add(new Integer(10));
  19.         integerBox.inspect("some text"); // 错误: 这还是一个字符串! 
  20.     }
  21. }

以上代码会出现编译错误,应为String不是Number类或Number的子类:
Box.java:21: inspect(U) in Box cannot
  be applied to (java.lang.String)
                        integerBox.inspect("10");
                                  ^
1 error
如果还想规定更多的实现接口,用 & 把限定类连起来:

 

五、子类
父类参数可以引用子类实例。这在面向对象领域叫做"is a"关系。
如:

  1.     Object someObject = new Object();
  2.     Integer someInteger = new Integer(10);
  3.     someObject = someInteger; // OK

这在泛型编程中同样适用:

  1.  Box box = new Box();
  2.     box.add(new Integer(10)); // OK
  3.     box.add(new Double(10.1)); // OK

现在,思考如下方法:

  1.      public void boxTest(Box n){
  2.         // method body omitted 
  3.     }


这个方法接受什么样的参数呢?从方法签名来看,它接受Box
那它能否接受Box 或 Box 呢?也许你认为能。
但结果却出乎意料,答案是:不能!因为 Box 或 Box并不是Box的子类,它们不兼容。
想象有一个Cage类,它可以装Animal类以及Animal类的子类,即它足够大可以装狮子,它的栏杆足够密也可以装蝶。
而Cage只是一个能盛下狮子的大笼子,Cage只是一个栏杆足够密的小笼子。
现在你想要一个可以盛所有动物的万能笼子,有时可以关狮子,有时可以装蝴蝶。
而我却给你一个只能装狮子的大笼子或一个只能装蝴蝶的小笼子,这意味着你会失去狮子或蝴蝶,你会满意吗?

 

六、通配符
现在假设我就要一个笼子,它不是万能的,它只能装一种动物,但具体装狮子或者蝴蝶我还不确定,这就要使用限定通配了。
怎样声明这样一个笼子呢?

  1. Cageextends Animal> someCage = ...;

在这里,? 就是一个限定通配符,Animal是上界。
你也可以声明下界
虽然Cage 和 Cage不是Cage的子类,可它们是Cage的子类。
Cage 是一个能装任意动物的笼子,而Cage是一个只能装一种动物的笼子,所以狮子笼子或蝴蝶笼子都是这样的笼子。


七、类型擦除
当泛型类型的类被实例化时,编译器使用类型擦除技术,把所有有关类型的信息都去掉,以使编译后的程序能与泛型出现以前的Java类库或程序兼容。
比如Box被编译成Box类,Box类叫做原始类型,原始类型是不带参数类型的泛型类名(或接口名)。
这意味着你无法知晓在运行时某个泛型类使用的是那种具体的类型。下面的操作是不可行的:

  1.     public class MyClass {
  2.     public static void myMethod(Object item) {
  3.         if (item instanceof E) {  //Compiler error
  4.             ...
  5.         }
  6.         E item2 = new E();   //Compiler error
  7.         E[] iArray = new E[10]; //Compiler error
  8.         E obj = (E)new Object(); //Unchecked cast warning
  9.     }
  10. }


当传统代码与泛型代码混合使用时,编译器可能会给出如下警告信息:
Note: WarningDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

 

如:

  1. public class WarningDemo {
  2.     public static void main(String[] args){
  3.         Box bi;
  4.         bi = createBox();
  5.     }
  6.     static Box createBox(){
  7.         return new Box();
  8.     }
  9. }

用 -Xlint:unchecked 选项重新编译代码,可得到详细信息:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box
        bi = createBox();
                      ^
1 warning

------------------------

词汇表:


泛型 Generics
泛型类型 Generic Types
类型变量 Type variables
类型参数 Type Parameters
限定类型参数 Bounded Type Parameters
子类 Subtyping
通配符 Wildcards
类型擦除 Type Erasure
泛型类型调用 generic type invocation
参数化类型 parameterized type

类型推断 type inference

你可能感兴趣的:(Java)