首页>JAVA>正文

【上海Java培训】Java基础之泛型详解

时间:2018-03-09 10:27:03   来源:上海尚学堂   阅读:
一、泛型定义与特点
1.为什么需要泛型

泛型在Java中有很重要的地位,网上很多文章罗列各种理论,不便于理解,本篇将立足于代码介绍、总结了关于泛型的知识。希望能给你带来一些帮助。

先看下面的代码:

 

  1. List list = new ArrayList();  
  2. list.add("CSDN_SEU_Cavin");  
  3. list.add(100);  
  4. for (int i = 0; i < list.size(); i++) {  
  5.   String name = (String) list.get(i); //取出Integer时,运行时出现异常  
  6. System.out.println("name:" + name);  
  7. }  

 

本例向list类型集合中加入了一个字符串类型的值和一个Integer类型的值。(这样合法,因为此时list默认的类型为Object类型)。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他原因,运行时会出现java.lang.ClassCastException异常。为了解决这个问题,泛型应运而生。
 

2.泛型只在编译阶段有效

看下面的代码:

  1. AyyayList<String> a = new ArrayList<String>();  
  2. ArrayList b = new ArrayList();  
  3. Class c1 = a.getClass();  
  4. Class c2 = b.getClass();  
  5. System.out.println(a == b); //true  

 

上面程序的输出结果为true。所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施,也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

 

上述结论可通过下面反射的例子来印证:

  1. ArrayList<String> a = new ArrayList<String>();  
  2. a.add("CSDN_SEU_Cavin");  
  3. Class c = a.getClass();  
  4. try{  
  5.     Method method = c.getMethod("add",Object.class);  
  6.     method.invoke(a,100);  
  7.     System.out.println(a);  
  8. }catch(Exception e){  
  9.     e.printStackTrace();  
  10. }  

 

因为绕过了编译阶段也就绕过了泛型,输出结果为:

  1. [CSDN_SEU_Cavin, 100]  

 

二、各种泛型定义及使用

1、泛型类定义及使用

我们先看看泛型的类是怎么定义的:
[java] view plain copy
 
  1. //定义  
  2. class Point<T>{// 此处可以随便写标识符号   
  3.     private T x ;        
  4.     private T y ;        
  5.     public void setX(T x){//作为参数  
  6.         this.x = x ;  
  7.     }  
  8.     public void setY(T y){  
  9.         this.y = y ;  
  10.     }  
  11.     public T getX(){//作为返回值  
  12.         return this.x ;  
  13.     }  
  14.     public T getY(){  
  15.         return this.y ;  
  16.     }  
  17. };  
  18. //IntegerPoint使用  
  19. Point<Integer> p = new Point<Integer>() ;   
  20. p.setX(new Integer(100)) ;   
  21. System.out.println(p.getX());    
  22.   
  23. //FloatPoint使用  
  24. Point<Float> p = new Point<Float>() ;   
  25. p.setX(new Float(100.12f)) ;   
  26. System.out.println(p.getX());    
先看看运行结果:

从结果中可以看到,我们实现了开篇中IntegerPoint类和FloatPoint类的效果。下面来看看泛型是怎么定义及使用的吧。

(1)、定义泛型:Point<T>
首先,大家可以看到Point<T>,即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是T,其实这个字母可以是任何大写字母,大家这里先记着,可以是任何大写字母,意义是相同的。
(2)类中使用泛型
这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。为方便起见,大家可以在这里把T当成String,即String在类中怎么用,那T在类中就可以怎么用!所以下面的:定义变量,作为返回值,作为参数传入的定义就很容易理解了。

[java] view plain copy
 
  1. //定义变量  
  2. private T x ;   
  3. //作为返回值  
  4. public T getX(){   
  5.     return x ;    
  6. }    
  7. //作为参数  
  8. public void setX(T x){    
  9.     this.x = x ;    
  10. }   
