Reputation: 361575
class A {
private int foo;
void bar(B b) { b.foo = 42; }
}
class B extends A { }
This fails to compile with the error:
A.java:3: error: foo has private access in A
void bar(B b) { b.foo = 42; }
^
1 error
Adding a cast to the base class makes it work.
void bar(B b) { ((A) b).foo = 42; }
Can someone point me to an explanation about why the first snippet is illegal? What's the reason it's prohibited? Here's what the JLS says:
Otherwise, the member or constructor is declared
private
, and access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.
As best I can tell, my code meets this wording. So is this a bug with the Java compiler, or is my interpretation of the JLS incorrect?
(Note: I'm not looking for workarounds, like making the variable protected
. I know how to work around this.)
Upvotes: 24
Views: 7048
Reputation: 2689
It looks to me that the spec is inconsistent. As John states, the body of the spec states
Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.
and there is no mention of subclasses. So class A should compile correctly. However example 6.6-5 states
A private class member or constructor is accessible only within the body of the top level class (§7.6) that encloses the declaration of the member or constructor. It is not inherited by subclasses.
This second statement is both weaker (no only if), but brings subclasses to the table. According to this A should not compile.
Upvotes: 1
Reputation: 1949
Error message "has a private access in A" is a java bug for a very very long time.
JDK 1.1:
JDK-4096353 : JLS 6.6.1: When subclass references are used to access privates of superclasses
contains code snippet exactly conforms the question one
class X{
private static int i = 10;
void f() {
Y oy = new Y();
oy.i = 5; // Is this an error? Is i accessable through a reference to Y?
}
}
class Y extends X {}
They tried to fix it and it leads to
JDK-4122297 : javac's error messages are not appropriate for a private field.
======TP1======
1 class C extends S {
2 void f(){
3 java.lang.System.out.println("foo");
4 }
5 }
6
7 class S {
8 private int java;
9 }
======
% javac C.java
C.java:3: Variable java in class S not accessible from class C.
java.lang.System.out.println("foo");
^
C.java:3: Attempt to reference field lang in a int.
java.lang.System.out.println("foo");
^
2 errors
======
But by specification java
isn't inherited in C and this program should compile.
It fixed in 1.2, but appears in 1.3 again
JDK-4240480 : name00705.html: JLS6.3 private members should not be inherited from superclasses
JDK-4249653 : new javac assumes that private fields are inherited by a subclass
And when generics come
JDK-6246814 : Private member of type variable wrongly accesible
JDK-7022052 : Invalid compiler error on private method and generics
However, by the JLS this member simply doesn't exist in the inherited type.
JLS 8.2. Class Members
Members of a class that are declared private are not inherited by subclasses of that class.
So b.foo
is illegal because class B
has no field named foo
.
It is no restriction, it is an absent field in B
.
Java has strong typing and we cannot access fields that do not exist in B
even if they exist in superclass A
.
Cast (A) b
is legal because B
is a subclass of A
.
A
has a field named foo
and we can access this private field because b(B b)
is a function in class A
even if b != this
due to
JLS 6.6.1. Determining Accessibility
Otherwise, if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.
Also if we write
class A {
private int foo;
void baz(A b) { b.foo = 42; }
}
class B extends A { }
class T {
void x() {
B b = new B();
b.baz(b);
}
}
It will compile because Java infer type arguments for polymorphic calls.
JLS 15.12.2.7. Inferring Type Arguments Based on Actual Arguments:
A supertype constraint T :> X implies that the solution is one of supertypes of X. Given several such constraints on T, we can intersect the sets of supertypes implied by each of the constraints, since the type parameter must be a member of all of them. We can then choose the most specific type that is in the intersection
Upvotes: 16
Reputation: 1130
Isn't this what the default access modifier is for?
Try this :
public class blah{
static class A {
int foo;
void bar(B b) {b.foo=42;}
}
static class B extends A {
}
}
You cannot access the private member directly from an ancestor, that is what private means. Now why does it work when you cast? And does that mean the documentation is incorrect?
I mentioned to a colleague that the java documentation may be erroneous and he points out that you are actually setting the value of foo from inside class A. So everything is correct. You cannot (because it is private) access foo from an descendant, so you must cast. And you cannot do that outside of the body of A.
I believe this is the correct answer.
Upvotes: -1
Reputation: 5021
You can't say b.foo
because foo
is private and thus will not be inherited, as a result the B
class can't see the foo
variable and is not aware if a variable named foo
even exist - unless its marked protected (as you said) or default (as they are in the same package i guess) or public.
If you want to use foo
without using an explicit cast like your second example you must use this.foo
or just foo
which has an implicit this
. As Javadocs specified, the this
keyword main reason is to prevent that:
The most common reason for using the this keyword is because a field is shadowed by a method or constructor parameter.
When you used ((A) b)
you are casting the reference type and the compiler will see it like if you are using an A
reference variable type, in other words something like A a
, and a.foo
is completely legal.
An illustrated summary of visibility and access to superclass's private instance variables:
Upvotes: 13
Reputation: 13749
The specification for field access expressions, chapter 15.11 says:
If the identifier does not name an accessible member field in type T, then the field access is undefined and a compile-time error occurs.
From the perspective of super class, looking at the types, I'd argue that the member is not accessible, hence the error.
I think the case you're presenting is closer to accessing a member as a field, which is shown in example 15.11-1-1.
class S { int x = 0; }
class T extends S { int x = 1; }
class Test1 {
public static void main(String[] args) {
T t = new T();
System.out.println("t.x=" + t.x + when("t", t));
S s = new S();
System.out.println("s.x=" + s.x + when("s", s));
s = t;
System.out.println("s.x=" + s.x + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
Just to answer your question:
Kindly explain what sort of bad code the restriction is protecting against.
Please consider following snippet.
public class X {
private int a;
public void bar(Z z) {
z.a // not visible, but if was, what 'a'
// would you actually access at this point 'X' or 'Z'
}
}
public class Z extends X {
private int a;
}
Upvotes: 2
Reputation: 312
We can't inherit the private
fields or methods. So in your code Class B
is completely unaware of variable foo
even though you're accessing from its own class A
.
Upvotes: -1
Reputation: 198023
Java is finicky about accessing private variables via a reference type that shouldn't have access to that variable. You should be able to do this legally by writing ((A) b).foo = 42
.
Upvotes: 0