在java的早期版本中,经常会通过对类型Object的引用来实现参数的“任意化”,这种“任意化”必然附带着显示强制类型转换,而这种转换是要求程序员在对实际参数类型预先知道的情况下进行的。为了解决上述问题,就出现了泛型语法。
1.为什么要使用泛型?
查看java语言API的帮助文档,经常会遇到类的后面跟着<E>标识,例如Vector<E>、ArrayList<E>等,其实<E>标志就是代表泛型。所谓泛型,其本质就是实现参数化类型,也就是说所操作的数据类型被指定为一个参数。
为了能够彻底理解泛型,下面通过一个对集合操作的例子来解释泛型出现的原因。
(1)创建学生类:
package com.ccniit.lg.泛型;public class Student { private int stuNum; public void setStuNum (int stuNum) { this.stuNum = stuNum; } public int getStuNum () { return this.stuNum; } public Student (int num) { setStuNum(num); } public String toString () { return this.stuNum+""; }}
(2)创建TestStudent类,将学生对象添加到Vector中。
package com.ccniit.lg.泛型;import java.util.Vector;public class TestStudent { public static void main (String[] args) { Vector vector = new Vector(); for (int i = 1;i < 6;i++) { Student student = new Student(i); vector.add(student); } Integer integer = new Integer(5); vector.add(integer); for (int i = 0;i < vector.size();i++) { System.out.println((Student) vector.get(i)); } }}
运行结果如下:
12345Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to com.ccniit.lg.泛型.Student at com.ccniit.lg.泛型.TestStudent.main(TestStudent.java:15)
上述代码虽然编译通过,但是在运行时出现了异常-ClassCastException。这主要是遍历集合对象vector的最后一个对象时,由于该对象integer是Integer类型对象而不是Student对象,所以在运行强制转换(Student) vector.get(i)代码时会出错。
(3)对于上述代码存在一种安全隐患,即强制类型转换错误时,编译器是不会提醒错误的,但是在运行时会出现异常。为了解决这个问题,可以在Vector添加方法中限制只能添加Student类型的对象。代码如下:
package com.ccniit.lg.泛型;import java.util.Vector;public class LimitedStudentVectory { private Vector vector = new Vector(); public void add (Student student) { vector.add(student); } public Student get (int i) { return (Student) vector.get(i); } public int size () { return vector.size(); } public static void main (String[] args) { LimitedStudentVectory limitedStudentVectory = new LimitedStudentVectory(); for (int i = 1;i < 6;i++) { Student student = new Student(i); limitedStudentVectory.add(student); } Integer integer = new Integer(7); //limitedStudentVectory.add(integer); for (int i = 0;i < limitedStudentVectory.size();i++) { System.out.println(limitedStudentVectory.get(i)); } }}
运行结果如下:
12345
代码解析:
a.在上述代码中,通过定义add()方法来限制添加对象的类型必须为Student,通过get()方法来限制得到的值必须为Student类型的对象。
b.在上述代码中,如果不注释limitedStudentVectory.add(integer)便会出现编译错误。
(4)上述代码虽然解决了TestStudent类的问题,但是要写很多代码。为了解决这个问题,可以使用泛型。如下:
package com.ccniit.lg.泛型;import java.util.Vector;public class GenericStudentVectory { public static void main (String[] args) { Vectorvector = new Vector (); for (int i = 1;i < 6;i++) { Student student = new Student(i); vector.add(student); } Integer integer = new Integer(7); //vector.add(integer); for (int i = 0;i < vector.size();i++) { System.out.println(vector.get(i)); } }}
运行结果为:
12345
在上述代码中,通过"Vector<Student> vector = new Vector<Student>()"限制了集合vector中的对象只能是Student类型。如果一定要添加"vector.add(integer)",编译器会报错。
2.泛型的一些特性:
在最新版的java语法中,希望在定义集合类时,明确表示要向集合中添加哪种类型的数据。于是java语言API的帮助文档中,集合类的后面都跟着<E>标志。例如:Vector的定义:Vector<E>
在上述定义中,此处的<E>指定Vector对象中只能存放E这种类型的对象。其中Vector<E>称为Vector泛型类型,E称为类型变量或者类型参数,Vector称为原始类型。
Vector<Integer> v = new Vector<Integer>();
在代码中Vector<Integer>称为参数化类型,Integer称为类型参数的实例或者实例类型参数,<>称为typeof。下面将一些泛型的特点列出来:
(1)参数化类型与原始类型兼容
当参数化类型引用一个原始类型的对象时,编译器只是警告而不报错;同样当原始类型引用一个参数化类型对象时,编译器也只警告不报错。如:
Vector<String> v = new Vector();
Vector v = new Vector<String>();
(2)参数化类型无继承性
例如:
Vector<String> v = new Vector<String>();
Vector<Object> v1 = v;
这段代码会报错。
有些人会认为由于String类型是Object类型的子类,所以String类型的Vector对象(v)可以赋给Object类型的Vector对象(v1)。
v1.add(new Object());
String s = v.get(0);
假如v可以赋给v1,上面代码首先v1对象添加一个Object类型对象成员,接着由于v1与v都指向同一个对象,所以可以通过v对象获取添加到v1对象中的成员。这时就会出现错误,因为v对象中的成员不再是String类型。所以代码"Vector<Object> v1 = v"是错误的。
这就好比教育部提供一个教师信息表给人口普查局,人口普查局可能向教师信息表里添加学生等信息,这就破坏了教师信息记录。
(3)泛型的“去类型”特性
所谓“去类型”,就是指泛型中的类型只是提供给编译器用的,当程序编译成功后就会去掉“类型”信息。下面通过代码来演示这个特性:
package com.ccniit.lg.泛型;import java.util.ArrayList;public class AdvancedGeneric { public static void main (String[] args) { ArrayListarrayList = new ArrayList (); arrayList.add("测试数据"); ArrayList arrayList2 = new ArrayList (); arrayList2.add(20); System.out.println("arrayList和arrayList2是否指向同一份字节码:"+(arrayList.getClass() == arrayList2.getClass())); }}
运行结果如下:
arrayList和arrayList2是否指向同一份字节码:true
代码解析:
a.泛型的作用只是限制集合中的输入类型,让编译器挡住源程序中的非法输入。即编译器能够辨别出arrayList集合中只能添加String类型对象,而arrayList2只能添加Integer类型对象。当向两个对象中添加其他类型对象时,编译器会报错。
b.代码"arrayList.getClass() == arrayList2.getClass()"的结果为true,说明编译器生成的对象arrayList集合和arrayList2集合的字节码为同一个对象。
通过以上代码运行结果可以发现:编译器编译带参数说明的集合时会去掉“类型”信息。这样做有一个好处,程序具体运行时将不会受到泛型影响。
(4)利用反射绕过泛型的类型限制
由于编译器生成的字节码会去掉泛型的类型信息,所以只要能跳过编译器,还是可以给通过泛型限制类型的集合中加入其它类型的数据。下面通过一个例子来演示:
package com.ccniit.lg.泛型;import java.lang.reflect.InvocationTargetException;import java.util.ArrayList;public class ReflectionGeneric { public static void main (String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { ArrayListarrayList = new ArrayList (); arrayList.add(23); arrayList.add(45); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "abc"); for (int i = 0;i < arrayList.size();i++) { System.out.println(arrayList.get(i)); } }}
运行结果为:
2345abc
代码解析:
在代码"arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "abc")"中,首先通过arrayList.getClass()得到arrayList的字节码,然后通过getMethod("add",Object.class)得到字节码中的add()方法,其参数为一个Object类型。接着通过invoke()方法实现调用对象arrayList的add("abc")方法,即向对象arrayList添加一个字符串对象。
通过上述代码的运行结果可以看出,虽然泛型限制了对象arrayList的类型只能是Integer类型,但是可以通过反射绕过编译器向对象arrayList中添加一个字符串对象abc。
3.泛型的通配符:
查看java语言API的帮助文档,经常会遇到类的后面跟着<?>、<? extends U>和<? super U>等标识,例如:<U> class <? extends U>等。如果想要彻底了解这些通配符,则需要从为什么要出现通配符开始。
从上面的几节内容可以发现,泛型可以实现类型的参数化,但是在具体定义时却使得泛型类型固定化。如何实现参数的类型可以类型化?例如,如何定义一个能接受任意参数化类型的集合方法?具体步骤如下:
(1)有的程序员通过设置方法接受的类型参数的实例为Object来实现。如下:
package com.ccniit.lg.泛型;import java.util.Vector;public class RandomGenerObject { public static void main (String[] args) { Vectorvector = new Vector (); Vector
运行结果如下:
测试数据1123
代码解析:
a.在randomMeth()方法中,对于Vector<Object>泛型,实现了可以向该参数中添加任何类型的对象。这是因为任何对象的基类都是Object类型,所任何类型的对象都可以自动转换为Object类型。
b.在具体调用randomMeth()方法时,只要传入的参数为Object类型泛型对象,才会正确运行。如果是其他类型泛型对象,则会编译出错。这是因为参数化类型无继承性。
(2)从RandomGenerObject类的最后实现可以看出,参数类型为Object类型泛型的方法randomMeth()并能接受任何类型的参数。为了实现该功能,在泛型中出现了"?"标识。如下:
package com.ccniit.lg.泛型;import java.util.Vector;public class RandomGener { public static void main (String[] args) { Vectorvector = new Vector (); vector.add(1); vector.add(2); Vector vector2 = new Vector (); vector2.add("aa"); vector2.add(2.2); randomMeth (vector); randomMeth(vector2); } private static void randomMeth(Vector vector) { System.out.println("输出"+vector+"各个成员----------"); for (Object obj : vector) { System.out.println(obj); } System.out.println("对象的大小:"+vector.size()); }}
最后运行结果为:
输出[1, 2]各个成员----------12对象的大小:2输出[aa, 2.2]各个成员----------aa2.2对象的大小:2
代码解释:
a.在randomMeth()方法中,通过"?"标识符实现接受任何类型参数的方法,即在具体调用该方法时,传入的对象可以是任意类型,例如Integer类型的对象vector和Object类型的对象vector2。
b.在randomMeth()方法中,虽然可以接受任何类型的参数,但是具体接受什么类型只有在具体调用方法时才能确定。因此在该方法中添加确定类型的成员(vector.add("1"))时,则会编译错误。可是如果调用与参数类型无关的方法(vector.size()),则编译不会报错。
(3)下面用一段代码来演示<? extends U>和<? super U>的用法:
package com.ccniit.lg.泛型;import java.util.AbstractList;import java.util.ArrayList;public class IntegrationGener { public static void main (String[] args) { Number num1 = new Integer(1); Number num2 = new Double(1.23); AbstractListlistNums = new ArrayList (); listNums.add(1); listNums.add(1.23); AbstractList listInteger = new ArrayList (); AbstractList listNums1 = new ArrayList (); AbstractList listNums2 = listInteger; AbstractList listNums3 = listInteger; listNums3.add(7); listNums3.add(null); System.out.println("listNums2中的元素:"+listNums2.get(0)); AbstractList listNums4 = listNums1; listNums4.add(6); listNums3.add(null); }}
代码解析:
a.对象num1和num2的定义代码,子类(Integer、Double)可以自动转换为其父类(Number)。同理,ArrayList<Number>泛型也可以自动转换成AbstractList<Number>泛型,因为AbstractList为ArrayList的父类。
b.AbstractList<Integer>的对象listInteger,之所以不能赋值给AbstractList<Number>类型的对象listNums1,是因为泛型的参数类型无继承性。因此虽然Integer时Number的子类,但是充当泛型的参数时,则不能实现转换。
c.为了解决泛型的参数类型无继承性,出现了extends和super标识符。AbstractList<? extends Number> listNums2表示listNums2对象可以被Number类型的任何子类对象(AbstractList<Integer> listInteger)赋值。AbstractList<? super Integer> listNums3表示listNums3对象可以被Integer类型的任何父类对象(listNums1)或Integer类型对象(listInteger)赋值。
注意:一条比较通用的规则是,如果要向列表中添加元素则用<? super T>,如果要从列表中获取元素则用<? extends T>,如果既要获取又要添加则不使用通配符。