1. Java泛型的实现方式
Java中实现泛型是从Java5开始的,由于历史遗留问题,即为了兼容低版本未用泛型实现的类库,Java采用了擦除的方式实现泛型,泛型代码在运行时会被擦除到他的边界范围。这样一来,在泛型代码内部,就无法获得有关泛型参数的类型信息。
package suzumiya;
import java.util.Arrays;
public class Template {
public static void main(String[] args) {
//获取泛型参数类型信息
System.out.println(Arrays.toString(
new Generic<String>().getClass().getTypeParameters()));
}
}
class Generic<T>{}
/* output :
[T]
*///~
上述代码我们可以看到,运行时我们能得到的泛型参数类型信息仅仅是作为占位符的标识符,并不能得到有效的类型信息,这一点与C++等支持泛型的语言有很大区别。由于这个原因,Java泛型的作用并不像我们想象中的那么大,很多时候是可以用多态来代替的。比如说:
package suzumiya;
public class Template<T extends Number> {
public void out(T obj){
System.out.println(obj);
//无法调用该方法,因为编译器无法获取obj的类型信息。
//obj.out();
}
public static void main(String[] args) {
Template<Inte> temp = new Template<Inte>();
temp.out(new Inte());
}
}
class Number {
public String toString() {
return "This is Number! ";
}
}
class Inte extends Number {
public String toString() {
return "This is Integer extends Number! ";
}
public void out(){
System.out.println("You can't invoke this method. ");
}
}
package suzumiya;
public class Template {
public void out(Number obj){
System.out.println(obj);
//无法调用该方法,因为编译器无法获取obj的类型信息。
//obj.out();
}
public static void main(String[] args) {
Template temp = new Template();
temp.out(new Inte());
}
}
上述两段代码执行结果完全一样。第一种是用泛型,第二种使用多态。但是当需要将泛型参数作为返回值类型时,泛型就比较方便了。
public class Template<T extends Number> {
public T out(T obj){
return obj;
}
public static void main(String[] args) {
Template<Inte> temp = new Template<Inte>();
Inte in = temp.out(new Inte());
in.out();
}
}
2. 擦除与边界
2.1 擦除问题
Java使用擦除的方式实现泛型的主要原因就是为了兼容曾经不是使用泛型实现的类库等。所以在Java代码中使用泛型并不是强制性的。即使父类带有泛型参数,子类继承时也可以选择不带泛型参数。
class Eat<T>{}
class Chew extends Eat{}
同样父类不带泛型参数时,子类可以带。
class Eat{}
class Chew<T> extends Eat{}
2.2 边界处理
我们平时再使用ArrayList等泛型类时可以发现,加入插入的数据类型和泛型参数表示的数据类型不符,编译就会报错。既然泛型代码在运行过程中把参数类型信息都擦除掉了,那为什么可以进行类型检查呢?问题在于边界,即对象进入和离开方法的地点。泛型中所有动作都发生的边界处,包括对传入的值进行类型检查和对传出的值进行类型转换。
3. 擦除带来的几个问题
在泛型代码中,任何在运行时需要知道类型详细信息的操作都无法完成。
package suzumiya;
public class Template<T> {
public static void test(Object obj){
//以下三个操作都会报错
if(obj instanceof T){}
T temp = new T();
T[] temp2 = new T[10];
}
public static void main(String[] args) {}
}
所以上述方法中的三步操作都会报错。下面讲一下解决办法。
3.1 类型匹配
既然泛型参数类型无法在运行时保留,我们可以自己在代码中添加类型信息。
package suzumiya;
public class Template<T> {
private Class<T> kind;
public Template(Class<T> kind){
this.kind = kind;
}
public boolean test(Object obj){
//判断obj是不是kind类型的
return kind.isInstance(obj);
}
public static void main(String[] args) {
Template<Fruit> fruit = new Template<Fruit>(Fruit.class);
System.out.println(fruit.test(new Fruit()));
System.out.println(fruit.test(new Apple()));
}
}
class Fruit{}
class Apple extends Fruit{}
/*output :
true
true
*////~
3.2 创建实例
在泛型代码中无法创建类型实例一方面是因为擦出了类型信息,另一方面是不知道构造函数是否有参数。
3.2.1 直接传入类型信息
public class Template<T> {
public T temp;
public void create(Class<T> kind){
try {
temp = kind.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Template<String> temp = new Template<String>();
temp.create(String.class);
String str = temp.temp;
}
}
在create方法中直接传入类型信息,利用newInstance方法创建实例。也可以利用Constructor类中的newInstance方法根据带参数的构造函数创建实例。
3.2.2 工厂模式
interface Factory<T>{
T create();
}
class IntegerFactory implements Factory<Integer>{
@Override
public Integer create() {
return new Integer(10);
}
}
public class Template<T> {
public T temp;
public <F extends Factory<T>> Template(F factory){
temp = factory.create();
}
public static void main(String[] args) {
System.out.println(new Template<Integer>(
new IntegerFactory()).temp);
}
}
3.3 泛型数组
public class Template<T> {
private Class<T> kind;
public Template(Class<T> kind){
this.kind = kind;
}
@SuppressWarnings("unchecked")
public T[] create(int size){
return (T[])Array.newInstance(kind, size);
}
public static void main(String[] args) {
Integer[] temp = new Template<Integer>(Integer.class).create(3);
}
}
4. 总结
总体来说,Java泛型由于擦除问题,在运行时无法获取反省参数的类型信息,所以如果实在必须要操作类型的话,建议传入相应的Class对象,手动存储类型信息。