John Kugelman
John Kugelman

Reputation: 361575

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

Upvotes: 24

Views: 7048

Answers (7)

Neil Masson
Neil Masson

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

Nikolay
Nikolay

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

Richard
Richard

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

Tarik
Tarik

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: here

Upvotes: 13

lpiepiora
lpiepiora

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

Anesh
Anesh

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

Louis Wasserman
Louis Wasserman

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

Related Questions