I had a problem I could actually solve myself, but I still don't understand why my original code doesn't work, or if there is a more elegant solution than the one I found. I'm presenting a simplified version of my code here.
Consider the following abstract superclass X:
public abstract class X{
private int i;
public void m1(X x){
x.i = 1;
m2(x);
}
public abstract void m2(X x);
}
When m1 is called, we manipulate a private field of X of the instance passed, and then we call m2 with that instance.
I have several subclasses of X, they are all alike in the sense that they also declare private members which they manipulate. In order to achieve that, they always need to make a cast at the beginning of m2. Here is one of them:
public class Y extends X{
private int j;
public void m2(X x){
Y y = (Y) x;
y.j = 0;
}
}
But - I can guarantee that every call of m1 of an instance of a subclass of X will always have a parameter that is of the same type, e.g. when I have an instance of Y, the parameter of the method m1 will always be another instance of Y.
Because of that guarantee, I wanted to make the cast unnecessary, by introducing generics. This is how I want my subclasses to look like:
public class Y extends X<Y>{
private int j;
public void m2(Y y){
y.j = 0;
}
}
How does the superclass X have to look like now? My first try was that:
public abstract class X<T extends X<T>>{
private int i;
public void m1(T x){
x.i = 1;
m2(x);
}
public abstract void m2(T x);
}
But - that doesn't work, when I compile this, I get the following error:
X.java:6: error: i has private access in X
That's usually what you get you try to access the private members of another class. Obviously, Java doesn't recognise that T is always an instance of X as well, although I used "T extends X" in the declaration.
I fixed X like this:
public abstract class X<T extends X<T>>{
private int i;
public void m1(T x){
X<?> y = x;
y.i = 1;
m2(x);
}
public abstract void m2(T x);
}
At least I'm not using casts any more - but why is this extra assignment necessary? And why didn't the original code work? Also, I found it strange I had to use X<?>
and could not use X<T>
.
I believe we can reduce your question down to: Why does the following example fail to compile?
public class Foo {
private final String bar = "bar";
public <T extends Foo> void printFoo(T baz) {
System.out.println(baz.bar); //bar is not visible
}
}
It's a great question, and it sure took me by surprise. But we can actually remove Generics from the equation by noting that this doesn't work either:
public class Foo {
private final String bar = "bar";
public void printFoo(SubFoo baz) {
System.out.println(baz.bar); //bar is not visible
}
}
class SubFoo extends Foo {
}
In other words, the issue is that you're dealing with a subclass of Foo
, not Foo
itself. In the case of T
, we don't know which subclass, but we know it is a subclass, or Foo
.
As you've already figured out, the solution (surprisingly, at least to me) is to upcast:
System.out.println(((Foo)baz).bar);
Or for the Generic case:
public <T extends Foo> void printFoo(T baz) {
System.out.println(((Foo)baz).bar);
}
Is the cast so bad? Not really. It's certainly as good or better than avoiding the cast with an intermediate variable. As with any upcast, I would assume it would be removed by the compiler. It exists only as a hint to the compiler. We certainly don't have to worry about the safety of the cast, because the erasure of T
is already Foo
.
I can only assume this restriction is required so as to be clear about the access...since SubFoo
could redeclare bar
itself, it could become ambiguous which bar
is being referred to, and so the cast is necessary. This is demonstrated in this complicated example:
public class Foo {
private final String bar = "hello";
static class SubFoo extends Foo {
private final String bar = "world";
}
public <T extends SubFoo> void printFoo(T baz) {
// System.out.println(baz.bar); // doesn't compile
System.out.println(((Foo)baz).bar); //hello
System.out.println(((SubFoo)baz).bar); //world
}
public static void main(String[] args) {
new Foo().printFoo(new SubFoo()); //prints "hello\nworld"
}
}
In this regard, it serves more as a qualifier than as a cast.
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