Class hierarchy problem with java generics
I have run into a problem with class hierarchy in a generic function.
I need to enforce that, with the two classes T
and U
specified in the function, one is the child of the other.
I have found to much surprise that the construct <T extends U>
does not at all enforce a parent child relation of U
and T
.
Instead it also allows T
and U
to be the same type.
This creates a problem because it looks like in a case where U extends T
Java will not indicate the error but instead it will happily deduce both objects to be of type T
(which is true no doubt) and then compile and run the code without any complaint.
Here is an example that illustrates the issue:
public class MyClass {
public static void main(String args[]) {
Foo foo = new Foo();
Bar bar = new Bar();
// This code is written as intended
System.out.println( justTesting(foo, bar) );
// This line shouldn't even compile
System.out.println( justTesting(bar, foo) );
}
static interface IF {
String get();
}
static class Foo implements IF {
public String get(){return "foo";}
}
static class Bar extends Foo {
public String get(){return "bar";}
}
static <G extends IF , H extends G> String justTesting(G g, H h) {
if (h instanceof G)
return h.get() + " (" + h.getClass() + ") is instance of " + g.getClass() + ". ";
else
return "it is the other way round!";
}
}
And this is the output:
bar (class MyClass$Bar) is instance of class MyClass$Foo.
foo (class MyClass$Foo) is instance of class MyClass$Bar.
I need to ensure that the parent child relation of the generic classes is observed by the compiler. Is there any way to do that?
Solution 1:
It compiles, because IF
is a "upper bound" for both H
and G
.
Means: Generics are not that "dynamic" as we think, and we could also write:
static <G extends IF, H extends IF> ... // just pointing out that G *could* differ from H
Disregarding null check, is this, what you want:
static <G extends IF, H extends G> String justTesting(G g, H h) {
if (g.getClass().isAssignableFrom(h.getClass())) {
return h.get() + " (" + h.getClass() + ") is instance of " + g.getClass() + ". ";
} else {
return "it is the other way round!";
}
}
?
Class.isAssignableFrom()
Prints:
bar (class com.example.test.generics.Main$Bar) is instance of class com.example.test.generics.Main$Foo.
it is the other way round!
And watch out with "anonymous classes", e.g.:
System.out.println(
justTesting(
new IF() {
@Override
public String get() {
return "haha";
}
}, foo)
);
System.out.println(
justTesting(
foo, new IF() {
@Override
public String get() {
return "haha";
}
}
)
);
print both "it is the other way round!", so the decision here is not that "binary".