Summary
There's one rather mind-bending idiom that appears periodically in Java generics. Here's what it looks like: class SelfBounded<T extends SelfBounded<T>>
Advertisement
This has the dizzying effect of two mirrors pointed at each other, a kind of infinite reflection. The class SelfBounded takes a generic argument T, T is constrained by a bound, and that bound is this class, with T as an argument.
Although it's difficult to parse when you first see it, it emphasizes that the extends keyword, when used with bounds, is definitely different than when it is used to create classes.
To understand what this idiom means, 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
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.
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 derived 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.
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());
}
} ///:~
So the question is: What does this constraint buy you?
One of the nice things it offers is that you can define methods within the class that use parameters of the same type as this new class.
Using your example:
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> {}
....and....
A a = new A(); a.set(new A());
........... It's not obvious here, but in a.set(T), class A is guaranteed to receive an argument of type A. This is nice for methods such as compareTo(T) (where it makes sense to define the method as necessarily operating on arguments of the same type of its enclosing class).
The self-bounded generic is a way of expressing a MyType relationship for parameters in subclasses. This is an instance of covariant parameter types, which normally is prohibited (or is turned into ad-hoc overloading). It provides a way to actually enforce something like "As this class is subclassed, the argument to method f must follow that subclassing".
As the previous poster suggested, a canonical instance of this is the Comparable interface. Without the self-bounded generic, one could only write
interface Comparable {
int compareTo(Object o);
}
which would allow compareTo() to be applied to any argument at all. It'd be nice if the compiler ensured we only compared "similar" objects. Hence, Comparable is written
interface Comparable<T extends Comparable<T>> {
int compareTo(T o);
}
A similar case could be made for equality (though Java doesn't do this):
So Jay, if I understand you correctly you're saying that self-bounding generics are about argument types rather than return types? In particular, allowing the argument types to vary to follow subclassing?
> So Jay, if I understand you correctly you're saying that > self-bounding generics are about argument types > rather than return types? In particular, allowing the > argument types to vary to follow subclassing?
That's where their value lies, yes. Since return types are permitted to vary covariantly during subclassing, self-bounded generics gain you no additional expressiveness there. It's the ability to specify "this argument should be 'just like me', even after subclassing", that is only expressible by self-bounded generics.
> Because it doesn't actually appear that way in the JDK > (see the Javadocs); it's just > > interface Comparable<T> { > int compareTo(T o); > } > > I assume that you're saying here that it could be > written that way?
I believe it cannot be done because it would cause too many problems with existing code.
> That's where their value lies, yes. Since return types are > permitted to vary covariantly during subclassing, > self-bounded generics gain you no additional > expressiveness there. It's the ability to specify "this > argument should be 'just like me', even after > subclassing", that is only expressible by self-bounded > generics.
You probably understand this but let's be absolutely clear: if by 'just like me' you mean 'same type as this' that last sentence is false. A self-bounded type is not a self-type. It's a common misconception that self-bounded types are equivalent to self-types. See the "Generics and covariant return types" post# 8 for example of this.
A self-bounded type cannot guarantee that the argument will be the same type as this after subclassing. There is no equivalent to self-types in Java generics. Personally, I think the syntax should be added to allow something like:
OK, to sum it up: self-bounding types are to enable covariant argument types.
Here's my current example to show this. However, as discussed, returning this produces a kind of weird type. I got it to work, but I have no idea if it's useful or not:
//: generics/SelfBounding2.java
class SelfBounded2<T extends SelfBounded2<T>> {
T element;
SelfBounded2<T> set(T arg) {
element = arg;
returnthis;
}
T get() { return element; }
void f(T arg) {}
void g(T arg) { element.f(arg); }
}
class User extends SelfBounded2<User> {
void h(User arg) { g(arg); }
}
publicclass SelfBounding2 {
publicstaticvoid main(String[] args) {
User user1 = new User(), user2 = new User();
SelfBounded2<User> sbUser = user1.set(user2);
User user4 = user2.get();
user1.f(user2);
user2.g(user1);
user1.h(user2);
}
} ///:~
> You probably understand this but let's be absolutely > clear: if by 'just like me' you mean 'same type as > this' that last sentence is false. A self-bounded > type is not a self-type. It's a common > misconception that self-bounded types are equivalent to > self-types. See the "Generics and covariant return types" > post# 8 for example of this. > A self-bounded type cannot guarantee that the argument > will be the same type as this after subclassing.
Hmm. If you mean "exactly the same type", I agree. The self-generic bound type will be a subtype of the type of 'this', though. I looked at the post you described and didn't see this contradicted.
What you're referring to here:
> There is no equivalent to self-types in Java generics.
are you referrring to "exact types"? and where MyType generally implies "exact MyType"?
> > You probably understand this but let's be absolutely > > clear: if by 'just like me' you mean 'same type as > > this' that last sentence is false. A > self-bounded > > type is not a self-type. It's a common > > misconception that self-bounded types are equivalent to > > self-types. See the "Generics and covariant return > types" > > post# 8 for example of this. > > A self-bounded type cannot guarantee that the argument > > will be the same type as this after subclassing. > > Hmm. If you mean "exactly the same type", I agree. The > self-generic bound type will be a subtype of the type of > 'this', though. I looked at the post you described and > didn't see this contradicted.
I think you misunderstand. The post I referred to is an example of this misconception.
I'll just post the counter-example here (Bruce already gave it in in the blog, actually.)
publicclass Test
{
publicstaticvoid main(String[] args)
{
A a = new B().f();
//B b = new B().f(); does not compile
}
}
abstractclass HasF< T extends HasF< T > > {
@SuppressWarnings( "unchecked" )
T f() {
System.out.println( "HasF.f()" );
return (T)this;
}
}
class A extends HasF<A>
{
}
class B extends HasF<A>
{
}
If you run the above, you will get a ClassCastException. This is because T is not guaranteed to be an instance of this.getClass(). A self-bounded type is not a self-type.
I don't really speak Java and so may be missing some of the constraint semantics here, but isn't this just C++'s "Curiously Recurring Template Pattern"?
Flat View: This topic has 28 replies
on 2 pages
[
12
|
»
]