首页 > 代码库 > Java 泛型

Java 泛型

泛型的作用

使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性.泛型对于集合类尤其有用.

说白了就是减少类型转换,增加可读性,同时避免代码中大量的Objcet的使用带来的转换安全隐患。

简单泛型类的定义

一个泛型类(generic class)就是具有一个或多个类型变量的类.下面是Pair类的代码:

  1. public class Pair<T>  
  2. {  
  3.     private T first;  
  4.     private T second;  
  5.     
  6.     public Pair(){ first = null; second = null;}  
  7.     public Pair(T first, T second) { this.first = first; this.second = second; }  
  8.     public T getFirst() { return first; }  
  9.     public T getSecond() { return second; }  
  10.     public void setFirst(T newValue) { first = newValue; }  
  11.     public void setSecond(T newValue) { second = newValue; }  
  12. }  

Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面.泛型类可以有多个类型变量.例如,可以定义Pair,其中第一个域和第二个域使用不同的类型:

  1. public class Pair<T, U> { ... }  

类定义中的类型变量指定方法的返回类型以及域和局部变量的类型.例如:

  1. private T first;    // uses the type variable  

注释:类型变量使用大写形式,且比较短,这是很常见的.Java库中,使用变量E表示集合的元素类型,KV分别表示表的关键字与值的类型. T(需要时还可以用临近的字母US)表示"任意类型".
用具体的类型替换类型变量就可以实例化泛型类型,例如:

  1. Pair<String>  

可以将结果想象成带有构造器的普通类:

  1. Pair<String>()  
  2. Pair<String>(String, String)  

和方法:

  1. String getFirst()  
  2. String getSecond()  
  3. void setFirst(String)  
  4. void setSecond(String)  

换句话说,泛型类可以看做普通类的工厂.

泛型方法

  1. class ArrayAlg  
  2. {  
  3.     public static <T> T getMiddle(T...a)  
  4.     {  
  5.         return a[a.length / 2];  
  6.     }  
  7. }  

这个方法是在普通类中定义的,而不是在泛型类中定义的.然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点.注意,类型变量放在修饰符(这里是 public static)的后面,返回类型的前面.
泛型方法可以定义在普通类中,也可以定义在泛型类中.
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

  1. String middle = ArrayAlg.<String>getMiddle("John""Q.""Public");  

在这种情况下,方法调用中可以省略<String>类型参数.编译器有足够的信息能够推断出所调用的方法.它用names的类型(String[])与泛型类型T[]进行匹配并推断出T一定是String,也就是说,可以调用

  1. String middle = ArrayAlg.getMiddle("John""Q.""Public");  

书上说在大多数情况下,对泛型方法的推断没有问题,但是,如果编译器无法推断出一个合适的类型,此时编译器就会报错。

如下:

  1. package com.zjf;
  2.  
  3. public class Test {
  4.    public static void main(String[] args) {
  5.       test("zjf","xhj");
  6.       test("zjf",11);
  7.       test("zjf",11,3.14);
  8.       test(11,3.14,0);
  9.    }
  10.  
  11.    public static <T> void test(T... t)
  12.    {
  13.  
  14.    }
  15. }

实际上,在我的环境下(java7),不仅编译没有报错,运行也没有问题。

我猜想因为他们都是属于Object的子类,再怎么推断不出来,也可以使用Object。

类型变量的限定

使用extends 进行限制

  1. public static <T extends Comparable> void test(T t1,T t2)
  2.    {
  3.       t1.compareTo(t2);
  4.    }

这样能保证,T必然是Comparable的子类或者接口实现,在方法里面就可以对变量a的内容使用Comparable具有的方法。

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

extends Comparable & Serializable  

泛型代码和虚拟机

虚拟机没有泛型类型对象--所有对象都属于普通类.无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type).原始类型的名字就是删除类型参数后的泛型类型名.擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object).

例如,Pair<T>的原始类型如下所示:

  1. public class Pair  
  2. {  
  3.     private Object first;  
  4.     private Object second;  
  5.     
  6.     public Pair(Object first, Object second)  
  7.     {  
  8.         this.first = first;  
  9.         this.second = second;  
  10.     }  
  11.     ...  
  12. }  

 因为T是一个无限定的变量,所以直接用Object替换.
 
在程序中可以包含不同类型的Pair,例如,Pair<String>Pair<GregorianCalendar> .而擦除类型后就变成原始的Pair类型了.

原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换.例如,Pair<T>中的类型变量没有显式的限定,因此,原始类型用Object替换T .假定声明了一个不同的类型.

  1. public class Interval<T extends Comparable & Serializable> implements Serializable  
  2. {  
  3.     private T lower;  
  4.     private T upper;  
  5.     ...  
  6.     public Interval(T first, T second)  
  7.     {  
  8.         if (first.compareTo(second) <= 0)  
  9.         {  
  10.             lower = first;  
  11.             upper = second;  
  12.         }  
  13.         else  
  14.         {  
  15.             lower = second;  
  16.             upper = second;  
  17.         }  
  18.     }  
  19. }  

    原始类型Interval如下所示:

  1. public class Interval implements Serializable  
  2. {  
  3.     private Comparable lower;  
  4.     private Comparable upper;  
  5.     ...  
  6.     public Interval(Comparable first, Comparable second)  
  7.     {...}  
  8. }  

翻译泛型表达式

当程序调用泛型方法时,如果擦掉返回类型,编译器插入强制类型转换.例如,下面这个语句序列

  1. Pair<Employee> buddies = ...;  
  2. Employee buddy = buddies.getFirst();  

擦除getFirst的返回类型后将返回Object类型.编译器自动插入Employee的强制类型转换.也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
   
对原始方法Pair.getFirst的调用
   
将返回的Object类型强制转换为Employee类型

Java 泛型