一个简单的泛型示例

在开始讨论泛型的理论知识之前,先看一个简单的泛型示例.下面的程序定义了两个类:第一个是泛型类Gen,第二个是GenDemo,他使用了Gen.
public class test2
{
    // @param args
    public static void main(String args[])
    {
     Gen<Integer> iOb;
     
     iOb = new Gen<Integer>(88);
     iOb.showType();
     
     int v = iOb.getOb();
     System.out.println("value : "+v);
     
     Gen<String> strOb = new  Gen<String>("Generics Test");
     strOb.showType();
     
     String str = strOb.getOb();
     System.out.println("value : "+str);
    }
}
class Gen<T>
{
    T ob;
    
    Gen(T o)
    {
        ob = o;
    }
    
    T getOb()
    {
        return ob;
    }
    
    void showType()
    {
        System.out.println("Type of T is  "+ob.getClass().getName());
    }
}
程序输出如下所示:
Type of T is java.lang.Integer
value : 88
Type of T is java.lang.String
value : Generics Test
下面仔细分析该程序,首先注意以下声明Gen语句:
class Gen<T>
其中,T是类型形参的名称,该名称用做当创建对象时传递给Gen的实际类型的占位符.因此在Gen中,只要需要类型形参就使用T.注意,T包括在<>中.此语法也可以推广.声明类型形参时,他都包括在尖括号中.由于Gen使用了类型形参,因此他是一个泛型类.
在Gen的声明中,名称T没有具体的意义,任何有效的标识符都可以使用,但是传统上都使用T.而且,建议类型形参的名称使用单字符的大写字母.另外两个常用的类型形参名是V和E.
接下来使用T来声明对象ob,如下所示:
T ob;
如上所述,T是当创建Gen对象时指定的实际类型的占位符,因此ob是传递给T类型的对象.例如,如果将String类型传递给T,那么ob就是String类型的.
下面看看Gen构造函数:
Gen(T o)
{
    ob = o;
}
注意形参o是T类型的,这表示o的实际类型由创建Gen对象时传递给T的类型决定.同样,由于形参o和成员变量ob都是T类型的,因此他们的类型都与创建Gen对象时传递给T的实际类型相同.
类型形参T还可以用来指定方法的返回类型,如getob()方法所示:
    T getOb()
    {
        return ob;
    }
由于ob也是T类型的,因此与getob()指定的返回类型兼容.
    void showType()
    {
        System.out.println("Type of T is  "+ob.getClass().getName());
    }
showType()方法显示T的类型,为此,他调用Class对象上的getName()方法,Class对象是通过调用ob对象的getClass()方法返回的.以前我们没有使用过该特性,所以下面仔细分析一下:Object类定义了方法getClass(),因此,getClass()是所有类类型的成员.他返回一个Class对象,该对象对应于调用他的对象的类类型.Class是在一个java.lang中定义的类,封装了有关类的信息.Class定义了多个可以在运行时获得有关类的信息的方法,其中包括getName()方法,他返回代表类型的字符串.
main演示了泛型类Gen,他首先创建一个整数Gen版本,如下所示:
Gen<Integer> iOb;
仔细看这个声明.首先,注意类型Integer在Gen之后的尖括号中指定.本例中,Integer是传递给Gen的类型形参T的类型实参.这样就创建了一个Gen版本,使得所有对T的应用都转换为对Integer的引用.因此,对于上述声明,ob是Integer类型,getOb()的返回类型是Integer.
需要指出的是,Java编译器不会实际创建不同版本的Gen,也不会创建任何其他泛型类.前面的说明只是方便理解,并不会实际发生.实际情况是,编译器将会删除所有的泛型类型信息,执行必要的类型转换,以便确保代码的行为与创建的特定版本的Gen相符合.因此,实际上程序中只存在一个版本的Gen.删除泛型类型信息过程称为擦除(erasure).
下一行语句将一个Integer版本的Gen类实例的引用赋值给iOb:
iOb = new Gen<Integer>(88);
注意,当调用Gen构造函数时,还指定了类型实参Integer.这样做是必要的,因为被赋值引用的对象的类型是Gen<Integer>.因此,new返回的引用也必须是类型Gen<Integer>,否则将会发生编译时错误.例如,下面的赋值将会导致编译时错误.
iOb = new Gen<Double>(88.0);
由于iOb的类型是Gen<Integer>,因此他不能用来引用一个Gen<Double>类型的对象.这种类型检查功能是泛型的主要优点之一,因为他能够确保类型安全性.正如程序注释的那样,下面的赋值语句:
iOb = new Gen<Integer>(88);
使用自动装箱功能把int值88封装为Integer.这样做是可以的,因为Gen<Integer>创建了一个使用Integer作为实参的构造函数.由于需要Integer,Java将自动把int值88装箱为Integer.当然,赋值语句也可以显式编写,如下所示:
iOb = new Gen<Integer>(new Integer(88));
但是使用这种形式并没有什么好处.然后该程序显示iOb中的ob类型,他是Integer.接下来使用下面一行语句获得ob的值:
int v = iOb.getob();
由于getob()的返回类型是T,他在iOb声明时由Integer替代,因此getob()的返回类型也是Integer,他在赋值给v(int类型)时自动拆箱为int.因此,不必把getob()的返回类型强制转换为Integer.
接下来,main()声明了一个Gen<String>类型的对象:
     Gen<String> strOb = new  Gen<String>("Generics Test");
