jdk1.5版本开始出现的新特性,用于解决安全问题,是一个类型安全机制。
jdk1.5中,还可以按原来的方式将各种不同类型的数据装到一个集合中,但编译器会报unchecked警告。
使用泛型的好处
1. 将运行时期出现的ClassCastException问题,转换到了编译时期,方便程序员解决问题,让运行期间问题减少,安全。
2. 避免了类型强制转换麻烦。
没有泛型之前,函数/方法扩展兼容性时一般将形参定义为Object类型,以接收作一类型,而Object类型参数在使用时一般需进行强制类型转换。
泛型格式
通过<>定义要操作的引用数据类型,只要见到<>,就是定义泛型,<>就是用来接收类型的。
泛型常用术语
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
1. 整个称为ArrayList<E>泛型类型
2. ArrayList<E>中E称为类型变量或类型参数
3. 整个ArrayList<Integer>称为参数化的类型
4. ArrayList<Integer>中的Integer称为类型参数的实例或实际参数类型
5. ArrayList<Integer>中的<>念typeof
6. ArrayList称为原始类型
定义带泛型的变量时,有一些注意事项,演示在下面在代码行和注释中:
import java.util.Collection;
import java.util.Vector;
public class GenericDemo1 {
public static void main(String[] args) {
//参数化类型可以引用一个原始类型的对象,编译报警告
Collection<String> collection3=new Vector();
//原始类型可以引用一个参数化类型对象,编译报警告
Collection collection4=new Vector<String>();
Vector<Integer> v1=new Vector<Integer>();
/*参数化类型不考虑类型参数的继承关系,下面2句编译失败
Vector<Stirng> v2=new Vector<Object>();
Vector<Object> v3=new Vecotr<String>();
Vector<Object> v3=v1;*/
/*创建数组实例时,数组的元素不能使用参数化的类型,下面语句中vectorLIst是一个数组,数组中的元素不能是Vector<Integer>类型的对象
Vector<Integer> vectorList[]=new Vector<Integer>[10];*/
}
}
泛型在集合框架中很常见,使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
import java.util.*;
public class GenericMapDemo {
public static void main(String[] args) {
HashMap<String,Integer> maps=new HashMap<String,Integer>();
maps.put("liso", 23);
maps.put("wangwu", 25);
maps.put("zhaoliu",40);
Set<Map.Entry<String, Integer>> entrySet=maps.entrySet();
for(Map.Entry<String, Integer> entry:entrySet){
System.out.println(entry.getKey()+" : "+entry.getValue());
}
System.out.println("===========================");
//遍历集合的另一种方式
Set<String> kSet=maps.keySet();
Iterator<String> it=kSet.iterator();
while(it.hasNext()){
String str=it.next();
System.out.println(str+" : "+maps.get(str));
}
}
}
泛型除了可以用在集合或数组变量上,还可以用在类和方法上。
泛型方法
为了让类中的不同方法可以操作不同类型,且类型还没确定,可以将泛型定义在方法上。
泛型定义在方法上时,放在返回值之前,修饰符之后。
普通方法、构造方法和静态方法中都可以使用泛型。
泛型方法的使用方法及一些注意事项见下面的代码:
import java.io.Serializable;
class GenericMethod {
public <T> void show(T t){
System.out.println("show: "+t);
}
public <E> void print(E e){
System.out.println("print: "+e);
}
public <T> T add(T x,T y){
//return (T)(x+y);编译不通过
return null;
}
//给定任一引用类型的数组,交换其中的元素
public <T> void swap(T[] a,int i,int j){
T tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
//定义泛型方法时也可以使用extends、super限定符来限定边界
public <T extends Number> T addNumber(T a,T b){
return null;
}
//可以限定多个边界
public <V extends Serializable & Cloneable> void method(){
}
//可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中
private static <T extends Exception> void sayHello() throws T
{
try{
}
catch(Exception e){//不能写成catch(T e)
throw (T)e;
}
}
//泛型中可以同时有多个类型参数,在定义它们的尖括号中用,分开
public static <K,V> V getVal(K key){
return null;
}
}
public class GenericMethodDemo{
public static void main(String[] args){
GenericMethod gm=new GenericMethod();
//调用泛型方法时可以随意传入任意类型的参数
gm.print("haha");
gm.print(4);
gm.show(56);
//泛型类型推断
Number n=gm.add(3.5, 5);//基本数据类型自动装箱成引用类型数据,
//Double d=gm.add(3.5, 5);//编译不通过,此时的实际类型参数必须是Double和Integer的共同父类,java编译器会进行类型推断来确定和检查类型参数
Object obj=gm.add(8, "abd");//Integer和String类型的交集类型是Oject
gm.swap(new String[]{"abc", "bdg", "iesdkl"},1,2);
//gm.swap(new int[]{2, 4, 5,9},3,1);编译不通过,实际类型参数不能是基本数据类型
//addNumber()只能接收Number类及其子类类型对象
gm.addNumber(4, 6);
gm.addNumber(6.7,9);
//gm.addNumber("abd", "u");编译不通过
}
}
泛型类
当类中要操作的引用数据类型(不能是基本数据类型)不确定时,早期定义Object来完成扩展,现在定义泛型来完成扩展。
泛型类定义的泛型,在整个类中有效。泛型类对象中的具体类型确定后,对象就只能操作这一个类型。
注:类中只有一个方法需要使用泛型时,应该使用类级别的泛型,而不是方法级别的泛型。
泛型类的使用方法及一些注意事项见下面的泛型类示例:
//泛型类
class GenericDao<T> {
private T field;
public void save(T obj){
this.field=obj;
}
public T getField(){
return field;
}
/*编译不通过,静态方法不可以访问类上定义的泛型,因为静态方法先于泛型对象加载
public static T update(T obj){
}*/
//静态方法操作的引用数据类型不确定时,可以将泛型定义在方法上
public static <T> T update(T obj){
return obj;
}
//泛型类中还可以定义 泛型方法,泛型方法使用的类型与泛型类中的可以不相同
public <E> void display(E e){
System.out.println(e);
}
}
public class GenericDaoTest{
public static void main(String[] args){
GenericDao<Integer> g=new GenericDao<Integer>();
//编译不通过,泛型类的类型参数化时,类型参数的实例必须是引用类型,不能是基本类型
//GenericDao<int> gg=new GenericDao<int>();
g.save(8);
System.out.println(g.getField());
g.display("hehe");
System.out.println(GenericDao.update("haha"));
//编译不通过
//g.save("aaa");
}
}
泛型的通配符
类型参数?表示可接收任一泛型
? extends E:可以接收E类型或者E类型的子类型
? super E:可以接E类型或E类型的父类型
泛型通配符使用方法及注意事项演示在下面的代码中:
import java.util.*;
public class GenericDemo4 {
public static void main(String[] args) {
//泛型中的通配符基本用法
//Integer是Number类的子类
Vector<? extends Number> v4=new Vector<Integer>();
//Vector<? extends Number> v5=new Vector<Object>();//编译不通过
Vector<? super Integer> v6=new Vector<Number>();
//Vector<? super Integer> v7=new Vector<Byte>();编译不通过
Class<?> y;
Class<String> x=String.class;
y=x;
//x=y;编译不通过,任一个类型泛型对象不能赋给指定类型泛型对象
}
public static void printCollection(Collection<?> collection){
//方法体中不能调用与参数化有关的方法,因为不知道自己未来匹配的类型是不是String
//collection.add("afb");
collection=new ArrayList<String>();//没错,可以整体指定泛型参数实例。
//可以调用与参数化无关的方法
System.out.println(collection.size());
}
}
java泛型原理
java中的泛型类型(或者泛型)类似于C++中的模板,但这种类似性仅限于表面,java语言中的泛型基本上完全是在编译器中实现,用于给编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级其jvm造成难以逾越的障碍。所以java的泛型采用了完全可以在编译器中实现的擦除方法。
泛型是提供给java编译器使用的,让编译器挡住源程序中的非法输入;已参数化的泛型类型,getClass方法的返回值与原始类型完全一样,编译器编译完成后,生成的字节码中会去掉泛型的类型信息,使程序运行效率不受影响。
只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用add()方法即可。
利用反射还可以得到泛型的实际类型参数:
import java.lang.reflect.*;
import java.util.*;
public class GenericDemo3 {
public static void main(String[] args) throws Exception
{
ArrayList<String> collection1=new ArrayList<String>();
ArrayList<Integer> collection2=new ArrayList<Integer>();
System.out.println(collection1.getClass()==collection2.getClass());//true
//利用反射技术向实际类型参数为Integer的泛型类型对象中添加String字符串
collection2.getClass().getMethod("add", Object.class).invoke(collection2, "abc");
System.out.println(collection2.get(0));//打印集合的第1个元素"abc"
//利用反射技术获取泛型的实际类型参数
Method applyMethod=GenericDemo3.class.getMethod("applyVector", Vector.class);
//获取Method对象的所有形参类型,该方法在jdk1.5版本才有
Type[] types=applyMethod.getGenericParameterTypes();
//获取第1个形参类型,ParameterizedType是一个接口,表示参数化类型,如 Collection<String>
ParameterizedType pType=(ParameterizedType)types[0];
//获取参数的类型
System.out.println(pType.getRawType());
//返回泛型的实际类型参数
System.out.println(pType.getActualTypeArguments()[0]);
}
public static void applyVector(Vector<Date> v1){
}
}
/*运行结果:
true
abc
class java.util.Vector
class java.util.Date
*/