(3)使用泛型类
下面是泛型类的用法:
[java] view plain copy
 
  1. //IntegerPoint使用  
  2. Point<Integer> p = new Point<Integer>() ;   
  3. p.setX(new Integer(100)) ;   
  4. System.out.println(p.getX());    
  5.   
  6. //FloatPoint使用  
  7. Point<Float> p = new Point<Float>() ;   
  8. p.setX(new Float(100.12f)) ;   
  9. System.out.println(p.getX());    
首先,是构造一个实例:
[java] view plain copy
 
  1. Point<String> p = new Point<String>() ;   
这里与普通构造类实例的不同之点在于,普通类构造函数是这样的:Point p = new Point() ; 
而泛型类的构造则需要在类名后添加上<String>,即一对尖括号,中间写上要传入的类型。
因为我们构造时,是这样的:class Point<T>,所以在使用的时候也要在Point后加上类型来定义T代表的意义。
然后在getVar()和setVar()时就没有什么特殊的了,直接调用即可。
从上面的使用时,明显可以看出泛型的作用,在构造泛型类的实例的时候:
[java] view plain copy
 
  1. //IntegerPoint使用  
  2. Point<Integer> p = new Point<Integer>() ;   
  3. //FloatPoint使用  
  4. Point<Float> p = new Point<Float>() ;   
尖括号中,你传进去的是什么,T就代表什么类型。这就是泛型的最大作用,我们只需要考虑逻辑实现,就能拿给各种类来用。
前面我们提到ArrayList也是泛型,我们顺便它的实现:
[java] view plain copy
 
  1. public class ArrayList<E>{  
  2.     …………  
  3. }  
看到了吧,跟我们的Point实现是一样的,这也就是为什么ArrayList能够盛装各种类型的主要原因。
(4)使用泛型实现的优势
相比我们开篇时使用Object的方式,有两个优点:
(1)、不用强制转换
[java] view plain copy
 
  1. //使用Object作为返回值,要强制转换成指定类型  
  2. Float floatX = (Float)floatPoint.getX();  
  3. //使用泛型时,不用强制转换,直接出来就是String  
  4. System.out.println(p.getVar());   
(2)、在settVar()时如果传入类型不对,编译时会报错

可以看到,当我们构造时使用的是String,而在setVar时,传进去Integer类型时,就会报错。而不是像Object实现方式一样,在运行时才会报强制转换错误。

2、多泛型变量定义及字母规范

(1)、多泛型变量定义
上在我们只定义了一个泛型变量T,那如果我们需要传进去多个泛型要怎么办呢?
只需要在类似下面这样就可以了:
[java] view plain copy
 
  1. class MorePoint<T,U>{  
  2. }  
也就是在原来的T后面用逗号隔开,写上其它的任意大写字母即可。想加几个就加几个,比如我们想加五个泛型变量,那应该是这样的:
[java] view plain copy
 
  1. class MorePoint<T,U,A,B,C>{  
  2. }  
举个粟子,我们在Point上再另加一个字段name,也用泛型来表示,那要怎么做?代码如下:
[java] view plain copy
 
  1. class MorePoint<T,U> {  
  2.     private T x;  
  3.     private T y;         
  4.   
  5.     private U name;  
  6.   
  7.     public void setX(T x) {  
  8.         this.x = x;  
  9.     }  
  10.     public T getX() {  
  11.         return this.x;  
  12.     }  
  13.     …………  
  14.     public void setName(U name){  
  15.         this.name = name;  
  16.     }  
  17.   
  18.     public U getName() {  
  19.         return this.name;  
  20.     }  
  21. }  
  22. //使用  
  23. MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();  
  24. morePoint.setName("harvic");  
  25. Log.d(TAG, "morPont.getName:" + morePoint.getName());  
从上面的代码中,可以明显看出,就是在新添加的泛型变量U用法与T是一样的。
(2)、字母规范
在定义泛型类时,我们已经提到用于指定泛型的变量是一个大写字母:
[java] view plain copy
 
  1. class Point<T>{  
  2.  …………  
  3. }  
