Self-bounded types
There's one rather mind-bending(adj. 离奇古怪令人费解的;强烈影响心绪的) idiom that appears periodically in Java generics. Here's what it looks like:
class SelfBounded<T extends SelfBounded<T>> { / / .. .
This has the dizzying(adj. 令人昏乱的;极快的;灿烂的) effect of two mirrors pointed at each other, a kind of infinite(adj. 无限的,无穷的;无数的;极大的) reflection. The class SelfBounded takes a generic argument T, T is constrained by a bound, and that bound is SelfBounded, with T as an argument.
This is difficult to parse when you first see it, and it emphasizes that the extends keyword, when used with bounds, is definitely different than when it is used to create subclasses.
Curiously recurring generics
To understand what a self-bounded type means, let's start with a simpler version of the idiom, without the self-bound.
You can't inherit directly from a generic parameter. However, you can inherit from a class that uses that generic parameter in its own definition. That is, you can say:
//: generics/CuriouslyRecurringGeneric.java class GenericType<T> {} public class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric> {} ///:~
This could be called curiously recurring generics (CRG) after Jim Coplien's Curiously Recurring Template Pattern in C++. The "curiously recurring" part refers to the fact that your class appears, rather curiously, in its own base class.
To understand what this means, try saying it aloud: "I'm creating a new class that inherits from a generic type that takes my class name as its parameter."What can the generic base type accomplish when given the derived class name? Well, generics in Java are about arguments and return types, so it can
produce a base class that uses the derived type for its arguments and return types. It can also use the derived type for field types, even though those will be erased to Object. Here's a generic class that expresses this:
//: generics/BasicHolder.java public class BasicHolder<T> { T element; void set(T arg) { element = arg; } T get() { return element; } void f() { System.out.println(element.getClass().getSimpleName()); } } ///:~
It's an ordinary generic type with methods that both accept and produce objects of the parameter type, along with a method that operates on the stored field (although it only performs Object operations on that field).We can use BasicHolder in a curiously recurring generic:
//: generics/CRGWithBasicHolder.java class Subtype extends BasicHolder<Subtype> {} public class CRGWithBasicHolder { public static void main(String[] args) { Subtype st1 = new Subtype(), st2 = new Subtype(); st1.set(st2); Subtype st3 = st1.get(); st1.f(); } } /* Output: Subtype *///:~
Notice something important here: The new class Subtype takes arguments and returns values of Subtype, not just the base class BasicHolder. This is the essence of CRG: The base class substitutes the derived class for its parameters. This means that the generic base class becomes a kind of template for common functionality for all its derived classes, but this functionality will use the derived type for all of its arguments and return values. That is, the exact type instead of the base type will be used in the resulting class. So in Subtype, both the argument to set( ) and the return type of get( ) are exactly Subtypes.
Self-bounding
The BasicHolder can use any type as its generic parameter, as seen here:
//: generics/Unconstrained.java class Other {} class BasicOther extends BasicHolder<Other> {} public class Unconstrained { public static void main(String[] args) { BasicOther b = new BasicOther(), b2 = new BasicOther(); b.set(new Other()); Other other = b.get(); b.f(); } } /* Output: Other *///:~
Self-bounding takes the extra step of forcing the generic to be used as its own bound argument. Look at how the resulting class can and can't be used:
//: generics/SelfBounding.java class SelfBounded<T extends SelfBounded<T>> { T element; SelfBounded<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A extends SelfBounded<A> {} class B extends SelfBounded<A> {} // Also OK class C extends SelfBounded<C> { C setAndGet(C arg) { set(arg); return get(); } } class D {} // Can't do this: // class E extends SelfBounded<D> {} // Compile error: Type parameter D is not within its bound // Alas, you can do this, so you can't force the idiom: class F extends SelfBounded {} public class SelfBounding { public static void main(String[] args) { A a = new A(); a.set(new A()); a = a.set(new A()).get(); a = a.get(); C c = new C(); c = c.setAndGet(new C()); } } ///:~
What self-bounding does is require the use of the class in an inheritance relationship like this:
class A extends SelfBounded<A> {}
This forces you to pass the class that you are defining as a parameter to the base class.
What's the added value in self-bounding the parameter? The type parameter must be the same as the class being defined. As you can see in the definition of class B, you can also derive from a SelfBounded that uses a parameter of another SelfBounded, although the predominant use seems to be the one
that you see for class A. The attempt to define E shows that you cannot use a type parameter that is not a SelfBounded.
Unfortunately, F compiles without warnings, so the self-bounding idiom is not enforceable. If it's really important, it may require an external tool to ensure that raw types are not being used in place of parameterized types.Notice that you can remove the constraint and all the classes will still compile, but E will also compile:
//: generics/NotSelfBounded.java public class NotSelfBounded<T> { T element; NotSelfBounded<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A2 extends NotSelfBounded<A2> {} class B2 extends NotSelfBounded<A2> {} class C2 extends NotSelfBounded<C2> { C2 setAndGet(C2 arg) { set(arg); return get(); } } class D2 {} // Now this is OK: class E2 extends NotSelfBounded<D2> {} ///:~
So clearly, the self-bounding constraint serves only to force the inheritance relationship. If you use self-bounding, you know that the type parameter used by the class will be the same basic type as the class that's using that parameter. It forces anyone using that class to follow that form.
It's also possible to use self-bounding for generic methods:
//: generics/SelfBoundingMethods.java public class SelfBoundingMethods { static <T extends SelfBounded<T>> T f(T arg) { return arg.set(arg).get(); } public static void main(String[] args) { A a = f(new A()); } } ///:~
This prevents the method from being applied to anything but a self-bounded argument of the form shown.
Argument covariance
The value of self-bounding types is that they produce covariant argument types—method argument types vary to follow the subclasses.
Although self-bounding types also produce return types that are the same as the subclass type, this is not so important because covariant return types were introduced in Java SE5:
//: generics/CovariantReturnTypes.java class Base {} class Derived extends Base {} interface OrdinaryGetter { Base get(); } interface DerivedGetter extends OrdinaryGetter { // Return type of overridden method is allowed to vary: Derived get(); } public class CovariantReturnTypes { void test(DerivedGetter d) { Derived d2 = d.get(); } } ///:~
The get( ) method in DerivedGetter overrides get( ) in OrdinaryGetter and returns a type that is derived from the type returned by OrdinaryGetter.get( ). Although this is a perfectly logical thing to do—a derived type method should be able to return a more specific type than the base type method that it's overriding—it was illegal in earlier versions of Java.
A self-bounded generic does in fact produce the exact derived type as a return value, as seen here with get( ):
//: generics/GenericsAndReturnTypes.java interface GenericGetter<T extends GenericGetter<T>> { T get(); } interface Getter extends GenericGetter<Getter> {} public class GenericsAndReturnTypes { void test(Getter g) { Getter result = g.get(); GenericGetter gg = g.get(); // Also the base type } } ///:~
Notice that this code would not have compiled unless covariant return types were included in Java SE5.
In non-generic code, however, the argument types cannot be made to vary with the subtypes:
//: generics/OrdinaryArguments.java class OrdinarySetter { void set(Base base) { System.out.println("OrdinarySetter.set(Base)"); } } class DerivedSetter extends OrdinarySetter { void set(Derived derived) { System.out.println("DerivedSetter.set(Derived)"); } } public class OrdinaryArguments { public static void main(String[] args) { Base base = new Base(); Derived derived = new Derived(); DerivedSetter ds = new DerivedSetter(); ds.set(derived); ds.set(base); // Compiles: overloaded, not overridden! } } /* Output: DerivedSetter.set(Derived) OrdinarySetter.set(Base) *///:~
Both set(derived) and set(base) are legal, so DerivedSetter.set( ) is not overriding OrdinarySetter.set( ), but instead it is overloading that method. From the output, you can see that there are two methods in
DerivedSetter, so the base-class version is still available, thus verifying that it has been overloaded.
However, with self-bounding types, there is only one method in the derived class, and that method takes the derived type as its argument, not the base type:
//: generics/SelfBoundingAndCovariantArguments.java interface SelfBoundSetter<T extends SelfBoundSetter<T>> { void set(T arg); } interface Setter extends SelfBoundSetter<Setter> {} public class SelfBoundingAndCovariantArguments { void testA(Setter s1, Setter s2, SelfBoundSetter sbs) { s1.set(s2); // s1.set(sbs); // Error: // set(Setter) in SelfBoundSetter<Setter> // cannot be applied to (SelfBoundSetter) } } ///:~
The compiler doesn't recognize the attempt to pass in the base type as an argument to set( ), because there is no method with that signature. The argument has, in effect, been overridden.
Without self-bounding, the ordinary inheritance mechanism steps in, and you get overloading, just as with the non-generic case:
//: generics/PlainGenericInheritance.java class GenericSetter<T> { // Not self-bounded void set(T arg){ System.out.println("GenericSetter.set(Base)"); } } class DerivedGS extends GenericSetter<Base> { void set(Derived derived){ System.out.println("DerivedGS.set(Derived)"); } } public class PlainGenericInheritance { public static void main(String[] args) { Base base = new Base(); Derived derived = new Derived(); DerivedGS dgs = new DerivedGS(); dgs.set(derived); dgs.set(base); // Compiles: overloaded, not overridden! } } /* Output: DerivedGS.set(Derived) GenericSetter.set(Base) *///:~
This code mimics OrdinaryArguments.java; in that example,DerivedSetter inherits from OrdinarySetter which contains a set(Base).Here, DerivedGS inherits from GenericSetter<Base> which also contains a set(Base), created by the generic. And just like OrdinaryArguments.java, you can see from the output that DerivedGS contains two overloaded versions of set( ). Without self-bounding, you overload on argument types. If you use self-bounding, you only end up with one version of a method, which takes the exact argument type.
相关推荐
《Thinking in Java》是Bruce Eckel的经典之作,第四版涵盖了Java编程语言的广泛主题,适合初学者和有经验的程序员。这本书深入浅出地讲解了Java的核心概念和技术,旨在帮助读者建立坚实的编程基础,并理解面向对象...
根据提供的文件信息,我们可以深入探讨《Java泛型与集合》这本书中的关键知识点。该书由Maurice Naftalin和Philip Wadler合著,并于2007年由O'Reilly Media出版。以下是对该书内容的一个综合概述,旨在帮助读者理解...
在“Java Generics - Our Generics Class - Part 3 Source code”中,我们可以期待深入理解如何设计和实现自己的泛型类。这部分源代码可能包含对泛型类、泛型方法、通配符、边界以及类型擦除等概念的实例。以下是...
在本教程中,我们将深入探讨Java泛型的源码实现,特别是"Java-Generics-Our-Generics-Class-Part-3-Source-code"项目中的部分。 首先,泛型的主要目标是消除运行时类型转换异常,例如`ClassCastException`。通过在...
《Thinking in Java》是Java编程领域的一本经典著作,由Bruce Eckel撰写,深受程序员喜爱。这本书分为第三版和第四版,提供了英文版和中文版,适合不同语言背景的学习者。书中内容详实且深入,从基础知识到高级概念...
泛型与集合 使用 进行初步翻译. 将利用碎片时间进行整理和校对,完整的时间段适合做其他需要大量思考的事,如果你有兴趣欢迎提交PR。 TODO 数据校对 目录 2.4 获取和放置原则 2.5 数组 2.6 通配符与类型参数 ...
《Thinking in Java》是Bruce Eckel的经典编程教材,它深入浅出地介绍了Java语言的核心概念和技术。这本书通过实例代码来讲解理论,使读者能够更好地理解和掌握Java编程。在这个压缩包中,我们很可能会找到与书中的...
很抱歉,但根据您给出的信息,这似乎是一个音乐文件列表,而非与"Thinking in Java"相关的IT知识内容。"Thinking in Java"是一本著名的编程书籍,通常与Java编程语言的学习和实践相关。如果您的目标是获取这方面的...
《Thinking in Java》是Bruce Eckel的经典编程教材,它涵盖了Java语言的核心概念和技术,深受程序员和初学者喜爱。这本书分为第三版和第四版,每个版本都有其独特的知识点和更新内容。 第三版是针对Java 2 Platform...
本项目"Java-Generics-and-Collections-Example"提供了对这两个主题的实例化理解和实践。 1. **Java泛型**: - 泛型是Java SE 5.0引入的新特性,它允许在类、接口和方法声明中使用类型参数,从而增强了类型检查和...
《Thinking in Java(英文原版第4版)》作为一本经典Java编程思想书籍,其内容涵盖了面向对象的叙述方式,并针对Java SE5/6版本新增了示例和章节。本书适合作为初学者的入门教材,同时也包含了足够的深度,适合专业...
Java是一种广泛使用的面向对象的编程语言,以其平台独立性、安全性、健壮性和高性能而闻名。这个"java基础教程----精华版"显然是一份精心整理的资料,旨在帮助初学者快速掌握Java编程的基础知识。下面将详细介绍Java...
《Thinking in Java》会详细解释如何使用这些集合以及迭代器(Iterator)和泛型(Generics)的概念,这些对于编写高效且可维护的代码至关重要。 多线程是并发编程的基础,Java提供了强大的支持。书中会讨论线程的...
### 《Thinking in Java》第四版关键知识点综述 #### 一、书籍概述与评价 《Thinking in Java》第四版是一本备受推崇的经典Java编程教材,由Bruce Eckel撰写,适用于初学者及进阶读者。本书自出版以来,便成为众多...
在有关Java核心的系列文章中,我们将继续学习2个新内容,即Generics和Collection,它们是Java中非常流行的对象。 泛型格式化参数化数据类型,以便我们可以将类,接口或方法用于许多不同的数据类型。 集合只是具有...
本书"Java Generics and Collections"深入探讨了这两个主题,帮助开发者编写更安全、更高效且可维护的代码。 首先,让我们来理解Java泛型。泛型是Java 5引入的一项特性,它允许在类、接口和方法中使用类型参数。这...