加入收藏 | 设为首页 | 会员中心 | 我要投稿 | RSS
您当前的位置:首页 > 教程文章 > java开发

Java中的 泛型(Generics)详解

时间:2014-04-14 16:31:23  来源:  作者:

 泛型(Generics)

目录:

  1. 强类型集合类
  2. 泛型类
  3. 泛型通配符
  4. 泛型方法
  5. 继承中的泛型
  6. 泛型接口和枚举
  7. 类型去除

集合类中的数据类型

集合类中可以存储各种数据,数据一旦存入,其类型均会转化为Object类型。
从集合类中取出数据时,一般均需要将Object类型转换回存入之前的实际类型

1 Vector v=new Vector();
2 v.add("张三"); //存入字符串
3 String name=(String)v.get(0); //强制类型转换,OK
4 v.add(new Date()); //存入当前时间对象,OK
5 /*
6 由于Date类型不能转换为String,下面语句会在运行时发生错误,但这种错误在编译时不会被检查出来
7 */
8 String date=(String)v.get(1); //编译器不会发现这里有问题

强类型集合

传统的集合类的实例中可以存储任意类型数据,这种集合类称为弱类型集合类。
JDK1.5以后,引入了强类型集合类
强类型集合类中,只能存储指定类型的数据
在强类型集合类中取出数据时,无需进行类型转换处理,如果数据类型不配备,编译时会直接报错
强类型集合并没有引入新的类名,只需在定义原有集合对象时,用尖括号(<>)指明其存储的数据类型名称即可。

强类型集合示例

1 //下面的向量类的实例中只能存储字符串类型数据
2 Vector<String> v=new Vector<String>(); 
3 v.add("张三"); //加入的是字符串,OK
4 String name=v.get(0); //取出时,无需做类型转换
5 /*
6 如果想在这种强类型集合中加入日期数据,在编译时就会报告错误
7 */
8 v.add(new Date()); //编译器会直接报告类型不匹配错误

定义泛型(Generics)类

强类型集合采用了JDK1.5引入的泛型语法。
泛型相当于类中一种特殊的类型,这种类型的特点是在实例化该类时可指定为某个具体的实际类型。
声明包含泛型的类的格式如下:
[访问修饰符] class 类名<泛型1,泛型2,…>{
泛型1 泛型成员1;
泛型2 泛型成员2;
//....
}
声明中的泛型1、泛型2等等泛型符号可以是任意合法的Java标识符。

泛型类的声明示例

01 /*
02 此处声明了一个包含泛型T的泛型类,T代表所有可能的类型,而T
03 的实际类型在Generic类实例化时指定。
04 */
05 public class Generic<T> {
06     private T f; //f为泛型成员
07   
08     public void setF(T f) {//setF方法的参数类型为泛型T
09         this.f = f;
10     }
11     public T getF() {//getF方法的返回类型为泛型T
12         return f;
13     }
14 }<BR>

泛型类的实例化

创建泛型类的实例时,可以使用一对尖括号指定泛型的真正类型

01 //f1中的泛型T在此指定为Boolean类型
02 Generic<Boolean> f1 = new Generic<Boolean>();
03     
04 //f2中的泛型T在此指定为Integer类型
05 Generic<Integer> f2 = new Generic<Integer>();
06   
07 //f1的setF方法只能接受Boolean类型数据
08 f1.setF(new Boolean(true));
09 Boolean b = f1.getF(); 
10 System.out.println(b);
11   
12 //f2的setF方法只能接受Integer类型的数据    
13 f2.setF(new Integer(10)); 
14 Integer i = f2.getF(); 
15 System.out.println(i);

实例化时的泛型的默认类型

泛型类实例化时,并不一定要指明泛型对应的实际类型,此时会使用Object作为泛型的默认类型

编译时编译器会发出警告:

Note: Generic.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

建立类型为泛型类的数组

如果要建立泛型类的数组,需要注意new关键字后面不要加入泛型的实际类型名,如下所示:

1 Generic<String>[] gs; //声明泛型类的数组
2   
3 //先对泛型数组进行初始化
4 gs=new Generic[5]; //不要写成new Generic<String>[5]
5   
6 //再分别为每一个数组元素进行初始化
7 gs[0]=new Generic<String>();//为第一个数组元素赋值
8 //....<BR>

包含多个泛型的类定义示例

包含有两个泛型定义的类声明和实例化:

01 public class Generic2<T1, T2> {
02     private T1 f1;
03     private T2 f2;
04     //....
05 }
06 //给出泛型T1,T2的实际类型
07 Generic<Integer, Boolean> f =
08                new Generic<Integer, Boolean>();
09 //没有给出T1,T2的实际类型
10 Generic f1=new Generic();//T1,T2将被默认为是Object类型

泛型成员的使用

