Java的泛型实现采用了擦除(erasure)机制,这给获取泛型的类型信息带来了一点麻烦。比如下面的代码(摘自Thinking in Java):
import java.util.*;
class Frob {}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION,MOMENTUM> {}
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<Frob>();
Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
Quark<Fnorkle> quark = new Quark<Fnorkle>();
Particle<Long,Double> p = new Particle<Long,Double>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
}
}
它的输出是:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
代码里面使用了Class.getTypeParameters( )方法,这个方法在JDK的文档里面的说明为:returns an array of TypeVariable objects that represent the type variables declared by the generic declaration...。也就是说她能够返回泛型声明中的变量的类型。
可能你会认为通过这个方法就能够拿到使用泛型时候的具体变量类型。但是事实并非如此,你能拿到的仅仅是在泛型声明中用作占位符的标识符号。
所以,事实就是,在使用泛型的代码里面你是没有办法拿到关于泛型参数的任何类型信息的,也就是你无法动态地知道泛型参数的具体类型。
这就是Java实现泛型时候的擦除机制。当你使用泛型的时候,Java会擦去所有的与特定类型有关的信息。你知道的仅仅是你正在使用一个对象而已。在Java看来,List<Integer>和List<String>是同一个类型,因为他们的特定类型信息都被擦除到原始类型了,也就是List,具体的类型信息都被擦掉了。
由于Java的擦除,使得在C++里面看起来很合理的代码,到了Java就不行了,比如有这样的C++ template代码:
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj;
public:Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }
};
class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};
int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipulate();
}
/* Output:
HasF::f()
上面的代码中,Manipulator类持有一个T类型的泛型参数。注意manipulate方法,在这个方法里面直接调用了f()函数。C++在初始化template的时候进行检查,编译器会检查你传入的HasF类是不是包含有f()函数。这种做法也是保证类型安全的。
但是在Java里面,如果将上面的代码直接翻译过来:
//: generics/Manipulation.java
// {CompileTimeError} (Won’t compile)
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
// Error: cannot find symbol: method f():
public void manipulate() { obj.f(); }
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator =
new Manipulator<HasF>(hf);
manipulator.manipulate();
}
} ///:~
这个时候Java编译器是会报错的,原因就是Java的擦除。在编译和运行时是无法知道泛型代码中的具体类型信息的。所以为了能够使用f()方法,在Java中我们就需要为泛型代码加绑定(bound)。如下:
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
} ///:~
<T extends HasF>表示T必须是HasF类或者它的子类。如果这一条满足了,那么调用f()就是安全的。
这里可以理解为,T被擦除到了HasF这个类型。你会发现在Java中这样使用泛型其实没什么用处,你可以轻而易举地去掉泛型而得到一段与使用了泛型一模一样的代码:
class Manipulator3 {
private HasF obj;
public Manipulator3(HasF x) { obj = x; }
public void manipulate() { obj.f(); }
} ///:~
所以,在Java中,合适使用泛型的情况是当你需要你的类或者方法支持多个类型的时候。但是也不能说<T extends HasF>这种形式就没有好处,比如当你的方法返回T的时候,你就能得到具体类型,而不用作强制类型转换了。
因为泛型并不是自Java诞生之日起就有的,因此Java之所以有这样的擦除机制,不是因为Sun的大牛们不知道问题的存在,而是为了兼容以前的Java版本。所以,可以说擦除并不是Java语言的特性。为了使得JDK1.5以前的代码能够使用带泛型特征的类库,或者1.5之后的代码能够使用1.5版本以前的类库,Java选择了擦除。
备注:
不能使用Java泛型类型的情况:
[list=1]
需要使用具体运行时类型信息的情况,如类型转换,instranceof运算符,new运算符等。当你定义了一个类,
class Foo<T> {
T var;
}
并且这样使用时,
Foo<Cat> f = new Foo<Cat>();
不要以为你的变量var已经是Cat类型了,一定要记住,它是Object类型的,因为Java的擦除。
因为擦除和版本兼容,泛型并不是被强制的。如下面代码:
class GenericBase<T> {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}
class Derived1<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
public class ErasureAndInheritance {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
} ///:~
Derivered2在继承GenericBase的时候没有使用泛型语法,但是编译器并没有警告提示,只有当调用d2.set()的时候编译器才提示了警告信息。从这个例子也可以看出,泛型的具体类型信息确实是被擦除掉了。在这里,泛型类型T被擦成了Object。
[/list]
分享到:
相关推荐
Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入,极大地增强了代码的类型安全性和重用性。本篇文章将带你入门Java泛型,通过实例深入理解其核心概念。 1. **泛型的基本概念** - 泛型...
Java泛型是Java语言中用于处理类型安全的一种机制,它允许在编译期间提供类型检查,并在运行时消除了类型转换。Java泛型深入的内容涵盖泛型的基本概念、泛型类、接口、方法以及泛型的使用限制和高级特性。 首先,...
根据提供的文件信息,我们可以确定本书的标题为《Java泛型和集合》(Java Generics and Collections),作者为Maurice Naftalin和Philip Wadler。该书详细介绍了Java中的泛型(Generics)特性以及集合(Collections...
本文将深入探讨Java泛型类型擦除的概念,并介绍在类型擦除后,为了保持泛型的安全性和便利性,Java设计者所采取的一些补偿机制。 1. **类型擦除**: - 在编译期间,所有的泛型类型信息都会被替换为它们的实际类型...
6. **擦除与桥方法(Erasure and Bridge Methods)**:Java泛型是通过类型擦除实现的,这意味着在运行时,所有泛型信息都会被移除。为了保持多态性,编译器会自动生成桥方法,以处理不同泛型类型的多态调用。 在...
### JVM如何理解Java泛型类 #### 一、引言 在Java中,泛型是一种强大的功能,它允许程序员编写灵活且类型安全的代码。然而,对于Java虚拟机(JVM)来说,它实际上并不理解泛型的概念。所有的泛型信息在编译阶段就被...
### Java泛型指南经典知识点解析 #### 一、引言 Java 1.5 版本引入了一系列重要的语言特性,其中“泛型”是其中一项关键特性。泛型的引入极大地提高了 Java 语言的类型安全性和代码重用性。本文档旨在深入探讨 ...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入。泛型允许程序员在代码中使用类型参数,从而增加了代码的类型安全性和重用性。它能够帮助我们避免在运行时出现ClassCastException...
在本小测试中,我们将探讨几个与Java泛型相关的概念:通配符(Wildcards)、消除(Erasure)、协变(Covariance)和原始类型(Raw Types)。以下是对这些概念的详细解释: 1. **通配符(Wildcards)** - 通配符是...
Java泛型的实现方式被称为“擦除”(Erasure),即在编译阶段,编译器会根据泛型参数进行类型检查和推断,但最终生成的字节码并不包含泛型信息。这意味着,如`List<Integer>`和`List<String>`在运行时实际上是同一个...
Java泛型在运行时会被擦除,因此泛型不提供运行时的类型检查。所有的泛型类和方法都会有一个没有泛型的等价形式,这个过程称为类型擦除。 4. **通配符的使用**: - 无界通配符:`?`,表示类型参数可以是任何类型...
4. **类型擦除**:Java泛型在编译后会进行类型擦除,这意味着在运行时所有的泛型信息都会丢失,泛型只在编译时起作用,用于增强类型安全。 5. **协变与逆变**:泛型的协变(Covariance)和逆变(Contravariance)...
Java泛型是自Java 1.5版本引入的一项重要特性,它允许在编程时指定容器(如List、Set、Map等)所存储数据的类型,从而增强了类型安全性和代码的可读性。泛型的基本概念包括类型参数(Type Parameter)、形式类型参数...
Java泛型的一个重要特性是类型擦除(Erasure),这意味着在编译时类型参数会被擦除掉,只保留原始类型。例如,`List<Integer>` 和 `List<String>` 在运行时都被看作是 `List` 的实例。因此,泛型信息不会传递到运行...
环境:Windows XP Professional、JDK 1.6、Ant 1.7 说明:Java泛型的动机是为解决类型转换在编译时不报错的问题。另外由于“范型编程”(Generic Programming)的推广,于是2004年JDK 5.0引用范型标准。本例子说明...
Java泛型是JDK 1.5引入的重要特性,它为集合类和其他容器类提供了类型安全的机制。泛型的主要目标是允许在编译时检查类型安全,避免强制类型转换,同时提供更好的性能。 1. **强类型集合类**: 在Java中,集合类如...