一、了解泛型
l 泛型的基础说明——主要看两点:一点是指对象类型,对象引用要看是否能够继承,比如ArrayList与Vector引用的关系,二点是指对象内部元素的类型<E>一致关系。这两点是相对独立的。
1引ArrayList<E>来说明
- 整个ArrayList<E>称为泛型类型
- ArrayList<E>中的E称为变量类型或类型参数
- 整个ArrayList<Integer>称为已参数化的类型
- ArrayList称为原始类型
- 泛型尖括号常常写在方法前面表示它是一个泛型方法。
- 说例,ArrayList<String> cv;ArrayList定义的是cv类型,String定义cv子元素类型。
- 泛型语法告诉JVM,参数化类型与原始类型之间存在转化,参数类型之间不存在转化。
2参数化类型与原始类型的互兼容性:
- 引用相当于c++中的指针,其结构声明是为了数据操作语法安全考虑的
- 参数化类型可以引用一个原始类型对象,虽然编译报告警告:
如,Collection <String> c=new Vector();
- 原始类型可以引用以一个参数化类型的对象,虽然编译报告警告:
如,Collection c=new Vector<String>();
3参数化类型不考虑类型参数的继承关系:
即 Vector <Streing> v =new Vector<Object>(); //不正确
Vector<Object> v= new Vector <String>(); //错误
4下面的代码不会报错!
Vector v1=new Vector<String>();//正确
Vector<Object> v=v1; //正确,计算机是代码是一行行的执行的,不能连着一起看。
5编译器不允许创建类型变量的数组时,数组的元素不能使用参数化的类型,即:
Vector <Integer> vectorList[] =new Vector<Integer>[10];是错误的,
Vector <Integer> vectorList[] =new Vector[10];正确啦,因为new Vector[10];是指初始化一个内存大小为10个Vector数组,new Vector<String>[10];则JVM不知道是不是创建大小为10Vector数组还是大小为10个String的数组,因为没设定这样指令,所以Jvm报警。
Collection vectorList[] =new Vector<Integer>[10];
6普通方法、构造方法和静态方法中都可以使用泛型。
7也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws中,但是不能用于catch子句中。
Private static <T extends Exception> sayHello() throws T
{ try{ }cath (Exception e){
Throw(T)e;
}
}
8在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如: public static <K,V>V getValue(K key){ return map.get(key) }
l 泛型是提供给Javac编码器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入(如3),编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,其效果就是对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合字段,再调用其add方法即可,如下:
即 Collection1.getClass().getMethod(“add”,Object.class).invoke(Collection1,New added data)
注意:思考到——同一数组类型,不同对象,其均存储在同一堆栈,内存的利用率若是很高,则必然要内存管理机制有挪移算法及数组相同元素只需存在一个,这个就要求,javac能够为每一个数组类型对象准备一张存储说明表,当知道理论上的下标时,能够映射到其真正的下标。
l 在泛型中,public void printCollection(Collection<?> collection)参数中的?是类型通配符,它的作用就是匹配任何参数化类型,但是该函数中不能使用collection对象调用跟参数类型有关的方法。比如add(E e),但是可以调用size(),average( int i)等这样与参数化类型无关的函数。
1在函数public void printCollection(Collection <?> cp)
{
cp.add(“String”) //有错,因为?是个通配符,它不确定,所以不能存储确定的类型的元素
cp.size()// 没错,因为函数size()括号里的类型与参数化类型无关
cp=new HashSet<Date>(); //正确,这是因为HashSet实现了Collection接口,所以子类对象能够赋给父类变量引用;而且?是一种通配符,因此可以匹配赋值。JVM却规定不能调用与参数化有关的函数。
}
2在函数public void printCollection(Collection <Object> cp)
{
cp.add(“String”) //没有错,”String”字符串是String类型,之所以没有错,这里cp对象是Collection,但对象里的子元素是Object类型,即当添加数据时,实质就是Object变量引用其他类型的变量的过程,但是反过来就不对啦,因为Object是可变结构,已知类型引用时自身的固定的类型是无法更改的。
cp=new Collection<String>()// 错误,这是由于javac会匹配语法格式不对而报错。
}
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用对象,不是用于存储,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
当要调用与参数化有关的数据时,使用自定义泛型<T>的表达方式可以达到同一样的目地。
同时,Object也是能够引用任意类型的。Object类型的数据代表着可变结构,该类型能够引用任意类型的数据。却也保留了源类型的信息,所以被Object引用后的数据能够按着原来的类型输出,但是,不能够强制输出与源类型不相干的类型的,如:
Object obj =”1234”;
Int var=(int)obj ; 是错误的。字串转换需用X.toString这样的形式
l 泛型中的?通配符的扩展
1限定通配符的上边界:
- 正确:Vector <? extends Number> x=new Vector<Integer>();——指示通配符?的匹配必须是Number的子类;下面的式子就出错了。
- 错误:Vector <? extends Number> x=new Vector<String>();
2限定通配符的下边界:
- 正确:Vector <? super Integer> x=new Vector<Number>();
- 错误:Vector <? super > x=new Vector<Integer>();
3 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如, Class.getAnnotation()方法的定义。并且可以使用&来指定多个边界,如 <V extends Serializable & cloneable> void method () { };
l 泛型中?通配符的对象赋值转化
Collection<?> y; Collection<String> x;
y=x; // 正确 :未知类型y引用已知类型x,就具有类型化啦。 x=y; //错误,原因是y的类型是未知的,(jvm规定已知类型不能引用未知类型)所以不能被引用。
注意:Java语法规定,接口的对象不能初始化却能够互相引用赋值,如上式。但是x,y在没有初始化之前,是不能引用或者赋值给别的变量的。
l Map 接口提供三种collection 视图,即可以通过键集、值集或键-值映射关系集的形式取出或添加映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序,因为Map类实现了Iterable接口,即具有迭代的功能,这个迭代的方法实现将会在Map类中的getValue()或者getKey()中被调用。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。其中键-值映射关系集的关系存储在Map的内部类中,即Map.Entry<K,V>,可以通过方法entrySet()取出。
l 自定义泛型(通过传递的实例对象或者返回对象来确定类型)
- 交换数组中的两个元素的位置的泛型方法语法定义如下:
Static <T> void swap( T[] a, int i, int j )
{
E t=a[i];
a[i]=a[j]; // T[]表示任意类型的数组
a[j]=t;
}
- 用于放置泛型的类型参数的尖括号应出现在方法的其他所有修辞符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
- 只有引用类型才能作为泛型方法的T,基本类型不能。swap(new int[3], 3, 5 );语句会报告编译错误。但是后面的<T>add(T x, T y )中用add(2,4),就自动装修。
这是因为编译器不会对new int[3]中的int自动拆箱和装箱了(),因为new int[3]你想要的有可能就是int数组呢?所以编译器不作为。装箱就是把基础类型封装成一个类。比如把int封装成Integer,这时你就不能把他当成一个数了,而是一个类了,对他的操作就需要用它的方法了。拆箱就是把类转换成基础类型。比如你算个加法什么的是不能用类的,就得把它转换成基本类型。
- 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如:
- 自定义类型在进行算术运算时,针对返回类型,jvm取类型最大公约数:
1 Object x2=add( 3, “abc” );
2 public static <T> add( String x, T y )// 一般都是Object类型
{
Returen null;
}
注意:Java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(编译器使用泛型类型信息保证类型安全,顺利编译,然后在生成字节码之前将其擦除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的。
l 根据调用泛型方法时实际传递的参数类型或返回类型的类型来推断最后的类型,具有如下规则:
- 直觉推断
Static <E> void swap (E[] , int i, int j );
- 多处用T,但是实际应用类型是同一类型的
Add(3,5)——static <T> add<T a,T b>
- 多处用T,但是实际应用类型不是同一类型的,但是T最后要统一
Int x=(2,3.7f)——static <T> T add(T a,T b);//这里优先考虑返回类型
将变量X的类型改为float,对比eclipse报告的错误提示,接着再将变量X类型改为Number,则没有了错误:
- 多处用T,但是实际应用类型不是同一类型的,且没有返回类型
Fill(new Integer[3] , 3.4f)——static <T> void fill<T[] a, T b>
这时无法参考返回类型,就得取实际应用类型的最大交集类型,上式的T的类型将看成Number。如果实际类型是其他引用类型,则看成Object。
- 多处用T,但是应用类型又具有参数化类型
copy(new Integer[5], new String[5])——static <T> void copy(T[] a, T[] b);
Copy(new Vector <String>() , new Integer[5])——
static <T> void copy(Collection <T> a, T[] b);
这时参数类型的类型推断具有传递性,上面第一种情况推断实际参数类型为Object, 编译没有问题,而第二种情况则根据参数化的Vector实例将类型变量直接确定为String类型,编译将出现问题。
二、 类泛型的定义:
当我们要对任意类型数据进行操作时,我们就需要把方法类型定义为泛型,对于多种操作,有不同的泛型方法,却无法使它们保持统一T,因为有的方法不带参数类型,有的方法只带固定参数类型,它们却都要有T返回类型。因此就需要把该类定义成泛型。
- 在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
- 不论是方法还是引用赋值,都必须有一个<E>的泛型类型说明。
即 public static void update2( E obj) 错误
Public static <T> void update2( T obj) 正确。 这个就是泛型方法。