Java学习之 泛型

来源:互联网 发布:手机广告清理软件 编辑:程序博客网 时间:2024/04/30 06:57


1、 泛型概述

泛型是javaJDK1.5的新特性,它的本质是参数化类型,即所操作的数据类型不确定,那么就指定成一个参数的形式。


什么时候用呢?就是在类中操作引用类型不确定的时候,或者想避免显示转换的麻烦时。

在你要用的类、接口和方法后跟个<>,里面写上类型名E或泛型的通配符‘?’。

 

好处:

(1)将运行时期的类型转换问题转移到编译时期,保证了安全。

如,集合中可以存入不同类型数据,编译时期发现不了,但是运行时期就会出错,而集合采用泛型,就可以指定类型,存入其它类型是非法的,编译时期就进行检查。


(2)将显示的强制转换变成了自动和隐式的,提高代码的重用行。

[java] view plaincopy
  1. //如不用泛型时:  
  2. List lt = ….;  
  3. MyClass  mc = (MyClass)lt.get(0);//这个需要显示的强制转换,早期默认的是Object类型。  
  4. //用泛型:  
  5. List<MyClass>lt = ….;  
  6. MyClass mc =lt.get(0);  
  7. //这个不需要强制进行转换了,加了泛型,无法存入其他类型,否则编译报错。  

 

2、 泛型擦除

泛型技术主要应用在编译时期。在编译时期,编译器负责参数类型的检测,到了运行时期,即生成了字节码后,所包含的泛型定义的类型信息就不存在了(或说被擦除了),这就是泛型的类型擦除,在生成的字节码中无类型信息,这也是虚拟机的局限性导致的。

下面看下证明:

[java] view plaincopy
  1. import java.util.ArrayList;  
  2. public class GenericDemo  
  3. {  
  4. public static void main(String[] args)  
  5. {  
  6.         //泛型类型为String  
  7.         ArrayList<String> at1 = newArrayList<String>();  
  8.         //泛型类型为Integer  
  9.         ArrayList<Integer>at2 = newArrayList<Integer>();  
  10.          
  11.         //运行结果为true,说明都是同一份字节码,已擦除了泛型类型了  
  12.         System.out.println(at1.getClass() ==at2.getClass());  
  13. }  
  14. }  

这也说明,泛型是给编译器考虑的。我们如果跳过编译器,那么又可以往集合中存入不同的数据类型了,比如利用反射,因为反射主要是针对运行时期的操作,反射可以达到绕过编译器的目的。


3、 泛型的一些术语

ArrayList<E> 整个称为泛型类型,E称为类型变量或类型参数。

ArrayList<Integer> 整个称为参数化的类型,Integer称类型参数的实例或实际类型参数。

ArrayList<Integer> 中的<>念为typeof

ArrayList 称为原始类型。


4、 泛型的通配符

在传入的类型不确定时,就可以使用通配符,即‘?’,如<?>。

 

那么写<?>和写具体泛型参数<E>有什么不同呢?

通配符?不能进行具体对象的操作,即不能调用与参数化有关的方法,因为?是不确定的类型,不具备对象,只能用些跟对象无关的方法,如集合的ArrayList原始类型的size()方法,与集合元素类型无关。

而泛型参数E是有对象的,可进行具体的对象的操作,是种限定类型, 如迭代器输出时,可以E e = it.next();而后对e进行对象操作。也可进行(E)”abc”;之类的强制转换。

另外,在泛型结构的内部也要使用泛型参数时,就需定义泛型参数E了。

下面看一个例子来理解两者的区别,也体现了通配符的弊端:

[java] view plaincopy
  1. import java.util.ArrayList;  
  2.    
  3. public class GenericDemo  
  4. {  
  5.    public static void main(String[] args)  
  6.   {  
  7.   }  
  8.   public static void fun1(ArrayList<?> alt)  
  9.   {  
  10.         //这个add方法传入的类型不确定,无法使用,需根据传入值确定  
  11.         //alt.add(不确定);  
  12.         //像size是通用方法,不需确定类型  
  13.         int len = alt.size();  
  14.   }  
  15.   public static void fun2(ArrayList<String>alt)  
  16.   {  
  17.         alt.add("aaa");//以确定参数化类型为String  
  18.   }  
  19. }  


5、 泛型限定

若想限定某些范围内的类型,但又不是全部类型,则利用泛型限定。

(1)泛型上限

格式:<?extends E>

可以接收E类型或者E的子类型对象。当想往集合中添加E类型和E类型的子类对象时,或者在取元素时,既可取E类型和E的子类型时可以采用泛型上限。

如:

[java] view plaincopy
  1. ArrayList<?extends Number> alt = new ArrayList<Integer>();//正确,Integer是Number的子类  
  2. ArrayList<? extends Number> alt =new ArrayList<String>();//错误  

(2)泛型下限

 格式:<?super E>

可以接收E类型或者E的父类对象。当想从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

如:

[java] view plaincopy
  1. ArrayList<? super Integer> alt = new ArrayList<Number>();//正确  
  2. ArrayList<? super Integer> alt = new ArrayList<Byte>();//错误  

6、 泛型方法

为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。

格式:

[java] view plaincopy
  1. public  void fun0(Q q){}  
  2. public <T>void fun1(T t){}  
  3. public <E>  E  fun2(Ee){return null;}  
  4. public static<W> void fun4(W w){}  
 

特殊之处:静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。

调用时需注意,T是一种后确定的类型,所以在传参时,可能传入不同类型,那么该方法取参数间的交集类型,如:

[java] view plaincopy
  1. fun(3,4);//泛型要求为引用类型,这里自动装箱成Integer  
  2. Number num = fun(0.3,5);//浮点类型和整型共存,取交集类型Number  
  3. Object obj =fun(3, “abc”);//整型和String都是Object子类,取交集Object  

