泛型
泛型的原理
类型擦除
类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List<String>和List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。
既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?
擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。
泛型的基本使用(查看jdk帮助文档某个类是否可以使用泛型)
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息<>中的类型,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
泛型的术语(用于交流):
整个ArrayList<E>称为泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译器报警告,例如,
Collection<String> c = new Vector();//如果通不过,以前写的程序就全废了
原始类型可以引用一个参数化类型的对象,编译器报警告,例如,
Collection c = new Vector<String>();//原来的方法接收一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>();//错误
Vector<Object> v = new Vector<String>();//也错误
在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面的代码会报错误吗?不会,编译器是一行一行执行的
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
泛型中的?通配符:
使用?通配符可以引用其他各种参数化的类型。?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
public static void printCollection(Collection<?> collection){
//collection.add(1);不能调用这个方法,因为它与参数有关(因为它不知自己未来匹配就一定是String)
collection.size();//可以调用,因为与类型参数无关
collection = new HashMap<Date>();//可以
}
泛型中的?通配符的扩展
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer();
//表示?可以是任意类型,但必须是Number或者是Number的子类,设定了上边界,必须是它的子类
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
//设定下边界,?表示可以是任意类型,但必须是Integer上面的类是Integer的父类
错误:Vector<? super Integer> x = new Vector<Byte>();
存取原则和PECS法则
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符。
java泛型与c++比较:java中的泛型类型类似于c++中的模板。但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器中实现的,用于编译器执行类型检查和类型推断,然后生成普通的非泛型字节码,这种实现技术称为擦除(编译器使用泛型类型信息保证类型安全,然后再生成字节码之前将其擦除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级JVM造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
java泛型方法没有c++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y){
return (T)(x + y);//因为x和y不知道有没有 +这个方法
}
泛型方法:
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报编译错误(因为new int[3]本身已经是对象了)
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
普通方法,构造方法和静态方法中都可以使用泛型
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。如:
private static <T extends Exception> sayHello() throws T{try{}catch(Exception e){throw(T)e;}}
在泛型中可以同时有多个类型参数,在定义他它们的尖括号中用逗号分,例如:public static <K,V> getValue(K key){return map.get(key);}
使用泛型方法还是?通配符:在一个类型变量用来表达两个参数或者参数和返回值之间的关系时,即同一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需呀使用泛型方法。
类型参数的类型推断:
编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4)->static <E> void swap(E[] a ,int i,int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5)->static <T> add(T a,T b)
当某个类型变量在整个参数列表中的所有参数和返回值中多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候多个参数中的最大交集类型,例如:下面语句实际对应的类型就是Number了,编译阶段没问题,只是运行时出问题:
fill(new Integer[3],3.5f)->static <T> void fill(T[] a, T y)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x = (3,3.5f) -> static <T> add(T a,T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为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> void copy(Collection<T> a,T[] b);
泛型类(型)
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T>{
private T field;
public void save(T obj){}
public T getById(int id){}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new GenericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能使基本类型。
当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的参数类型。
问题:类中多个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?类级别的
为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:
当泛型方法是静态的时,这种情况下不能使用类类型参数。
当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个方法签名中使用相同类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。
以上为java泛型知识点总结.
1介绍
Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。下面是一个不用泛型例子:-
List myIntList=newLinkedList(); //1
myIntList.add(newInteger(0));//2
Integerx=(Integer)myIntList.iterator().next(); //3
这种转换不仅显得混乱,更可能导致类型转换异常ClassCastException,运行时异常往往让人难以检测到。保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会,这也是泛型设计的初衷。下面是一个使用了泛型的例子:
-
List<Integer>myIntList=newLinkedList<Integer>(); //1’
myIntList.add(newInteger(0));//2’
Integerx=myIntList.iterator().next(); //3’
在第1行代码中指定List中存储的对象类型为Integer,这样在获取列表中的对象时,不必强制转换类型了。
2定义简单的泛型
下面是一个引用自java.util包中的接口List和Iterator的定义,其中用到了泛型技术。
-
void add(E x);
Iterator<E> iterator();
}
public interfaceIterator<E> {
E next();
boolean hasNext();
}
也许可以这样认为,List<Integer>表示List中的类型参数E会被替换成Integer。
-
public interfaceIntegerList {
void add(Integer x)
Iterator<Integer> iterator();
}
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。此外,需要注意的是,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以,若static方法需要使用泛型能力,必须使其成为泛型方法。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List<String>和List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。因为编译的时候会有类型擦除,所以不能通过同一个泛型类的实例来区分方法,如下面的例子编译时会出错,因为类型擦除后,两个方法都是List类型的参数,因此并不能根据泛型类的类型来区分方法。
/*会导致编译时错误*/ |
3.泛型和子类型
为了彻底理解泛型,这里看个例子:(Apple为Fruit的子类)
List<Apple>apples = new ArrayList<Apple>(); //1 List<Fruit> fruits = apples; //2 |
4.通配符
4.1通配符?
先看一个打印集合中所有元素的代码。
//不使用泛型 voidprintCollection(Collection c) { Iterator i=c.iterator(); for (k=0;k < c.size();k++) { System.out.println(i.next()); } } | //使用泛型 voidprintCollection(Collection<Object> c) { for (Object e:c) { System.out.println(e); } } |
//使用通配符?,表示可以接收任何元素类型的集合作为参数 voidprintCollection(Collection<?> c) { for (Object e:c) { System.out.println(e); } } |
Collection<?>c=new ArrayList<String>(); c.add(newObject());//compile time error,不管加入什么对象都出错,除了null外。 c.add(null); //OK |
4.2边界通配符
1)?extends通配符
假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次: public abstractclass Shape { public abstract void draw(Canvas c); } } public class Circleextends Shape { private int x,y,radius; public void draw(Canvas c) { ... } } } public classRectangle extends Shape private int x,y,width,height; public void draw(Canvasc) { ... } } } |
//原始版本 public voiddrawAll(List<Shape> shapes) { for (Shapes:shapes) { s.draw(this); } } | //使用边界通配符的版本 public voiddrawAll(List<?exendsShape> shapes) { for (Shapes:shapes) { s.draw(this); } } |
这里就又有个问题要注意了,如果我们希望在List<?exendsShape> shapes中加入一个矩形对象,如下所示:
shapes.add(0, new Rectangle()); //compile-timeerror
那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。
不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。
2)?super通配符
-
List<Shape>shapes = new ArrayList<Shape>();
List<? superCicle> cicleSupers = shapes;
cicleSupers.add(newCicle()); //OK, subclass of Cicle also OK
cicleSupers.add(new Shape()); //ERROR
3)边界通配符总结
-
如果你想从一个数据类型里获取数据,使用? extends 通配符
-
如果你想把对象写入一个数据结构里,使用? super 通配符
-
如果你既想存,又想取,那就别用通配符。
5.泛型方法
考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本: static voidfromArrayToCollection(Object[]a, Collection<?> c) { for (Object o:a) { c.add(o); //compiletime error } } |
static <T>void fromArrayToCollection(T[] a, Collection<T>c){ for(T o : a) { c.add(o);// correct } } |
Object[] oa = newObject[100]; Collection<Object>co= new ArrayList<Object>(); fromArrayToCollection(oa,co);// T inferred to be Object String[] sa = newString[100]; Collection<String>cs= new ArrayList<String>(); fromArrayToCollection(sa,cs);// T inferred to be String fromArrayToCollection(sa,co);// T inferred to be Object Integer[] ia = newInteger[100]; Float[] fa = newFloat[100]; Number[] na = newNumber[100]; Collection<Number>cn= new ArrayList<Number>(); fromArrayToCollection(ia,cn);// T inferred to be Number fromArrayToCollection(fa,cn);// T inferred to be Number fromArrayToCollection(na,cn);// T inferred to be Number fromArrayToCollection(na,co);// T inferred to be Object fromArrayToCollection(na, cs);// compile-timeerror |
public <T> voidgo(T t) { System.out.println("genericfunction"); } publicvoidgo(Stringstr) { System.out.println("normalfunction"); } publicstaticvoidmain(String[] args) { FuncGenricfg =newFuncGenric(); fg.go("haha");//打印normalfunction fg.<String>go("haha");//打印genericfunction fg.go(newObject());//打印genericfunction fg.<Object>go(newObject());//打印genericfunction } |
6.其他需要注意的小点
1)方法重载
在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。 /*代码一:编译时错误*/ public class Erasure{ | /*代码二:正确*/ public class Erasure{ |
2)泛型类型是被所有调用共享的
所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList<String>和ArrayList<Integer>类型参数不同,但是他们都共享ArrayList类,所以结果会是true。
List<String>l1= new ArrayList<String>(); List<Integer>l2= new ArrayList<Integer>(); System.out.println(l1.getClass() ==l2.getClass()); //True |
3)instanceof
不能对确切的泛型类型使用instanceOf操作。如下面的操作是非法的,编译时会出错。 Collection cs = newArrayList<String>(); if (cs instanceof Collection<String>){…}// compileerror.如果改成instanceofCollection<?>则不//会出错。 |
4)泛型数组问题
不能创建一个确切泛型类型的数组。如下面代码会出错。List<String>[]lsa = new ArrayList<String>[10];//compileerror.
因为如果可以这样,那么考虑如下代码,会导致运行时错误。
List<String>[]lsa = new ArrayList<String>[10]; //实际上并不允许这样创建数组 Object o = lsa; Object[] oa =(Object[]) o; List<Integer>li= new ArrayList<Integer>(); li.add(newInteger(3)); oa[1] = li;//unsound, but passes run time store check String s = lsa[1].get(0); //run-timeerror - ClassCastException |
List<?>[] lsa= new List<?>[10]; // ok, array of unbounded wildcard type Object o = lsa; Object[] oa =(Object[]) o; List<Integer>li= new ArrayList<Integer>(); li.add(newInteger(3)); oa[1] = li;//correct String s = (String)lsa[1].get(0);// run time error, but cast isexplicit Integer it = (Integer)lsa[1].get(0); // OK |
http://www.aqee.net/java-generics-quick-tutorial/
http://www.infoq.com/cn/articles/cf-java-generics
http://blog.csdn.net/daniel_h1986/article/details/5708605
http://www.cnblogs.com/stephen-liu74/archive/2012/01/20/2228938.html
sun官方文档:generics-tutorial.pdf
转载自:http://qiemengdao.iteye.com/blog/1525624
参考:
IBM系列:
Java 理论和实践: 了解泛型:http://www.ibm.com/developerworks/cn/java/j-jtp01255.html
Java 理论与实践: 使用通配符简化泛型使用:http://www.ibm.com/developerworks/cn/java/j-jtp04298.html
多角度看 Java 中的泛型:http://www.ibm.com/developerworks/cn/java/j-lo-gj/
诊断 Java 代码: 轻松掌握 Java 泛型:http://www.ibm.com/developerworks/cn/java/j-djc02113/
Java泛型-类型擦除:http://blog.csdn.net/caihaijiang/article/details/6403349
java泛型:http://www.cnblogs.com/panjun-Donet/archive/2008/09/27/1300609.html
掌握 Java 泛型类型(一):http://blog.csdn.net/kalex/article/details/195562
JAVA泛型详解:http://blog.csdn.net/mtawaken/article/details/9813581
http://blog.csdn.net/jinuxwu/article/details/6771121
java泛型(一)、泛型的基本介绍和使用:http://blog.csdn.net/lonelyroamer/article/details/7864531
Java泛型:类型檫除、模板和泛型传递:
http://shentar.me/java泛型:类型檫除、模板和泛型传递/