NOTE: This question is not Enum-related, so it's not duplicate. Enum's are forced to compare only-with-itself because compiler generation of type parameter, not because java recursive type parameter.
I'm trying to find an advantage for declaring a class as:
public class Some<E extends Some<E>>
versus declaring it as:
public class Some<E extends Some>
I have tried providing methods returning E
and methods returning Some<E>
, different cross-calls in complicated class hierarchy and every time I've tried to remove additional <E>
- no new errors/warnings came up.
Can you show me a method that proves the advantage of this additional <E>
?
I assume that there exists one because of JDK declarations:<E extends Comparable<? super E>>
Responses to other questions on SO gives for example:
With the additional construct, you know that any class that extends Enum is comparable only against itself
But, I can easily break this theory:
public static class Animal<E extends Animal<E>> {
public boolean compare(E other) {...}
}
public class Cat extends Animal<Cat> { }
public class Dog extends Animal<Cat> { } // Note "Cat" !!!
Despite the generic recursion, i can still compare Dog with Cat:
Dog dog = new Dog();
dog.compare(new Cat());
Transalting theory:
you know that any class that extends Animal is comparable only against itself
this is false - I comapred class Dog which extends Animal with Cat, not itself.
There are very few situations in which a bound like class Some<E extends Some<E>>
is necessary. Most of the time people write this, the bound is not actually utilized in the code, and class Some<E>
would work just as well.
However, there are some particular situations in which the bound in class Some<E extends Some<E>>
is actually used. For example:
abstract class Some<E extends Some<E>> {
abstract E foo();
Some<E> bar() {
return foo();
}
}
As to your question -- what about class Some<E extends Some>
? Well, first most glaring issue is that you're using a raw type. Raw types should never be used in new code. But you are not convinced.
The raw type with the above class (class Some<E extends Some>
) does compile with a warning (which you can ignore to your own peril). However, the raw type means it's possible to do unsafe things with it.
It takes some effort to come up with an example to demonstrate that it's unsafe. Here is one:
abstract class Some<E extends Some> {
abstract E foo();
Some<E> bar() {
return foo();
}
}
class SomeFoo extends Some<SomeFoo> {
SomeFoo foo() { return this; }
}
class SomeBar extends Some<SomeFoo> {
SomeFoo foo() { return new SomeFoo(); }
}
class SomeBaz extends Some<SomeBar> {
SomeBar foo() { return new SomeBar(); }
}
// then in some method:
Some<SomeBar> a = new SomeBaz();
Some<SomeBar> b = a.bar();
SomeBar c = b.foo();
The code compiles with warnings but no errors, and throws a ClassCastException
at runtime.
Concerning the statement
every time I've tried to remove additional - no new errors/warnings came up.
This should not be the case. It should print a warning, because you are using the raw type Some
, and the result of this is missing type safety, as demonstrated in the answer by newacct.
The Dog
/Cat
example is a bit contrived, and somewhat flawed. You suggested declaring a class
public class Dog extends Animal<Cat> { } // Note "Cat" !!!
But here, the type parameter basically means: "This parameter (Cat
) is the type that objects of this class (Dog
) can be compared with". You are thus explicitly stating that a Dog
should be comparable to Cat
. Even with sophisticated languages and smart compilers, after all, it's in the responsibility of the programmers to write code that makes sense.
Indeed, there are not many cases where these self-referential generic types are necessary. One of these examples is sketched in this FAQ entry: It declares a structure of nodes (that is, a tree) where the type parameter can be used to decouple the definition of the tree structure from the actual type of the nodes:
public class RecurringTest {
public static void main(String[] args) {
SpecialNode sa = new SpecialNode(null);
SpecialNode sb = new SpecialNode(sa);
SpecialNode s = sa.getChildren().get(0);
}
}
abstract class Node<N extends Node<N>> {
private final List<N> children = new ArrayList<N>();
private final N parent;
protected Node(N parent) {
this.parent = parent;
if (parent != null) {
this.parent.getChildren().add(getThis());
}
}
abstract N getThis();
public N getParent() {
return parent;
}
public List<N> getChildren() {
return children;
}
}
class SpecialNode extends Node<SpecialNode> {
public SpecialNode(SpecialNode parent) {
super(parent);
}
SpecialNode getThis() {
return this;
}
}
But from my personal experience, I can say that when you think you need to create such a type, you should thoroughly think about the benefits and drawbacks. The latter mainly refers to reduced readability. When you have the choice between methods like
Node<? extends Node<? extends N, ? extends T>, T> doSomething(
Node<? super Node<? extends N>, ? extends T> p,
Node<? extends Node<? super N>, ? super T> c) { ... }
that are type safe, or methods like
Node doSomething(Node parent, Node child) { ... }
that are not type safe (because of raw types, or simply because the types have not been genericified), then I'd prefer the latter. Code is read by humans.
The difference is that Some<E extends Some>
means two things:
E
is a raw type. In brief, raw types have all generic information stripped from the class, which in turn can create unexpected behaviourE
can be any class of Some
!Conversely, using Some<E extends Some<E>>
means:
E
is typedE
must be the same class as the class in which it's declaredThe raw part an issue, but the bigger implication is the type bounds. This code demonstrates the difference, with the "B" classes using (or trying to use) the "A" classes as the generic type.
// The raw version
interface Some<E extends Some> {
E get();
}
class SomeA implements Some<SomeA> {
public SomeA get() {
return new SomeA();
}
}
// Compiles OK
class SomeB implements Some<SomeA> {
public SomeA get() {
return new SomeA();
}
}
// The typed version
interface SomeT<E extends SomeT<E>> {
E get();
}
class SomeTA implements Some<SomeTA> {
public SomeTA get() {
return new SomeTA();
}
}
// Compile error
class SomeTB implements SomeT<SomeA> {
public SomeA get() {
return new SomeTA();
}
}
Class SomeB
compiles fine - the type of E is bound only to Some
, so any Some
class will do, but class SomeTB
throws a compile error:
type argument SomeTA is not within bounds of type-variable E
The type of E
must be exactly the same as the containing class.
This is important if you're expecting parameters and return types to be the same as the class itself, which is usually what you want with typed classes.
The type being bounded by a raw type is less of an issue, because it's still a Some
, but there may be cases where it makes a difference (I can't think of any just now).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With