当然不是的!!!!任意一个大写字母都可以。他们的意义是完全相同的,但为了提高可读性,大家还是用有意义的字母比较好,一般来讲,在不同的情境下使用的字母意义如下:
  •  E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
  •  K,V — Key,Value,代表Map的键值对
  •  N — Number,数字
  •  T — Type,类型,如String,Integer等等
如果这些还不够用,那就自己随便取吧,反正26个英文字母呢。
再重复一遍,使用哪个字母是没有特定意义的!只是为了提高可读性!!!!

3、泛型接口定义及使用

在接口上定义泛型与在类中定义泛型是一样的,代码如下:

[java] view plain copy
 
  1. interface Info<T>{        // 在接口上定义泛型    
  2.     public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型    
  3.     public void setVar(T x);  
  4. }    

与泛型类的定义一样,也是在接口名后加尖括号;
(1)、使用方法一:非泛型类
但是在使用的时候,就出现问题了,我们先看看下面这个使用方法:

[java] view plain copy
 
  1. class InfoImpl implements Info<String>{   // 定义泛型接口的子类  
  2.     private String var ;                // 定义属性  
  3.     public InfoImpl(String var){        // 通过构造方法设置属性内容  
  4.         this.setVar(var) ;  
  5.     }  
  6.     @Override  
  7.     public void setVar(String var){  
  8.         this.var = var ;  
  9.     }  
  10.     @Override  
  11.     public String getVar(){  
  12.         return this.var ;  
  13.     }  
  14. }  
  15.   
  16. public class GenericsDemo24{  
  17.     public  void main(String arsg[]){  
  18.         InfoImpl i = new InfoImpl("harvic");  
  19.         System.out.println(i.getVar()) ;  
  20.     }  
  21. };  
首先,先看InfoImpl的定义:
[java] view plain copy
 
  1. class InfoImpl implements Info<String>{     
  2.  …………  
  3. }  
要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有<T>!
然后在在这里我们将Info<String>中的泛型变量T定义填充为了String类型。所以在重写时setVar()和getVar()时,IDE会也我们直接生成String类型的重写函数。
最后在使用时,没什么难度,传进去String类型的字符串来构造InfoImpl实例,然后调用它的函数即可。
[java] view plain copy
 
  1. public class GenericsDemo24{  
  2.     public  void main(String arsg[]){  
  3.         InfoImpl i = new InfoImpl("harvic");  
  4.         System.out.println(i.getVar()) ;  
  5.     }  
  6. };  
(2)、使用方法二:泛型类

在方法一中,我们在类中直接把Info<T>接口给填充好了,但我们的类,是可以构造成泛型类的,那我们利用泛型类来构造填充泛型接口会是怎样呢?

[java] view plain copy
 
  1. interface Info<T>{        // 在接口上定义泛型  
  2.     public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
  3.     public void setVar(T var);  
  4. }  
  5. class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
  6.     private T var ;             // 定义属性  
  7.     public InfoImpl(T var){     // 通过构造方法设置属性内容  
  8.         this.setVar(var) ;    
  9.     }  
  10.     public void setVar(T var){  
  11.         this.var = var ;  
  12.     }  
  13.     public T getVar(){  
  14.         return this.var ;  
  15.     }  
  16. }  
  17. public class GenericsDemo24{  
  18.     public static void main(String arsg[]){  
  19.         InfoImpl<String> i = new InfoImpl<String>("harvic");  
  20.         System.out.println(i.getVar()) ;  
  21.     }  
  22. };  
最关键的是构造泛型类的过程:
[java] view plain copy
 
  1. class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
  2.     private T var ;             // 定义属性  
  3.     public InfoImpl(T var){     // 通过构造方法设置属性内容  
  4.         this.setVar(var) ;    
  5.     }  
  6.     public void setVar(T var){  
  7.         this.var = var ;  
  8.     }  
  9.     public T getVar(){  
  10.         return this.var ;  
  11.     }  
  12. }  
