36_张孝祥_Java基础加强_入门泛型的基本应用
l 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
代码演示:
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args)throws Exception {
ArrayList<String> list = new ArrayList<String>();
Object obj = list.getClass().getMethod("add",Object.class).invoke(list,1);
System.out.println(obj);
System.out.println(obj.getClass());
}
}
/*
结果为:
true
class java.lang.Boolean
注意:invoke方法返回的是被调用的方法的返回值。
*/
37_张孝祥_Java基础加强_泛型的内部原理及更深应用
l ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
Ø 整个称为ArrayList<E>泛型类型
Ø ArrayList<E>中的E称为类型变量或类型参数
Ø 整个ArrayList<Integer>称为参数化的类型
Ø ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参
Ø ArrayList<Integer>中的<>念着typeof
Ø ArrayList称为原始类型
l 参数化类型与原始类型的兼容性:
Ø 参数化类型可以引用一个原始类型的对象,编译报告警告,例如, Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗? Ø 原始类型可以引用一个参数化类型的对象,编译报告警告,例如, Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去。l 参数化类型不考虑类型参数的继承关系:
Ø Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Ø Vector<Object> v = new Vector<String>(); //也错误!
l 编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
l Vector<Integer>vectorList[] = new Vector<Integer>[10];
l 思考题:下面的代码会报错误吗?
l<1> Vector v1 = new Vector<String>(); //正确,体现了java对旧版本的兼容
l Vector<Object> v =v1;//正确
<2> 但是,Vector<Object> v1 = newVector<String>();是错误的。
<3>还有,Vector v1 =new Vector<String>(); //原始类型可以引用一个参数化类型的对象
Vector<Integer> v = v1;//这两句话在编译时期也不会报错,参数化类型可以引用一个原始类型的对象。
v.add(1);//编译通过。
v.add(“123”);//编译不通过,说明泛型在编译期间,主要参考”=”左边。
² 上述几个例子参看参数化类型与原始类型的兼容性。
38_张孝祥_Java基础加强_泛型的通配符扩展应用
l 泛型中的?通配符
Ÿ 问题:
Ø 定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
Ÿ 错误方式:
Ÿpublic static void printCollection(Collection<Object> cols) {
Ÿ for(Objectobj:cols) {
Ÿ System.out.println(obj);
Ÿ }
Ÿ /*cols.add("string");//没错
Ÿ cols = newHashSet<Date>();//会报告错误!*/
Ÿ}
Ÿ 正确方式:
Ÿpublic static void printCollection(Collection<?> cols) {
Ÿ for(Objectobj:cols) {
Ÿ System.out.println(obj);
Ÿ }
Ÿ //cols.add("string");//错误,因为它不知自己未来匹配就一定是String
Ÿ cols.size();//没错,此方法与类型参数没有关系
Ÿ cols = newHashSet<Date>();
Ÿ }
² 总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
l 泛型中?通配符的拓展:
Ÿ 限定通配符的上边界:
Ø 正确: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<? extendsNumber> y = new Vector<Integer>();
Ø Vector<Number> x =y;
Ø 上面的代码错误,原理与Vector<Object > x11= new Vector<String>();相似,
Ø 只能通过强制类型转换方式来赋值。
39_张孝祥_Java基础加强_泛型集合的综合应用案例
泛型集合类的综合案例:
l 能写出下面的代码即代表掌握了Java的泛型集合类:
l HashMap<String,Integer> hm = new HashMap<String,Integer>();
l hm.put("zxx",19);
l hm.put("lis",18);
l
l Set<Map.Entry<String,Integer>> mes= hm.entrySet();
l for(Map.Entry<String,Integer> me : mes) {
l System.out.println(me.getKey()+ ":" + me.getValue());
l }
l 对在jsp页面中也经常要对Set或Map集合进行迭代:
l<c:forEach items=“${map}” var=“entry”>
l ${entry.key}:${entry.value}
l</c:forEach>
注意:标签对里边的${…}是EL表达式。
40_张孝祥_Java基础加强_自定义泛型方法及其应用
由C++的模板函数引入自定义泛型
l 如下函数的就够很相似,仅类型不同:
Ø int add(int x,int y){
return x+y;
}
Ø float add(float x,float y){
return x+y;
}
Ø double add(double x,double y){
return x+y;
}
l C++用模板函数,只写一个通用的方法,他可以适应各种类型,适宜代码如下:
Template<classT>
T add(I x,T y){
return (T)x+y;
}
l 定义泛型方法
Ÿ Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
Ÿ<T> T add(T x,T y) {
Ÿ return (T) (x+y);
Ÿ //return null;
Ÿ }
Ÿ用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示
Ÿ 交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E>void swap(E[] a, int i, int j) {
Et = a[i];
a[i]= a[j];
a[j]= t;
}
² 只有引用类型才能作为泛型方法的实际参数,基本数据类型不能作为泛型的实际参数,swap(newint[3],3,5);语句会报告编译错误。
l 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}//接口的多实现
l 普通方法、构造方法和静态方法中都可以使用泛型。
l 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
l 在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
lpublic static <K,V> V getValue(K key) { return map.get(key);}
l 用下面的代码说明对异常如何采用泛型:
private static<T extends Exception> asyHello()throws T{
try{
…
}catch(Exception e){
throw (T)e;
}
}
41_张孝祥_Java基础加强_自定义泛型方法的练习与类型推断总结
l 泛型方法的练习题:
<1> 编写一个泛型方法,自动将Object类型的对象转换成其他类型。
本题的正确答案:
public static <T>T autoCovert(Object obj){
return(T)obj;
}
在练习过程中尝试了一些模板函数的编写,如下:
@SuppressWarnings("unchecked")
public static <T>T autoCovert1(Object obj){
return (T)(obj.getClass().getName()+":"+obj);
}
@SuppressWarnings("unchecked")
public static <T>T autoCovert2(T obj) throws Exception{//泛型的参数类型不确定,不能用T.class求T的字节码。
return (T)GenericTest2.class.getMethod("autoCovert1",Object.class).invoke(null,"aa");
}
public static <T>T autoCovert3(T t,Object obj){
//return (T)(t+obj);//这种方式会报错:The operator + is undefined for the argument type(s) T, Object
return null;
}
小结:模板函数autoCovert3不能用+号连接两个对象,暂时没什么好的解决方法。
疑问:如何定义运算符:+,才能让两个参数产生联系?
<2>定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
public static <T>void fileArray(T[] a,T obj){
for(int i=0;i<a.length;i++){
a[i] = obj;
}
}
<3>采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
² 在这种情况下,前面的通配符方案要比范型方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用范型方法。
代码1:
public static void printCollection(Collection<?> collection){
//collection.add(1);
System.out.println(collection.size());
for(Object obj : collection){
System.out.println(obj);
}
}
代码2:
public static <T> void printCollection2(Collection<T> collection){
//collection.add(1);
System.out.println(collection.size());
for(Object obj : collection){
System.out.println(obj);
}
}
<4>定义一个方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中。
public static <T> void copy1(Collection<T> dest,T[] src){
// int length = src.length;//为了减少内存开销,涉及到运算的代码,能不写在//for循环中的尽量不要写。
// for(int i=0;i<length;i++){
// dest.add(src[i]);
// }
for(T t : src){
dest.add(t);
}
}
42_张孝祥_Java基础加强_自定义泛型类的应用
定义泛型类型
l 如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
l public classGenericDao<T> {
l private T field1;
l public voidsave(T obj){}
l public TgetById(int id){}
l }
l 类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
Ø GenericDao<String> dao = null;
Ø new genericDao<String>();
l 注意:
Ø 在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
Ø 当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
l 问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
Ø 使用方法级别的。
代码演示:
import java.util.Set;
//dao data accessobject--->crud
public class GenericDao<E> {
public void add(E x){
}
public E findById(int id){
return null;
}
public void delete(E obj){
}
public void delete(int id){
}
public void update(E obj){
}
public static <E> void update2(E obj){
}
public E findByUserName(String name){
return null;
}
public Set<E> findByConditions(String where){
return null;
}
}
² 注意:在定义泛型时,即使定义了类级别的泛型,静态方法也要单独定义。
43_张孝祥_Java基础加强_通过反射获得泛型的实际类型参数
通过反射获得泛型的参数化类型
l 示例代码:
Class GenericalReflection {
private Vector<Date> dates = newVector<Date>();
public void setDates(Vector<Date>dates) {
this.dates = dates;
}
public static void main(String[] args) {
Method methodApply =GenericalReflection.class.getDeclaredMethod("applyGeneric",Vector.class);
ParameterizedType pType =(ParameterizedType)
(methodApply.getGenericParameterTypes())[0];
System.out.println("setDates("
+ ((Class)pType.getRawType()).getName() + "<"
+ ((Class)(pType.getActualTypeArguments()[0])).getName()
+ ">)" );//参数类型名+参数类型的泛型名数组的第一个元素名
}
private voidapplyGeneric(Vector<String> v){
}
}
l 泛型DAO的应用:
Ø public abstract class DaoBaseImpl<T> implementsDaoBase<T> {
Ø protected Class<T>clazz;
Ø public DaoBaseImpl() {
Ø Type type =this.getClass().getGenericSuperclass();
Ø ParameterizedTypept = (ParameterizedType) type;
Ø this.clazz =(Class) pt.getActualTypeArguments()[0];
Ø System.out.println("clazz= " + this.clazz);
Ø }
Ø }
public classArticleDaoImpl extends DaoBaseImpl<Article> implements ArticleDao {
}
l 视频中的代码讲解:
public class GenericTest {
public static void main(String[] args)throws Exception {
GenericDao<ReflectPoint> dao = newGenericDao<ReflectPoint>();
dao.add(new ReflectPoint(3,3));
//String s = dao.findById(1);
//Vector<Date> v1 = newVector<Date>();
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);//通过反射获取方法对象。
Type[] types = applyMethod.getGenericParameterTypes();//获取方法//的泛型参数类型数组
ParameterizedType pType = (ParameterizedType)types[0];
System.out.println(pType.getRawType());
System.out.println(pType.getActualTypeArguments()[0]);
}
public static void applyVector(Vector<Date> v1){
}
}