由于类型实参是String,因此String在Gen中替代了T,正如程序剩余的部分所示,这(在概念上)创建了一个String版本的Gen.
(1)泛型只能用于引用类型
当声明泛型类型的实例时,传递给类型形参的类型实参必须是类类型.不能使用基本类型,如int或char.例如,对于Gen,可以传递任何类类型给T,但是不能传递基本类型给T.下面的声明是非法的:
Gen<int>intOb = new Gen<int>(53);
当然,不能指定基本类型并非严重限制,因为可以使用类型封装器来封装基本类型.而且,Java的自动装箱和自动拆箱机制使得类型封装器的使用变得透明.
(2)泛型类型是否相同基于其类型实参
对于泛型类型的理解至关重要的一点是,某个特定版本的泛型类型的引用与相同泛型类型的另一个版本不兼容.例如,对于前面的程序,下面一行代码有错误,无法编译:
iOb = strOb;
尽管iOb和strOb都是Gen<T>类型的,但他们是对不同类型的引用,因为他们的类型实参不同,这也是泛型增强类安全性和防止错误发生的途径之一.
(3)带有两个类型形参的泛型类
可以在泛型类型中声明多个类型形参.要指定多个类型形参,只需使用逗号分隔的形参列表.例如,下面的TwoGen是Gen类的变体,他使用了两个类型形参:
public class test2
{
    // @param args
    public static void main(String args[])
    {
     Gen<Integer,String> iOb;
     
     iOb = new Gen<Integer,String>(88,"test");
     iOb.showType();
     
     int v = iOb.getOb1();
     System.out.println("ob1 value : "+v);
     
     String str = iOb.getOb2();
     System.out.println("ob2 value : "+str);
    }
}
class Gen<T,V>
{
    T ob1;
    V ob2;
    
    Gen(T o1,V o2)
    {
        ob1 = o1;
        ob2 = o2;
    }
    
    T getOb1()
    {
        return ob1;
    }
    
    V getOb2()
    {
        return ob2;
    }
    
    void showType()
    {
        System.out.println("ob1 Type of T is  "+ob1.getClass().getName());
        System.out.println("ob2 Type of V is  "+ob2.getClass().getName());
    }
}
该程序输出如下所示:
ob1 Type of T is java.lang.Integer
ob2 Type of V is java.lang.String
ob1 value : 88
ob2 value : test
注意Gen是如何声明的:
class Gen<T,V>
他指定了两个类型形参T和V,中间用逗号分隔.由于有两个类型形参,因此在创建对象时要有两个类型实参传递给TwoGen,如下所示:
iOb = new Gen<Integer,String>(88,"test");
这里Integer取代T,String取代了V.虽然连个类型实参不同,但是类型实参也可以相同.例如,下面的代码同样有效:
Gen<String,String> x = new Gen<String,String>("A","B");
这里,T和V都是String类型的,当然,如果类型实参相同,使用两个类型形参就没有必要.
(4)泛型类的一般形式
前面的示例中给出的泛型语法可以推而广之.下面总结了声明泛型类的一般语法形式:
class class-name<type-param-list>
下面是声明泛型类应用并创建泛型实例的语法:
class-name<type-arg-list> var-name = new class-name<type-arg-list>(cons-arg-list);