在泛型类中的泛型成员不能直接实例化,其实例必须要通过方法的参数传递给泛型成员:
 

01 public class Generic3<T> {
02     
03   private T[] array; //此处不能用new T[]实例化array
04     
05   public void setArray(T[] array) {
06         this.array = array;
07   }
08   public T[] getArray() {
09         return array;
10   }
11 }

 泛型成员实例化示例

通过方法的泛型参数,将数组的实例传递给类中的泛型数组:

1 String[] strs = {"caterpillar", "momor", "bush"};
2 Generic3<String> f = new Generic3<String>();
3   
4 //向泛型成员array传递实际的字符串数组
5 f.setArray(strs);
6   
7 //读取泛型成员array的值,将其赋给字符串数组变量strs
8 strs = f.getArray(); //此时array的类型为字符串数组

泛型成员的可用方法

由于泛型类型只有在类实例化后才能确定,类中的泛型成员只能使用Object类型中的方法

01 class Generic<T>{
02    T f;
03    void setF(T f){ this.f=f; }
04    //....
05    void doSome(){
06       /*
07        getClass和toString都是Object中的方法
08        */
09       System.out.println(f.getClass().getName());
10       System.out.println(f.toString());
11    }
12 }

 限制泛型上限类型

extends关键字用来指定泛型的上限,在实例化泛型类时,为该泛型指定的实际类型必须是指定类的子类或指定接口的子接口

01 import java.util.List;
02 public class ListGeneric<T extends List> {
03     private T list;
04     public void setList(T list) {
05         this.list = list;
06     }
07     public T getList() {
08         return list;
09     }
10 }

在限定泛型的类型时,无论要限定的是接口或是类,都要使用extends关键词

限制泛型上限类型的示例

1 ListGeneric<Vector> f1 =
2                   new ListGeneric<Vector>();
3 ListGeneric<ArrayList> f2 =
4                   new ListGeneric<ArrayList>();
5   
6 //如果不是List的类型,编译时就会发生错误
7 ListGeneric<HashMap> f3 =
8                    new ListGeneric<HashMap>();

type parameter java.util.HashMap is not within its bound

ListGeneric<HashMap> f3 = new ListGeneric<HashMap>();

默认的泛型限制类型

定义泛型类别时,如果只写以下代码:

1 public class Generic<T> {
2     //....
3 }<BR type="_moz">

 

相当于下面的定义方式

1 public class Generic<T extends Object> {
2 //....
3 }

限定泛型上限后的成员可用方法

泛型类型的上限一经限定,类中的泛型成员就可使用上限类型中的方法和其他可用成员:

01 import java.util.List;
02 import static java.lang.System.out;//静态导入
03   
04 public class ListGeneric<T extends List> {
05     private T list;
06     public void setList(T list) {
07         this.list = list;
08     }
09     public void doSome(){
10       //add、get方法都是List接口中定义的方法
11       list.add(new Integer(0));
12       out.println(list.get(0));//此处省略了System
13     }
14 }

泛型类实例之间的赋值

同一泛型类,如果实例化时给定的实际类型不同,则这些实例的类型是不兼容的,不能相互赋值。

1 Generic<Boolean> f1 = new Generic<Boolean>();
2 Generic<Integer> f2 = new Generic<Integer>();
3 f1=f2; //发生编译错误

incompatible types found : Generic<java.lang.Integer> 
required: Generic<java.lang.Boolean> f1 = f2;

泛型中的Object类型兼容性

 Object是所有类的父类,因此,所有的类型的实例都可赋值给声明为Object类型的变量

1 Boolean f1 = new Boolean(true);
2 Integer f2 = new Integer(1);
3 Object  f=f1;  //OK
4 f=f2;  //OK<BR>

在实例化泛型类时,将泛型指定为Object类型却不存在着和其他类型之间的兼容性:

1 Generic<Boolean> f1 = new Generic<Boolean>();
2 Generic<Integer> f2 = new Generic<Integer>();
3 Generic<Object> f=f1; //f1和f类型并不兼容,发生编译错误
4 f=f2;  //f2和f类型同样不兼容,也会发生编译错误

泛型通配字符(Wildcard)

泛型类实例之间的不兼容性会带来使用的不便。
使用泛型通配符(?)声明泛型类的变量可以解决这个问题

01 Generic<Boolean> f1 = new Generic<Boolean>();
02 Generic<Integer> f2 = new Generic<Integer>();
03 Generic<Object>  f3 = new Generic<Object>();
04   
05 //f可代表Generic所有可能的实例
06 Generic<?>   f; 
07   
08 f=f1;  //OK
09 f=f2;  //OK
10 f=f3;  //OK

通配符用作方法的参数

