2

How can you access a protected field in a base class's base class that is hidden by a field in a base class?

An example:

package foo;

public class Foo {
    protected int x;

    int getFooX() { return x; }
}
package bar;

public class Bar extends foo.Foo {
    protected int x;

    // Can access foo.Foo#x through super.x
}

The Foo class's x field is shadowed by Bar's field of the same name, but can be accessed by reflection:

package baz;

public class Baz extends bar.Bar {
    {
        // Want getFooX() to return 2
        // ((foo.Foo) this).x = 2;  // Fails due to access error; Makes sense
        // super.x = 2;  // Changes bar.Bar#x
        // super.super.x = 2;  // Syntax error
        // foo.Foo.this.x = 2;  // Syntax error
        try {
            Field Foo_x = foo.Foo.class.getDeclaredField("x");
            Foo_x.setAccessible(true);
            Foo_x.setInt(this, 2);
        } catch (ReflectiveOperationException e) { e.printStackTrace(); }
        // Doesn't compile error if field changes name
    }
}

Is there a way to do this without reflection, and without making changes to the superclasses?

kaya3
  • 47,440
  • 4
  • 68
  • 97
Artyer
  • 31,034
  • 3
  • 47
  • 75

2 Answers2

1

Doesn't work?

public static void main(String... args) {

        Baz b = new Baz();

        try {
            Field Foo_x = Foo.class.getDeclaredField("x");
            Foo_x.setAccessible(true);
            Foo_x.setInt(b, 2);
            System.out.println(b. getFooX());
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
Ihar Sadounikau
  • 741
  • 6
  • 20
1

If the classes are in the same package, then you can cast this to the type you want to access the field of. Field names are resolved statically, based on the compile-time type of the expression on the left of the ., so the code below accesses A's field because the expression ((A) this) has compile-time type A.

class A {
    protected int x = 1;
}

class B extends A {
    protected int x = 2;
}

class C extends B {
    int getAx() {
        return ((A) this).x;
    }

    void setAx(int x) {
        ((A) this).x = x;
    }
}

Note that this works only in the case where the classes are in the same package. In your example the classes are in different packages, so because the field is protected you'll get a compilation error.

In this case, it is impossible to access A's field without reflection, because of this part of the Java Language Specification (§6.6.2, emphasis mine):

Let C be the class in which a protected member is declared. Access is permitted only within the body of a subclass S of C.

In addition, if Id denotes an instance field or instance method, then:

  • ...
  • If the access is by a field access expression E.Id, or a method invocation expression E.Id(...), or a method reference expression E :: Id, where E is a Primary expression (§15.8), then the access is permitted if and only if the type of E is S or a subclass of S.

Here the class you're writing in is C, so the protected field of the superclass A is only accessible by an expression like (expr).x if the expression's type is C or a subclass of C. But in that case, the .x will always resolve to B's field rather than A's field.

Logically, it follows that accessing A's field is not permitted by any field access expression in C.

We can also rule out the other kinds of expressions which can access a field: a simple name x doesn't work, and a super expression cannot be chained like super.super.x (see this answer).

Community
  • 1
  • 1
kaya3
  • 47,440
  • 4
  • 68
  • 97
  • 1
    Try putting `A`, `B`, and `C` in different packages (like the question has it), and see if it still works. It doesn't. – Andreas Nov 23 '19 at 23:45
  • Oh, missed that. But this answer does work when they're in the same package. The question isn't worded specifically about the case where they're in different packages, although the example given does have them in different packages. – kaya3 Nov 23 '19 at 23:54
  • I've edited the answer to cover the case where they are in different packages. It turns out to be impossible without reflection, per the JLS. – kaya3 Nov 24 '19 at 00:21
  • *"it follows that accessing A's field is not permitted by **any** field access expression in C"*. Not true. C could access A's field if B hadn't hidden it. – Andreas Nov 24 '19 at 00:24
  • ...the entire premise of the question is that B **has** hidden it, and that you can't change B to make it not hidden. – kaya3 Nov 24 '19 at 00:25
  • I know, but the sentence was too generic, and easy to misunderstand, hence I added clarifying *comment*. – Andreas Nov 24 '19 at 00:29
  • It's not a generic statement; it is specifically about the classes `A`, `B` and `C` defined earlier within the answer. It refers to those classes by their names. – kaya3 Nov 24 '19 at 00:36