泛型是1.5的所有新特性中最难深入掌握的部分,他的文档中有很多数学推理公式.不过,我们在实际应用中不用掌握
那么深入,掌握泛型中一些最基本的内容就差不多了.没有使用泛型时,只要是对象,不管是什么 类型的对象,都可以存储进同一个集合中. 使用泛型集合,可以将一个集合中的元素限定* 为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;并且当从该集合中获取一个对象时,编译器也可以知道这个对象的类型,不需要对,对象进行强制类型转换..
package cn.itcast.day2;
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;
import java.util.HashSet;
import java.util.Vector;
import org.junit.Test;
public class GenericTest {
public static void main(String[] args)throws Exception {
//不加泛型的集合..
ArrayList collection=new ArrayList();
collection.add(1);
collection.add(1l);
collection.add("abc");
int i=(Integer)collection.get(0);
//加上泛型后的集合..(带类型说明的集合)
ArrayList<String> collection2=new ArrayList<String>();
collection2.add("abc");
//加上泛型<Integer>的集合,规定只能add(Integer)...
ArrayList<Integer> collection3=new ArrayList<Integer>();
collection3.add(565);
/** 泛型是提供给java编译器使用的,可以限定集合中的输入类型,让编译器挡住
* 源程序中的非法输入,编译器编译,*****加上泛型的集合时会将"类型"信息去除掉,*****
* 使程序运行效率不收影响,对于参数化的泛型类型,getClass()方法的返回值
* 和原始类型完全一样.********由于编译生成的字节码会去掉泛型的类型信息,所以,只要
* 能跳过编译器,就可以往某个泛型集合中加入其他类型的数据.
* ****例如,用反射得到集合,在调用其add方法即可..
*/
//加个扩展:深入理解下编译器:编译器是一个严格按照语法检查的一个工具,他不考虑运行时的
//结果,他是一行行扫描判断语法.上下没有什么逻辑关系..例如:
//Vector v1=new Vector<String>(); 均不报错 不用顾虑
//Vector<Object> v=v1; 均不报错 逻辑关系
//所以说,在泛型中,参数类型不要考虑父子关系
//下面,现验证下,到底是不是,泛型只存在于编译期..
System.out.print("验证泛型只存在于编译期:");
System.out
.println(collection2.getClass() == collection3.getClass());//true
//*********************************************************************
//同过上面测试,的确ok,,,,所以,我们就来****通过反射****,跳过编译器,对加上泛型的collections3
//进行一次,跨编译器的操作把.
Method method=(Method)collection3.getClass().getMethod("add",Object.class);
method.invoke(collection3, "hello");//将字符串加到ArrayList<Integer>中
System.out.println("成越过编译器赋值:"+collection3.get(1));//成功
printCollection(collection3);//调用方法遍历这个集合...
//实际类型必须为引用类型(对象),这里T对应的是int
//调用方法,调换数组中的位置
swap(new String[]{"abc","hello","cba"},2,1);
//注意:只有引用类型才能作为泛型方法的实际参数
//------swap(new int[]{2,43,534},2,1);会报编译错误.--
//练习,
//自动类型转换...:
Object obj="abc";
String strobj=autoConvert(obj);
//int intobj=autoConvert(obj);//运行出错.编译逃过
//将任意类型数组中的填充成这个类型的对象.
fill(new String[]{"32432","234sdnfis","sfisb","sfnis"},"哈");
/****通过[[反射]]获得泛型的参数化类型******************************************************************************
* 应用,例如在web中,要返回一个集合,这个集合中没有指定
* 具体的类型,需要得到具体的参数化类型....
*/
//*******************************************************************
//定义一个带泛型的Vector集合...
Vector<Date> v1=new Vector<Date>();
//左思右想,判定,通过,v1这个对象自己是没办法获得,<Date>的,因为,泛型只存在于编译期
//既然上面的方法不可行,那么,就想想别的,想一下,Method方法中,
//getGenericParameterTypes();获取泛型参数类型.
Method applyVector=GenericTest.class.getMethod("applyVector",Vector.class);
Type[] types=applyVector.getGenericParameterTypes();
//ParameterizedType,Type的子类
ParameterizedType pType=(ParameterizedType)types[0];
System.out.println("直接打印pType"+pType);//,复写了Object的toString()方法,返回格式为:java.util.Vector<java.util.Date>
//返回 Type 对象,表示声明此类型的类或接口---就是得到自己原始的类型---
System.out.println("原始的类型:"+pType.getRawType());//class java.util.Vector
// 返回表示此类型实际类型参数的 Type 对象的数组-----就是得到自己实际的参数-----
//注意,实际的参数,有可能有多个,比如Map<K,V>...所以,返回为数组...
System.out.println("实际的的参数"+pType.getActualTypeArguments()[0]);//class java.util.Date
//*******************************************************************
}
//辅助方法start
//将v1这个变量,交给一个方法去使用,如-----作为参数-----,或者返回值.----就可通过
//Method中的一些特殊方法,得到我们想得的....
public static void applyVector(Vector<Date> v1){}
//可将任意类型数组中的所有元素填充
private static <T> void fill(T[] arr,T fillval){
for(int i=0;i<arr.length;i++){
arr[i]=fillval;
}
}
//自动类型转换-------具体类型根据返回值确定.
private static <T> T autoConvert(Object obj){
return (T)obj;
}
//交换任意数组类型中的两个元素,可以用到泛型.
private static <T> void swap(T[] arr,int i,int j){
T temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
//辅助方法end
/**泛型中的?通配符(可以匹配任意类型)
* 孝祥老师总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作
* 引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法.
*
* 泛型中的?通配符的扩展
* 限定通配符的上边界:<? extends Number>限定父类是Number或他的子类
* 限定通配符的下边界:<? super Integer>限定子类是Integer或他的父类
* 限定通配符总是包括自己
*/
//当然,除了在应用泛型时可以用限定符外,在定义泛型时也可以使用限定符,例如:
//public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
//采用通配符方案打印出任意集合的所有元素.
private static void printCollection(Collection<?> cols) {
System.out.println("****遍历整个集合开始");
for(Object obj:cols){
System.out.println(obj);
}
System.out.println("遍历整个集合结束****");
//cols.add("String");//错误,因为它不知道自己未来匹配的,就一定是String
cols.size();//没错,次方法与类型参数没有
cols=new HashSet<Date>();//也没错,这个就相当于给Collection<?> cols重新赋值..
}
//采用自定义泛型方法的方式打印出任意参数化类型的集合中所有内容
private static <T> void printCollection2(Collection<T> cols,T val) {
System.out.println("****遍历整个集合开始");
for(Object obj:cols){
System.out.println(obj);
}
cols.add(val);//通过,因为,他的类型是确定的.
}
//类型参数的类型推断:
//主要注意:下面copy2中,多出对应不同的类型,并且没有返回值,就取最大交集.如果有返回值,就优先考虑返回值的类型.
//下面copy1中,表明类型参数的推断,也具有传播性.
//将任意参数类型的集合中的数据复制到一个对应类型的数组中
public static <T> void copy1(Collection<T> dest,T[] src){}
//将任意类型的数组中的数据复制到一个对应类型的数组中
public static <T> void copy2(T[] dest,T[] src){}
//测试一下,<T>取,多个参数的交集...
private static <T> T addition(T x,T y){
//C++中的模版可以,做到下面被注释的功能.
//return(T)(x+y);//The operator + is undefined for the argument type(s) T, T
return (T)x;
}
@Test
public void Test(){
copy1(new Vector<String>(),new String[10]);//类型推断具有传播性
copy2(new Date[10],new String[10]);//取交集
//验证泛型,的灵活取值....取参数的交集,为最后的<T>
int a=addition(1,2);
Number b=addition(1,2.3);//这里的基本类型,可以自动装箱...
Object c=addition(1,"abc");
}
}
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与
参数化有关的方法
自定义泛型方法极其应用
由C++的模版函数引入自定义泛型(java的泛型是从C++中借鉴过来的,java的反省没有C++的泛型功能强大,由于java虚拟机设计的原因,但是尽量去模仿C++的泛型)
如下函数的结构很相似,仅类型不同
int add(int x,int y){
return x+y;
}
float add(float x,float y){
feturn x+y;
}
double add(double x,double y){
return x+y;
}
C++用模板函数解决,只写一个通用的方法,它可以适应各种类型,示例代码:
template<class T>
T add(T x, T y){
return(T)( x+y);
}
Java的泛型方法没有C++模板函数功能强大,java中的使用模板函数代码无法通过编译
Java中的泛型类型(或者泛型)类似于C++中的模板,但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型判断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(编译器使用泛型类型信息保证类型安全,然后再生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级其JVM造成难以逾越的障碍,所以,java的泛型采用了可以完全在编译器中实现的擦除方式
定义泛型类型
如果类的实例对象中的多处都要用到同一个泛型参数。即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao <E> {
private T fleld1;
public void save(T obj){}
public T getByid(int id){}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如:如下两种方式都可以:
GenericDao<String> dao=null;
New genericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变和静态方法调用,因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数
注意:catch中必须明确要抓那一个异常,catch(中不能用T)
Throw(T)e;抓到异常后,把他包装成别一个异常,继续往外仍.在分层设计中很常见.