通配符也可以用于方法的参数类型的声明,表示该参数可接受对应泛型类型的任意实例。
以下类定义中的printCollection方法可以打印任意强类型集合中的内容

1 class Util{
2   
3   //Collection<?>可以匹配任何强类型集合
4   static void printCollection(Collection<?> c){
5      for(Object o : c)
6        System.out.println(o);
7   
8   
9 }

为通配符指定匹配上限

和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限:

1 Generic<? extends List> f = null;
2 f = new Generic<ArrayList>(); //Ok
3 ..... 
4 f = new Generic<Vector>();  //OK
5 ....
6 //以下语句会发生编译错误,因为HashMap没有实现List接口
7 f = new Genric<HashMap>();

incompatible types found : Generic<java.util.HashMap>

required: Generic<? extends java.util.List> mf = new Generic<HashMap>();

 限定通配符匹配类型的下限

还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型

1 //将f限定为只能代表采用java.sql.Date的父类实例化的实例
2 Generic<? super java.sql.Date> f = null;
3 f=new Generic<java.sql.Date>(); //Ok
4   
5 //Ok,java.util.Date是java.sql.Date的父类
6 f=new Generic<java.util.Date>(); 
7   
8 //错误,因为String不是java.sql.Date的父类
9 f=new Generic<String>();

通配符对泛型成员的影响

一旦对象使用通配字符声明,就无法利用它为类中的泛型成员传入新的实例,这时只能读取其中的泛型成员或者移除泛型成员存储的原有实例。

01 Generic<String> f = new Generic<String>();
02 foo.setF("caterpillar");
03 Generic<?> immutableF = f;
04 //可读取泛型成员f中保存的字符串实例
05 System.out.println(immutableF.getF());
06   
07 //可通过传递null参数来移除泛型成员f中保存的字符串实例
08 immutableF.setF(null);
09   
10 //不能通过immutableF的setF方法再次传递新的实例给类中
11 //泛型成员f,所以下面这行无法通过编译
12 immutableF.setF("wang");

泛型默认类型的实例类型兼容性

 实例化泛型类时采用默认泛型类型,此时泛型类的实例和其他给定类型的泛型类实例之间存在着类型兼容性,可以直接相互赋值。

1 Generic<Boolean> f1 = new Generic<Boolean>();
2 Generic<Integer> f2 = new Generic<Integer>();
3   
4 Generic f = new Generic();//默认泛型类型
5   
6 f  = f1;  //OK
7 f  = f2;  //OK
8 f1 = f;  //OK
9 f2 = f;  //OK

使用泛型默认类型虽然可以做到类型的兼容性,但会失去泛型带来的编译时刻类型检查的优点。

通配符用作方法参数的局限性

考虑如下任务:
编写一个方法,该方法含有两个参数,一个参数类型为Object类型的数组,另一个参数类型为对应的强类型集合,需要将数组中的元素复制到集合中。考虑到通配符的作用,在类M中有如下的方法定义:

01 class M{
02  /*
03   aToC方法的参数c因为是用通配符表示的任意强类型的集合,而?
04   代表未知类型,编译器无法确定其具体的类型,所以会导致错误
05  */
06  public static void aToC(Object[] a,Collection<?> c){
07         for(Object o : a)
08           c.add(o);//编译出错,此处可以用泛型方法解决
09       
10  }
11 }

泛型方法

不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:
访问修饰符 <泛型列表> 返回类型 方法名(参数列表){
      实现代码
}
其中泛型列表为用逗号分隔的合法Java标识符。
在泛型列表中声明的泛型,可用于该方法的返回类型声明、参数类型声明和方法代码中的局部变量的类型声明。
类中其他方法不能使用当前方法声明的泛型。

泛型方法声明示例

使用泛型方法可以解决上述的泛型通配符造成的问题:

01 class M{
02  /*
03  方法aToC声明了一个泛型T,该方法将任意类型的数组a中的所有
04  元素复制到相应的强类型集合c当中而不会导致编译错误。
05  此处的泛型声明T仅作用于aToC方法的声明部分和实现代码部分。
06  */
07  public static <T> void aToC(T[] a,Collection<T> c){
08         for(T o : a)
09           c.add(o);//不会出现类似通配符的编译错误
10       
11  }
12 }

泛型方法的调用

调用泛型方法和调用普通方法没有任何不同,只需要传递含有具体类型的实参即可:

01 //对M中定义的泛型方法aToC进行调用测试
02 class TestM{
03   public static void main(String[] args){
04     String[] sa= new String[100];
05     Collection<String> cs=
06                        new Vector<String>();
07      Collection<Object> co=
08                        new Vector<Object>();
09   
10     M.aToC(sa,cs); //aToC中的泛型T此时匹配类型String
11     M.aToC(sa,co); //aToC中的泛型T此时匹配类型Object
12   }
13 }

限定泛型方法中泛型类型

泛型方法中的声明的泛型,同样可以使用extends关键字限定其类型的下限:

1 class M{
2   //限定aToC方法中的泛型T必须是实现了序列化接口的类型
3  public static <T extends java.io.Serializable> 
4         void   aToC(T[] a,Collection<T> c){
5            for(T o : a)
6               c.add(o);
7  }
8 }

继承中的泛型

继承时如需保留父类泛型,需要在声明时加入父类泛型

01 public class SubGeneric<T1, T2, T3>
02                 extends Generic<T1, T2> {
03     private T3 f3;
04     
05     public void setF3(T3 f3) {
06         this.f3 = f3;
07     }
08    
09     public T3 getF3() {
10         return f3;
11     }
12 }<BR>

如果不保留父类中的泛型声明,则继承下来的T1与T2自动变为Object类型
建议父类中的泛型声明在子类中都要保留

继承时指定父类的泛型类型

如果在继承时,不想保留父类中的泛型,但也不想使用默认的Object类型,此时可以直接指定父类中的泛型。

01 public class SubGeneric<T3>
02        extends Generic<String, Object> {
03     private T3 f3;
04     
05     public void setF3(T3 f3) {
06         this.f3 = f3;
07     }
08    
09     public T3 getF3() {
10         return f3;
11     }
12 }

泛型接口

接口也可包含泛型的声明:

1 public interface I<T1, T2>{
2     T1 getT1();
3     T2 getT2();
4     //....
5 }<BR>

 

实现泛型接口时,类在定义时可以不声明泛型接口中的泛型,此时接口中的泛型也会自动变为Object类型:

1 public class IC implements I{
2   
3   public Object getT1(){  } //对接口方法getT1的实现
4   public Object getT2(){  } //对接口方法getT2的实现
5   //....
6 }

泛型接口的实现

如果需要在实现泛型接口时,保留接口中的泛型,类在定义时就必须要保留泛型接口中的泛型声明:

1 public class IC<T1,T2> implements I<T1,T2>{
2   
3   public T1 getT1(){  } //对接口方法getT1的实现
4   public T2 getT2(){  } //对接口方法getT2的实现
5   //....
6 }
7   
8 //声明泛型接口的变量,并利用泛型实现类进行实例化
9 I<String,Integer> i=new IC<String,Integer>();<BR>

实现泛型接口时指定泛型类型

在实现泛型接口时,也可直接指定接口中的泛型的实际类型:

01 //实现接口I时,直接指定泛型T1、T2的类型
02  class IC implements I<String,Integer>{
03    
04  //由于指定接口I中T1类型为String,getT1返回类型必须为String
05  public String getT1(){  }
06   
07  //由于指定接口I中T2类型为Integer,getT2返回类型必须为Integer
08  public Integer getT2(){  }
09   //....
10 }

泛型和枚举

由于枚举类型不能直接实例化,所以枚举的定义中不能含有泛型的声明,但枚举中可包含泛型方法的定义。

1 public enum TrafficLight{
2   Red,Amber,Green;
3   private int duration;
4   public static <T> 
5          void avgDuration(Collection<T> carType){
6      //....
7   }
8   //....
9 }

类型去除(Type Erasure)

JDK1.5引入的泛型实际上是一种编译器层次上的"语法糖",在虚拟机层次上并不直接支持泛型。编译器在编译时会自动"去除"有关泛型的定义,这样的处理方式有以下特点:

字节码与1.5以前的版本相同,保持了字节码的兼容性
安全性不受影响
性能不受影响-没增加也没减少
泛型的实际类型信息保存在类文件里

类型去除影响的一些Java语法

由于JDK1.5采用类型去除处理泛型语法,造成了一些比较晦涩的Java语法变化:

  • instanceof运算符不能用于包含泛型类型的判断
1 if(a instanceof java.util.List<String>) //错误<BR>
  • 方法的重载出现了一些怪异的语法现象,一些看似合理的方法重载不能通过编译,这些有望在JDK1.7中得到解决:
1 //print方法1
2 void print(java.util.List<String> list); 
3 //print方法2,不能和方法1同时存在 
4 void print(java.util.List<Integer> list); 
5 //print方法3,可以和方法1同时存在,构成重载方法 
6 int print(java.util.List<Integer> list);<BR type="_moz">
1   

 

来顶一下
返回首页
返回首页
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
推荐资讯
在CentOS下搭建Android 开发环境
在CentOS下搭建Androi
轻松搭建属于自己的Ubuntu发行版
轻松搭建属于自己的Ub
利用SUSE Studio 打造自己的个性化Linux发行版
利用SUSE Studio 打造
那些采用PHP技术的IT大企业
那些采用PHP技术的IT大
相关文章
    无相关信息
栏目更新
栏目热门