jdk1.5新特性之泛型(二)
在jdk1.5新特性之泛型(一)中,主要是谈到了泛型的概念、用法以及一些注意事项。在这一章中,我们会对泛型有一个更深入的认识。
我们在jdk1.5的帮助文档中看到集合类都实现了这种泛型机制。如果我们自己想写一个类,这个类也能够更加的通用、扩展,这样能做到吗?答案是可以的。
1.自定义泛型
1.1自定义泛型类
格式:public class 类名<泛型类型1...>
注意:泛型类型必须是引用类型,并且可以定义多个泛型类型,多个泛型类型之间用逗号隔开。
下面看一个泛型类:
package wj.wcj.TestGeneric;
public class AddObjectTool <T>{
private T obj;
public AddObjectTool(){}
public AddObjectTool(T obj) {
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
package wj.wcj.TestGeneric;
public class TestGenericClass {
public static void main(String[] args) {
AddObjectTool<String> addStr = new AddObjectTool<>("java");
System.out.println(addStr.getObj());
AddObjectTool<Integer> addInt = new AddObjectTool<>(60);
System.out.println(addInt.getObj());
}
}
可以看到在上面的代码中定义了一个带泛型声明的AddObjectTool<T>类,实际使用AddObjectTool<T>类时会为T形参传入实际类型,来满足我们的需求。
在这里我们有一个地方需要注意,当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如为AddObjectTool<T>类定义构造器,器构造器依然是AddObjectTool,而不是AddObjectTool<T>,但调用该构造器时却可以使用AddObjectTool<T>的形式,当然应该为T形参传入实际的类型参数。
1.2自定义泛型方法
对于泛型类,在类上定义的T这个类型参数后,那么调用方法时,给的类型必须要和T相同,也就是说比如ArrayList<String> list = new Array<String>,这个list的泛型类型是String,那么,你调用的方法给的参数必须是String类型。但是,谁说我的方法一定要和类的类型一致呢,而且如果类上没有泛型,方法还能不能用泛型了呢。
我们来看一下这样的问题:假如我们需要一个方法,它能够把Object数组的元素添加到一个集合中。看下面代码:
public static void fromArrToCollen(Object[] obj , Collection<Object> col){
for(Object o : obj){
col.add(o);
}
}
我们可以看到,这样做没有问题的,但是你有没有发现这个方法太局限了。这个方法中的col形参,它的数据类型是Collection<Object>,但是我们知道参数化类型不考虑类型参数的继承关系,也就是说这个方法只能将Object数组的元素复制到Object(Object的子类不行)的集合中。
那么,为了解决这一类问题,java引入了泛型方法。
格式: 修饰符 <T...> 返回值类型 方法名(形参列表){//方法体 }
我们对上面的代码进行改造:
public static <T>void fromArrToCollen(T[] obj , Collection<T> col){
for(T o : obj){
col.add(o);
}
}
调用上面方法:
public static void main(String[] args) {
Object[] o = new Object[30];
Collection<Object> col = new ArrayList<>();
//T代表Object
fromArrToCollen(o, col);
String[] sa = new String[30];
Collection<String> cs = new ArrayList<>();
//T代表String
fromArrToCollen(sa, cs);
//T代表Object
fromArrToCollen(sa, col);
}
上面程序定义了一个泛型方法,该方法中定义了一个T类型形参,这个T类型形参就可以在该方法内当成普通类型使用。
我们可以看到当你传入什么(两个参数类型相同或者一个参数类型是另一个参数类型的子类),T都能很好的表示,那么T怎么知道是什么类型的呢,这就依赖于编译器能够对实际参数类型推断。
根据调用泛型方法时实际传递的参数类型或返回值类型判断,具体规则如下:
(1)当某个类变量只在整个参数列表中的所有参数和返回值中的一处被调用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭借着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) —> static <E> void swap(E[] a,int i, int j)
(2) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多出的实际应用类型都对应同一种类型来确定,这很容易凭借感觉推断出来,例如:
add(3,5) —> static <T> T add(T a,T b)
(3) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多出的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出了问题:
fill(new Integer[3],3.5f) —> static <T> void fill(T [] a,T v)
(4) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x=(3,3.5f) —> static <T> T add(T a,T b);
(5) 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译出现问题:
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);
1.3自定义泛型接口
格式:pubic interface 接口名<E>{
public abstract 返回值类型 方法名(E e)
}
下面定义一个泛型接口:
/*
* 泛型接口:把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
然后定义实现类去实现接口:实现类分为两种情况
第一种情况:已经知道该是什么类型的了
public class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
那么,调用它的代码:
Inter<String> i = new InterImpl();
i.show("hello");
此时可以知道,调用时只能是传递字符串对象。但是这种是不常见的,因为这一种把类型固定死了,不够灵活,局限性太大了。
第二种情况:还不知道是什么类型的
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
那么,调用它的代码:
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
这样子可以传递任意类型,更通用,更灵活。
2.其实不存在泛型类
我们知道ArrayList<String> 和ArrayList<Integer> 是不同的,因为这两个集合装的东西是不一样的,ArrayList<String>只能装字符串对象,而ArrayList<Integer> 只能装整型对象,但实际上,系统并没有为ArrayList<Stirng> 和ArrayList<Integer>生成新的class文件。也就是说ArrayList<Stirng> 和ArrayList<Integer>的字节码都一样。
可以看到打印的是true。
所以总结:
java的泛型类型(或者泛型)类似于C++的模板。但这种相似性仅限于表面,java语言的泛型基本上完全是在编译器中实现的,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种技术叫做擦除(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除),这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级其jvm造成障碍,所以java的泛型采用了可以完全在编译器中实现的擦除。所以List<Integer>和List<String>得到的字节码是一份字节码。所以在虚拟机看来并不存在泛型类。