Reputation: 1002
Combining private field access with the CRTP in Java seems to tickle a weird edge case in the visibility rules:
public abstract class Test<O extends Test<O>> implements Cloneable {
private int x = 0;
@SuppressWarnings("unchecked")
@Override
protected final O clone() {
try {
return (O) super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError(ex);
}
}
public final int getX() {
return x;
}
public final O withX(int x) {
O created = clone();
created.x = x; // Compiler error: The field Test<O>.x is not visible
return created;
}
}
Simply changing the withX()
method to this...
public final O withX(int x) {
O created = clone();
Test<O> temp = created;
temp.x = x;
return created;
}
...makes the code compile. I tested this in Oracle's javac
and in Eclipse's compiler. What gives?
Upvotes: 4
Views: 316
Reputation: 28539
This isnt actually an issue with generics. The JLS inheritance rules keep private fields from being visible in sub classes. Because X is private, it is not a member of type O
, even though it is a member of type Test<O>
and O
is a subtype of Test<O>
. If you had used code like:
public final O withX(int x) {
Test<O> created = clone();
created.x = x;
return (O) created;
}
It would work. This is an instance where LSP is not upheld by Java, but it is only a local problem with the type system, since private fields are only available to objects of the same type. If it didn't work this way than private fields wouldn't really be private. I dont think having a special exception to the rules for recursive templates is a good idea.
Note, this is never actually a limitation of what you can do. You can always cast the subtype up to the the supertype when you want to make the change, just like you do in your alternative code.
Upvotes: 8