The mystery of erasure
As you begin to delve more deeply into generics, there are a number of things that won't initially make sense. For example, although you can say ArrayList.class, you cannot say ArrayList<Integer>.class. And
consider the following:
//: generics/ErasedTypeEquivalence.java import java.util.ArrayList; public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2);// True } } /* * Output: true */// :~
ArrayList<String> and ArrayList<Integer> could easily be argued to be distinct types. Different types behave differently, and if you try, for example, to put an Integer into an ArrayList<String>, you get different behavior (it fails) than if you put an Integer into an ArrayList< Integer > (it succeeds). And yet the above program suggests that they are the same type.
Here's an example that adds to this puzzle:
//: generics/LostInformation.java import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; 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())); } }
According to the JDK documentation, Class.getTypeParameters( ) "returns an array of TypeVariable objects that represent the type variables declared by the generic declaration..." This seems to suggest that you might be able to find out what the parameter types are. However, as you can see from the output, all you find out is the identifiers that are used as the parameter placeholders, which is not such an interesting piece of information.
The cold truth is:
There's no information about generic parameter types available inside generic code.
Thus, you can know things like the identifier of the type parameter and the bounds of the generic type—you just can't know the actual type parameter(s) used to create a particular instance. This fact, which is especially frustrating if you're coming from C++, is the most fundamental issue that you must deal
with when working with Java generics.
Java generics are implemented using erasure. This means that any specific type information is erased when you use a generic. Inside the generic, the only thing that you know is that you're using an object. So List<String> and List< Integer> are, in fact, the same type at run time. Both forms are "erased" to their raw type, List. Understanding erasure and how you must deal with it will be one of the biggest hurdles you will face when learning Java generics, and that's what we'll explore in this section.
The C++ approach
Here's a C++ example which uses templates. You'll notice that the syntax for parameterized types is quite similar, because Java took inspiration from C++:
//: generics/Templates.cpp #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() ///:~
The Manipulator class stores an object of type T. What's interesting is the manipulate( ) method, which calls a method f( ) on obj. How can it know that the f( ) method exists for the type parameter T? The C++ compiler checks when you instantiate the template, so at the point of instantiation of Manipulator <HasF>, it sees that HasF has a method f( ). If it were not the case, you'd get a compile-time error, and thus type safety is preserved.
Writing this kind of code in C++ is straightforward because when a template is instantiated, the template code knows the type of its template parameters.Java generics are different. Here's the translation of HasF:
//: generics/HasF.java public class HasF { public void f() { System.out.println("HasF.f()"); } } ///:~
If we take the rest of the example and translate it to Java, it won't compile:
//: 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(); } } ///:~
Because of erasure, the Java compiler can't map the requirement that manipulate( ) must be able to call f( ) on obj to the fact that HasF has a method f( ). In order to call f( ), we must assist the generic class by giving it a bound that tells the compiler to only accept types that conform to that bound.This reuses the extends keyword. Because of the bound, the following compiles:
//: generics/Manipulator2.java class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } } ///:~
The bound <T extends HasF> says that T must be of type HasF or something derived from HasF. If this is true, then it is safe to call f( ) on obj.
We say that a generic type parameter erases to its first bound (it's possible to have multiple bounds, as you shall see later). We also talk about the erasure of the type parameter. The compiler actually replaces the type parameter with its erasure, so in the above case, T erases to HasF, which is the same as replacing T with HasF in the class body.
You may correctly observe that in Manipulations.Java, generics do not contribute anything. You could just as easily perform the erasure yourself and produce a class without generics:
//: generics/Manipulator3.java class Manipulator3 { private HasF obj; public Manipulator3(HasF x) { obj = x; } public void manipulate() { obj.f(); } } ///:~
This brings up an important point: Generics are only useful when you want to use type parameters that are more "generic" than a specific type (and all its subtypes)—that is, when you want code to work across multiple classes. As a result, the type parameters and their application in useful generic code will
usually be more complex than simple class replacement. However, you can't just say that anything of the form <T extends HasF> is therefore flawed. For example, if a class has a method that returns T, then generics are helpful,because they will then return the exact type:
//: generics/ReturnGenericType.java class ReturnGenericType<T extends HasF> { private T obj; public ReturnGenericType(T x) { obj = x; } public T get() { return obj; } } ///:~
You have to look at all the code and understand whether it is "complex enough" to warrant the use of generics.
We'll look at bounds in more detail later in the chapter.
相关推荐
《Thinking in Java》是Bruce Eckel的经典之作,第四版涵盖了Java编程语言的广泛主题,适合初学者和有经验的程序员。这本书深入浅出地讲解了Java的核心概念和技术,旨在帮助读者建立坚实的编程基础,并理解面向对象...
《Thinking in Java》是Java编程领域的一本经典著作,由Bruce Eckel撰写,深受程序员喜爱。这本书分为第三版和第四版,提供了英文版和中文版,适合不同语言背景的学习者。书中内容详实且深入,从基础知识到高级概念...
很抱歉,但根据您给出的信息,这似乎是一个音乐文件列表,而非与"Thinking in Java"相关的IT知识内容。"Thinking in Java"是一本著名的编程书籍,通常与Java编程语言的学习和实践相关。如果您的目标是获取这方面的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它深入浅出地介绍了Java语言的核心概念和技术。这本书通过实例代码来讲解理论,使读者能够更好地理解和掌握Java编程。在这个压缩包中,我们很可能会找到与书中的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它涵盖了Java语言的核心概念和技术,深受程序员和初学者喜爱。这本书分为第三版和第四版,每个版本都有其独特的知识点和更新内容。 第三版是针对Java 2 Platform...
《Thinking in Java(英文原版第4版)》作为一本经典Java编程思想书籍,其内容涵盖了面向对象的叙述方式,并针对Java SE5/6版本新增了示例和章节。本书适合作为初学者的入门教材,同时也包含了足够的深度,适合专业...
《Thinking in Java》会详细解释如何使用这些集合以及迭代器(Iterator)和泛型(Generics)的概念,这些对于编写高效且可维护的代码至关重要。 多线程是并发编程的基础,Java提供了强大的支持。书中会讨论线程的...
### 《Thinking in Java》第四版关键知识点综述 #### 一、书籍概述与评价 《Thinking in Java》第四版是一本备受推崇的经典Java编程教材,由Bruce Eckel撰写,适用于初学者及进阶读者。本书自出版以来,便成为众多...
《Thinking in Java》是Bruce Eckel的经典之作,第四版更是深受全球Java开发者喜爱的教材。这本书深入浅出地讲解了Java编程语言的核心概念和技术,旨在为初学者提供全面且深入的Java学习指导。 首先,书中的第一章...
《Thinking in Java 4th Edition with Annotated Solution Guide》是一本经典的Java编程教材,由Bruce Eckel撰写。这本书深入浅出地介绍了Java编程的核心概念和技术,对于初学者和经验丰富的开发者来说,都是一个...
《Thinking in Java》是Java编程领域的一本经典之作,由Bruce Eckel撰写,深受程序员喜爱。这本书深入浅出地介绍了Java语言的核心概念和技术,旨在帮助读者建立起坚实的基础,并提升编程思维能力。书中不仅包含了...
《Thinking in Java 4th 习题答案》涵盖了多个关键的Java编程概念和技术,包括深入的容器理解、输入/输出(I/O)处理、枚举类型(Enumerated Types)、注解(Annotations)以及并发编程(Concurrency)。这些章节是Java学习...
《Thinking In Java》是Bruce Eckel的经典之作,它深入浅出地介绍了Java编程语言的核心概念和技术,被广大程序员视为学习Java的权威指南。第四版更是加入了更多现代Java特性,如Generics、Annotations等,使读者能够...
Java 泛型详解 Java 中的泛型是 Java 5(JDK 1.5)中引入的一项新特性,旨在解决类型安全和代码重用的问题。泛型允许程序员对类型进行抽象,使得代码更加灵活和可维护。 泛型的优点: 1. 类型安全:泛型可以在...
《Thinking in Java》是Bruce Eckel的经典著作,它在Java编程领域享有极高的声誉,是许多初学者和专业开发者的必备参考书籍。该书的第三版中文电子版PDF提供了全面而深入的Java学习资源,旨在帮助读者理解Java语言的...
本资料 "[Java泛型和集合].(Java.Generics.and.Collections).Maurice.Naftalin&Philip.Wadler.文字版" 由知名专家Maurice Naftalin和Philip Wadler编著,提供了关于这些主题的深入理解。 **Java泛型** 是自Java...