泛型方法的一些特点:

(1)<>的位置,它应放在修饰符和返回类型之间。

(2)只有引用类型才能作为泛型方法的实际参数,可自动装箱的除外,如上述的fun(3,4),但若有声明public <E>void swap(E[]a){},却不能swap(newint[])这样调用,int[]已经是个int型数组对象了,无法自动装箱。

(3)在定义泛型时可以使用extends限定符,并且可以使用&连接多个边界。

例如:public <E extends Number> Efun(E e){}

&连接多边界:<Vextends Serializable&cloneable> void method();

(4)普通方法、构造方法和静态方法都可以使用泛型。

(5)也可以用类型表示异常,称为参数化异常,可以用于throws列表中,但是不能用于catch子句中。

如:

[java] view plaincopy
  1. //定义泛型异常  
  2. public static <T extends Exception> void fun() throws T  
  3. {  
  4.        try  
  5.        {  
  6.        }  
  7.        catch(Exception e)//不能用泛型类型T  
  8.        {  
  9.               throw(T)e;//可进行异常的转换  
  10.        }  
  11. }  

(6)在泛型中可以同时有多个类型参数,在定义他们的<>中用逗号隔开

              例如:public static <K,V> VgetValue(K key){return map.get(key)}


7、 泛型类

(1)是什么:类级别的泛型,即在类上定义泛型参数,那么该类的实例对象都要使用和保持同一个实际类型。

(2)什么时候用:当类中操作的数据类型不确定时,以前是通过定义Object来扩展的,现在可以使用泛型。

(3)怎么用:看格式

[java] view plaincopy
  1. class GenericClass<T>  
  2. {  
  3.             private T field;  
  4.             public GenericClass(T field){  
  5.                this.field = field;  
  6.             }  
  7.             public void fun(T t){}  
  8.             public T getFun(){return null;}//带返回值  
  9. }  

引用泛型类:类级别的泛型是根据引用该类时指定的类型信息来参数化类型变量的,看引用格式:

GenericClass<String> gc = new GenericClass<String>();


(4)一些特点

(a)泛型类定义的泛型,在整个类中都是有效的,如果被方法调用,那么泛型类的对象明确需要操作的具体类型后,所有要操作的类就固定了。

(b)在对泛型类进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

(c)当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。原因:因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数,也就是说类的泛型参数是针对对象的。


8、 泛型的定义

(1)   定义泛型:当有不确定的类型需要传入到集合中时,需要定义泛型。

(2)   定义泛型类:如果类型确定后,所操作的方法都属于此类型,则定义泛型类。

(3)   定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法。


9、 泛型参数的类型推断

编译器判断泛型的实际参数的过程称之为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

 

根据调用泛型方法时实际传递的参数类型或返回值的类型推断,具体规则如下:

(1)   当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

swap(newString[],3,4)àstatic<E>void swap(E[],inti,int j);


(2)   当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭感觉推断出来,例如:

add(3,4)àstatic <T> T add(T  a,T  b);


(3)   当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时,这多处的实际应用类型都对应到了不同的类型,且没有返回值,这时取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是取得交集Number了,编译没有问题,只是运行时出问题:

fill(newInteger[],3.5f)àstatic <T> void fill(T[]  a, T v);


(4)   当某个类型变量在整个参数列表中的和返回值中的多处被应用了,如果调用方法时,这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面的语句实际对应的类型是Integer,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

int x=add(3,3.5f)àstatic <T> T add(T  a, T b);


(5)   参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将变量直接确定为String类型,编译将出现问题:

情况一:copy (new  Integer[5],new  String[5])-->  static <T> void copy(T a, T  b);

情况二:copy(new Vector<String>(),new Integer[5])-->  static<T> voidcopy(Collection<T> a, T[]  b);


10、泛型定义在接口上

[java] view plaincopy
  1. //格式:  
  2. Interface Inter<T>  
  3. {  
  4.        void show(T  t);  
  5. }  
  6. class InterImpl<T> implements Inter<T>  
  7. {  
  8.        public void show(T  t)  
  9.        {  
  10.               System.out.println("show:"+t);  
  11.        }  
  12. }  

接口泛型的定义规则与类泛型相似。


11、 通过反射获得泛型的参数化类型

先看下面代码:

[java] view plaincopy
  1. importjava.lang.reflect.Method;  
  2. import java.lang.reflect.ParameterizedType;  
  3. importjava.lang.reflect.Type;  
  4. importjava.util.Date;  
  5. importjava.util.Vector;  
  6.    
  7. public class GenericReflect  
  8. {  
  9.      public static voidmain(String[] args) throws Exception  
  10.      {  
  11.             //获取fun方法  
  12.             Method method =GenericReflect.class.getMethod("fun", Vector.class);  
  13.             //Type类型是Calss的父类,Method类中提供了获取泛型参数的方法  
  14.             Type[] types =method.getGenericParameterTypes();  
  15.             // ParameterizedType是Type的子类  
  16.             ParameterizedTypepType = (ParameterizedType)types[0];  
  17.             //获取泛型的原始类型  
  18.             System.out.println(pType.getRawType());  
  19.             //获取泛型的参数变量  
  20.             System.out.println(pType.getActualTypeArguments()[0]);  
  21.      }  
  22.      public static voidfun(Vector<Date> vd)  
  23.      {  
  24.      }  

通过反射是不能直接获取Vector的类型的,但是它通过泛型方法,利用反射却可以知道Vector<Date>的原始类型及参数类型。

http://blog.csdn.net/zzbxsdby/article/details/11602139
原创粉丝点击