在这个类中,我们构造了一个泛型类InfoImpl<T>,然后把泛型变量T传给了Info<T>,这说明接口和泛型类使用的都是同一个泛型变量。
然后在使用时,就是构造一个泛型类的实例的过程,使用过程也不变。
[java] view plain copy
 
  1. public class GenericsDemo24{  
  2.     public static void main(String arsg[]){  
  3.         Info<String> i = new InfoImpl<String>("harvic");  
  4.         System.out.println(i.getVar()) ;  
  5.     }  
  6. };  
使用泛型类来继承泛型接口的作用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。
那我们稍微加深点难度,构造一个多个泛型变量的类,并继承自Info接口:
[java] view plain copy
 
  1. class InfoImpl<T,K,U> implements Info<U>{   // 定义泛型接口的子类  
  2.      private U var ;      
  3.      private T x;  
  4.      private K y;  
  5.      public InfoImpl(U var){        // 通过构造方法设置属性内容  
  6.          this.setVar(var) ;  
  7.      }  
  8.      public void setVar(U var){  
  9.          this.var = var ;  
  10.      }  
  11.      public U getVar(){  
  12.          return this.var ;  
  13.      }  
  14.  }  
在这个例子中,我们在泛型类中定义三个泛型变量T,K,U并且把第三个泛型变量U用来填充接口Info。所以在这个例子中Info所使用的类型就是由U来决定的。
使用时是这样的:泛型类的基本用法,不再多讲,代码如下:
[java] view plain copy
 
  1. public class GenericsDemo24{  
  2.     public  void main(String arsg[]){  
  3.         InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");  
  4.         System.out.println(i.getVar()) ;  
  5.     }  
  6. }  

 

4、泛型类和泛型方法

如下,我们看一个泛型类和方法的使用例子,和未使用泛型的使用方法进行了对比,两者输出结果相同,在这里贴出来方便读者体会两者的差异。泛型接口的例子有兴趣可以去找一些资料,这里就不赘述了。

(1)使用泛型的情况

  1. public static class FX<T> {  
  2.     private T ob; // 定义泛型成员变量  
  3.   
  4.     public FX(T ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public T getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showTyep() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.     public static void main(String[] args) {  
  17.         FX<Integer> intOb = new FX<Integer>(100);  
  18.         intOb.showTyep();  
  19.         System.out.println("value= " + intOb.getOb());  
  20.         System.out.println("----------------------------------");  
  21.   
  22.         FX<String> strOb = new FX<String>("CSDN_SEU_Calvin");  
  23.         strOb.showTyep();  
  24.         System.out.println("value= " + strOb.getOb());  
  25. }  

(2)不使用泛型的情况

  1. public static class FX {  
  2.     private Object ob; // 定义泛型成员变量  
  3.   
  4.     public FX(Object ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public Object getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showTyep() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.   
  17.     public static void main(String[] args) {  
  18.         FX intOb = new FX(new Integer(100));  
  19.         intOb.showTyep();  
  20.         System.out.println("value= " + intOb.getOb());  
  21.         System.out.println("----------------------------------");  
  22.   
  23.         FX strOb = new FX("CSDN_SEU_Calvin");  
  24.         strOb.showTyep();  
  25.         System.out.println("value= " + strOb.getOb());  
  26.     }  

 

输出结果均为:

  1. T的实际类型是: java.lang.Integer  
  2. value= 100  
  3. ----------------------------------  
  4. T的实际类型是: java.lang.String  
  5. value= CSDN_SEU_Calvin  
感谢阅读上海Java培训文章,
文章参考链接:http://blog.csdn.net/wangzhiguo9261/article/details/78908281 ;https://www.cnblogs.com/lzq198754/p/5780426.html
 
分享:0

电话咨询

客服热线服务时间

周一至周五 9:00-21:00

周六至周日 9:00-18:00

咨询电话

021-67690939
15201841284

微信扫一扫