Search code examples
javaprivate

Can't access private variable from own class via subclass instance


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.)


Solution

  • 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