----------- android培训、java培训、java学习型技术博客、期待与您交流! -----------
一泛型的基本运用及内部原理
泛型,是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时,会去除掉“类型"信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
ArrayList <E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为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 v = new Vector<String>(); 编译通过,原始类型=参数化类型
Vector<Object> v1 = v;编译通过,参数化类型= 原始类型
二 ? 通配符
问题:定义一个方法,该方法用于打印任意参数化类型的集合中的所有数据
错误方式:
public static void printCollection(Collection<Object> cols)
{
for(Object obj:cols)
{
System.out.println(obj);
}
cols.add("String");//没错,因为是Object 所以可以add String
cols = new HashSet<Date>();//错误,上面是Object 下面是Date 不能这样做
正确方式:
public static void printCollection(Collection<?> cols)
{
for(Object obj:cols)
{
System.out.println(obj);
}
cols.add("String");//错误,因为不知道?是什么类型,不一定匹配String类型
cols.size();//正确,此方法与类型参数没有关系
cols = new HashSet<Date>();//正确
//使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用于引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
错误方式:
public static void printCollection(Collection<Object> cols)
{
for(Object obj:cols)
{
System.out.println(obj);
}
cols.add("String");//没错,因为是Object 所以可以add String
cols = new HashSet<Date>();//错误,上面是Object 下面是Date 不能这样做
正确方式:
public static void printCollection(Collection<?> cols)
{
for(Object obj:cols)
{
System.out.println(obj);
}
cols.add("String");//错误,因为不知道?是什么类型,不一定匹配String类型
cols.size();//正确,此方法与类型参数没有关系
cols = new HashSet<Date>();//正确
//使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用于引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
}
}
}
泛型中的?通配符的扩展
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:
限定通配符总是包括自己。
三 自定义泛型方法
自定义泛型,由C++的模板函数引入自定义泛型
如下函数的结构很相似,仅类型不同
int add(int x,int y)
{
{
return x+y;
}
int add(float x,float y)
{
{
return x+y;
}
int add(double x,double y)
{
{
return x+y;
}
交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] a,int i ,int j)
{
E t = a[i];
a[i] = a[j];
a[j] = t;
}
用于防止泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回值类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
只有引用类型才能作为泛型方法的实际参数,swap(new int[2]{3,2},1,2),语句会报编译错误
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如:
Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,如
<V extends Serializable&cloneable> void method(){}
普通方法、构造方法和静态方法中都可以使用泛型
也可以用类型变量来表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch后的()中
在泛型中可以同时又多个类型参数,在定义他们的<>中用逗号分开,例如:
public static<K,V> V getValue(K key){return map.get(key);}
java中的泛型类型类似于c++中的模板,但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器中用于编译器执行类型检查和类型推断,然后生成普通的非凡行的字节码,这种实现技术为擦除erasure(编译器使用泛型类型保证类型安全,然后在生成字节码之前将其清除),这是因为扩展虚拟机指令来支持泛型被认为是无法接受的,这回为java厂商升级其jvm造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
四自定义泛型类
自定义泛型类的应用
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T>
{
private T field1;
public void save(T obj)
{
}
public T getById(int id)
{
}
}
类级别的泛型时根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都课可以
GenericDao<String> dao = null;
new GenericDao<String>();
注意:在堆泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类锁共享的,所以静态成员不应该有类级别的类型参数。
public static <E> void update2()//可以,给该静态方法加泛型
{
}
public static void update(T obj)//错误,这里不能调用泛型参数
{
}
问题:类中只有一个方法需要使用泛型,是使用类级别的翻新还是使用方法级别的泛型?方法级别。
五通过反射获得泛型的实际类型参
得到参数化类型的方法,通过反射得到该方法的字节码,然后得到该方法中参数的泛型类型