Java中的泛型是提供给Javac编译器,可以限定其和中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带泛型说明的集合时会除掉“类型”信息,是程序运行效率不受影响。对参数化的泛型类型,getClass()方法的返回值和原始类型的完全一样,由于编译器生成的字节码会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型其和中加入其他类型的数据,例如,用放射得到契合,再调用add方法。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class GenericConvert {
public static void main(String[] args){
//定义一个Integer类型化的ArrayList,并装入两个Integer对象
ArrayList<Integer> collections = newArrayList<Integer>();
collections.add(111);
collections.add(222222);
//通过反射将其他类型的对象存入collections中
Class clazz = collections.getClass();
try {
Method methodAdd = clazz.getMethod("add", Object.class);
methodAdd.invoke(collections, "itheima");
} catch (SecurityException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
//打印collections中的元素
System.out.println(collections);
}
}
运行结果:[111, 222222, itheima]
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>();//原来的方法接受一个集合参数,新的类型也要能传进去
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args)throws Exception{
ArrayList collections1 = new ArrayList<Integer>();//此时可以装任意类型的对象
collections1.add("dsfg");
collections1.add(2);
System.out.println(collections1);
ArrayList<Integer>collections2 = new ArrayList();//此时只能装Integer类型,装其他类型错;
collections2.add(1);
//collections.add("dfdg");//错
System.out.println(collections2);
}
}
运行结果:
[dsfg, 2]
[1]
由实验结果我们可以得知,泛型参数化,是由定义时ArrayList<Integer>类型参数化来决定的,如果没有<Integer>则可以装入任意对象,如果加了则只能加入这中对象。
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = newVector<Integer>[10];
思考题:下面的代码会报错误吗?
Vector v1 = newVector<String>();
Vector<Object> v = v1;
package liaoli.review2;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args)throws Exception{
//ArrayList<String>list1= new ArrayList<Object>();//泛型是不考虑继承关系的 所以错
//ArrayList<Object>list2= new ArrayList<String> ();//泛型是不考虑继承关系的 所以错
ArrayList list1 = new ArrayList<Object>();
ArrayList<String> list2 =list1;
//上面的代码是正确的,list1可以装入任意对象,而list2只能装入String类型的对象
list1.add("itheima");
list1.add(1);
list2.add("wolaile");
//list2.add(111);//错
System.out.println(list1);
System.out.println(list2);
}
}
运行结果:
[itheima, 1, wolaile]
[itheima, 1, wolaile]
泛型中的通配符‘?’可以引用其他各种参数化的类型,‘?’通配符定义的变量主要用作引用,可以调用参数化无关的方法,不能调用由参数化有关的方法,
//用通配符写一个打印集合的方法
public static void printCollection(Collection<?> collections) {
for(Object collection :collections){
System.out.println(collection);
}
//collections.add(2);不能使用与类型相关的方法所以报错
collections.size();//可以使用与类型无关的方法
}
泛型中的‘?’通配符的扩展
限定通配符的上边界:
? extends Number 表示Number以及Number的子类
? super Integer 表示Integer以及Integer的父类
正确: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>();
提示:
限定通配符总是包括自己。
?只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y = newVector<Integer>();
Vector<Number> x = y;
上面的代码错误,原理与Vector<Object > x11 = newVector<String>();相似,
只能通过强制类型转换方式来赋值。
自定义模板类
在方法上定义泛型时,泛型必须加载方法的返回值之前
public static <T>void exchangeValueOfArray(T[]arr,int i,int j){
T temp = arr[i];
arr[i]= arr[j];
arr[j]= temp;
}
我们发现exchangeValueOfArray(newint[]{1,2,3,4},2,3);报错,这是因为只有引用类型才能作为泛型方法的实际参数
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> voidmethod(){}
普通方法、构造方法和静态方法中都可以使用泛型。
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
private static <T extendsException> sayHello()throws T
{
try{
}catch(Exceptione){
throw (T)e;
}
}
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static<K,V> 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> T add(T a, T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的公共父类,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) à static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x=(3,3.5f) à static <T> 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>(), newInteger[5]) à static <T> voidcopy(Collection<T> a , T[] b);
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
当类中只有一个方法需要使用泛型,则使用方法级别的泛型
由于编译器编译带泛型说明的集合时会除掉“类型”信息,所以有
ArrayList<Integer>.class==ArrayList<Double>.class==ArrayList.class
所以对于方法
Public void m(ArrayList<Integer> list)
Public void m(ArrayList<Double> list)
Public void m(ArrayList list)
不能算是重载的。
同时,也就说明了我们不能同过反射直接冲类的生上拿到泛型的参数类型
我们同过反射不能直接获得泛型的参数类型,但我们可以通过一个以此泛型类型为常数的方法来的到泛型的参数类型,如下:
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
public class GenericTest {
public static void main(String[] args)throws Exception{
Method applyMethod = GenericTest.class.getMethod("applyArrayList", ArrayList.class);
Type[] types = applyMethod.getGenericParameterTypes();
ParameterizedType pType = (ParameterizedType) types[0];
System.out.println(pType.getRawType());
System.out.println(pType.getActualTypeArguments()[0]);
}
public void applyArrayList(ArrayList<Date> collections){
}