Java泛型中的擦除
来源:互联网 发布:怎样申请淘宝小二介入 编辑:程序博客网 时间:2024/05/23 15:47
1、擦除
当深入了解泛型后,就会发现很多东西初看起来是没有意义的。例如,尽管可以申明ArrayList.class,但是不能申明为Array<Integer>.class。例如:
class Manipulator<T> { private T obj; public Manipulator(T x) { obj = x; } // Error: cannot find symbol: method f():// public void manipulate() { obj.f(); }}
经过编译后生成的.class文件如下:
class Manipulator{ public Manipulator(Object x) { obj = x; } private Object obj;}
即在class Manipulator<T>内部,T都被Object类型替代,这就是擦除,即使你Manipulator<String> manipulator = new Manipulator<String>("a")调用时注入的不是Object类型,它依然会被擦除成Object类型,所以不能在class Manipulator<T>内部使用调用T的类型所特有的方法,只能使用Object能使用的那些方法。当然你可以协助泛型类,给定泛型的边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了extends关键字。例如:
class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T x) { System.out.println();obj = x; } public void manipulate() { obj.f(); }}
反编译后得到:
class Manipulator2{ public Manipulator2(HasF x){ System.out.println(); obj = x; } public void manipulate(){ obj.f(); } private HasF obj;}
<T extends HasF>表明T必须具有类型HasF或者从HasF导出的类型。这里编译器实际上会把类型参数替换为它的擦除,即T擦除到了HasF,就好像在类型声明中用HasF替换了T一样。
2、擦除的问题
擦除的代价是显著的,泛型不能显示的引用运行时的类型的操作之中,例如转型、instanceof、new表达式。因为所有的类型信息都丢失了。例如:
class Foo<T>{ T var}
那么看起来在创建Foo实例时:Foo<Cat> f = new Foo<Cat>();当进入class Foo<T>内部时,就会发现T会被Object替换,而不是Cat,所以在编写这个类的代码时必须非常注意,它只是一个Object。
3、擦除的边界
public class GenericHolder<T> { private T obj; public void set(T obj) { this.obj = obj; } public T get() { return obj; } public static void main(String[] args) { GenericHolder<String> holder = new GenericHolder<String>(); holder.set("Item"); String s = holder.get(); }}
在这个例子中,hoider.set()方法里如果传入的不是一个字符串,而是其他类型的参数,编译器会报错,而holder.get()的方法获取到值赋给s时,也不用我们自己来转型,因此,擦除在方法或者类内部移除了有关实际类型的信息,但是会在传递进来的值进行额外的编译期检查,即编译器对set()方法的参数进行检查,而对get()返回的值进行转型也会有编译器自动插入(可以通过javap命令查看)。记住泛型边界即是对象进入和离开方法的地点。
4、擦除的补偿
正如我们看到,擦除丢失了在泛型代码中执行某些操作的能力。任何需要确切类型信息的操作都将无法操作:
public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) {// if(arg instanceof T) {} // Error// T var = new T(); // Error// T[] array = new T[SIZE]; // Error// T[] array = (T)new Object[SIZE]; // Unchecked warning }}
这里对使用instanceof的使用都失败了,因为类型信息已经被擦除了,但是可以通过显式的传递类型的Class对象来进行补偿。
class Building {}class House extends Building {}public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class); System.out.println(ctt1.f(new Building())); System.out.println(ctt1.f(new House())); ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class); System.out.println(ctt2.f(new Building())); System.out.println(ctt2.f(new House())); }} /* Output:truetruefalsetrue
编译器可以确保类型标签匹配泛型参数
5、通配符
public class NonCovariantGenerics { // Compile Error: incompatible types:/* List<Fruit> flist = new ArrayList<Apple>(); */}
当我们阅读这段代码时,即不能将一个持有Apple类型的容器赋值给一个持有Fruit类型的容器,但是回头一想会觉得一个持有Fruit的容器确实向上转型为一个持有Fruit的容器,打个比喻:"一个装苹果的篮子属于一个装水果的篮子么",我觉得答案应该是属于的,但是编译器编译这行代码会报错,大概是因为编辑器知道的信息太少了,如果它能像我们这样来推断就是属于的,但是它不知道任何有关这方面的信息,故不能对此进行推断,所以会报错。
但是有时我们会在两个类型中建立某种类型的向上关系,这是通配符所允许的:
public class GenericsAndCovariance { public static void main(String[] args) { // Wildcards allow covariance: List<? extends Fruit> flist = new ArrayList<Apple>(); // Compile Error: can't add any type of object: // flist.add(new Apple()); // flist.add(new Fruit()); // flist.add(new Object()); flist.add(null); // Legal but uninteresting // We know that it returns at least Fruit: Fruit f = flist.get(0); }}
这里的"?"表示List可以持有任何类型,只要是Fruit继承下来的都可以,例如flist可以指向new Array<Apple>(),也可以给它赋值为new Array<Orange>(),只要是从Fruit继承下来即可。因为我们不能知道它持有的是什么类型,所以不能调用add方法往里面添加对象(除了null)。但是可以使用get()方法获取对象,因为它至少是Fruit类型的。
还有一种是超类型通配符,方法是指定<? super MyClass>,甚至可以使用类型参数<? super T>:
public class SuperTypeWildcards { static void writeTo(List<? super Apple> apples) { apples.add(new Apple()); apples.add(new Jonathan()); // apples.add(new Fruit()); // Error }}
此时Apple是下界,那么往其中添加Apple或者Appple的子类型是安全的,但是此时不能调用get方法,因为你不知道你读出来的类型。
还有一种就是无界通配符<?>,那么List、List<?>、List<? extends Object>有什么差别呢,实际上它们三个差别极小,相互转换时可能会发出警告,List是原生的,实际上就是List<Object>,即代表可以持有任何类型的组合,而List<?>表示具有某种特定类型的非原生List,只是我们不知道那种类型是什么,而List<? extends Object>可以通过前面的通配符来理解。
public class Wildcards { // Raw argument: static void rawArgs(Holder holder, Object arg) { // holder.set(arg); // Warning: 只有警告 // Unchecked call to set(T) as a // member of the raw type Holder // holder.set(new Wildcards()); // Same warning // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information has been lost: Object obj = holder.get(); } // Similar to rawArgs(), but errors instead of warnings: static void unboundedArg(Holder<?> holder, Object arg) { // holder.set(arg); // Error:holder可以是指向一个持有任何类型的Holder,因此set时,不能判断参数类型是否满足,会报错 // set(capture of ?) in Holder<capture of ?> // cannot be applied to (Object) // holder.set(new Wildcards()); // Same error // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information has been lost: Object obj = holder.get(); } static <T> T exact1(Holder<T> holder) { T t = holder.get(); return t; } static <T> T exact2(Holder<T> holder, T arg) { holder.set(arg); T t = holder.get(); return t; } static <T> T wildSubtype(Holder<? extends T> holder, T arg) { // holder.set(arg); // Error: // set(capture of ? extends T) in // Holder<capture of ? extends T> // cannot be applied to (T) T t = holder.get(); return t; } static <T> void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg); // T t = holder.get(); // Error: // Incompatible types: found Object, required T // OK, but type information has been lost: Object obj = holder.get(); } /** * @param args */public static void main(String[] args) { Holder raw = new Holder<Long>(); // Or: raw = new Holder(); Holder<Long> qualified = new Holder<Long>(); Holder<?> unbounded = new Holder<Long>(); Holder<? extends Long> bounded = new Holder<Long>(); Long lng = 1L; rawArgs(raw, lng); rawArgs(qualified, lng); rawArgs(unbounded, lng); rawArgs(bounded, lng); unboundedArg(raw, lng); unboundedArg(qualified, lng); unboundedArg(unbounded, lng); unboundedArg(bounded, lng); // Object r1 = exact1(raw); // Warnings: // Unchecked conversion from Holder to Holder<T> // Unchecked method invocation: exact1(Holder<T>) // is applied to (Holder) Long r2 = exact1(qualified); Object r3 = exact1(unbounded); // Must return Object Long r4 = exact1(bounded); // Long r5 = exact2(raw, lng); // Warnings: // Unchecked conversion from Holder to Holder<Long> // Unchecked method invocation: exact2(Holder<T>,T) // is applied to (Holder,Long) Long r6 = exact2(qualified, lng); // Long r7 = exact2(unbounded, lng); // Error: // exact2(Holder<T>,T) cannot be applied to // (Holder<capture of ?>,Long) // Long r8 = exact2(bounded, lng); // Error: // exact2(Holder<T>,T) cannot be applied // to (Holder<capture of ? extends Long>,Long) // Long r9 = wildSubtype(raw, lng); // Warnings: // Unchecked conversion from Holder // to Holder<? extends Long> // Unchecked method invocation: // wildSubtype(Holder<? extends T>,T) is // applied to (Holder,Long) Long r10 = wildSubtype(qualified, lng); // OK, but can only return Object: /*compile error xukun * Object r11 = wildSubtype(unbounded, lng);*/ Long r12 = wildSubtype(bounded, lng); // wildSupertype(raw, lng); // Warnings: // Unchecked conversion from Holder // to Holder<? super Long> // Unchecked method invocation: // wildSupertype(Holder<? super T>,T) // is applied to (Holder,Long) wildSupertype(qualified, lng); // wildSupertype(unbounded, lng); // Error: // wildSupertype(Holder<? super T>,T) cannot be // applied to (Holder<capture of ?>,Long) // wildSupertype(bounded, lng); // Error: // wildSupertype(Holder<? super T>,T) cannot be // applied to (Holder<capture of ? extends Long>,Long) }}
参考自《Java编程思想》
- Java泛型中的擦除
- [ java ] 泛型中的擦除与转换!
- Java中的泛型和类型擦除
- JAVA泛型擦除
- Java泛型擦除
- Java泛型擦除
- Java泛型擦除
- Java泛型擦除
- Java泛型擦除
- JAVA泛型擦除
- Java 泛型擦除
- java--泛型擦除
- Java -- 泛型中的类型擦除机制介绍(二)
- java 泛型类型擦除
- Java 泛型的擦除
- JAVA泛型擦除机制
- java泛型类型擦除
- java泛型擦除问题
- RHCS(概念篇)
- 再出发~!Hello CSDN
- linux驱动程序makefile文件的解释
- asp.net网站和php的discuz单向同步登陆
- Canada Cup 2016 D 优先队列
- Java泛型中的擦除
- 推荐技术网站
- android设置程序无标题栏样式
- 7 javascript DOM
- Oracle系统触发器
- C++------IO库
- 项目 5 - 顺序串算法(4)
- 使用js,Jquery 禁用浏览器的back 和 next 按钮:
- 关于使用OKHttp调试自签名证书进行双向认证的一些看法