How exactly do inner classes access elements in an outer class?

1.3k views Asked by At

I should mention that I had figured out the specific programming issue after I had thought to ask this question, so this is less of a programming problem and more a question on the reasons behind the problem.

I had been testing the limitations of Java when using access modifiers, and started applying these tests to basic inheritance concepts.

Here is the code:

package test.Inheritance;

public class SuperClass {

    private static int x = 0;
    protected static int y = 1;

    public static void main(String[] args){
        SupplementalClass2 child = new SupplementalClass2();
        NestedClass local = new NestedClass();
        InnerClass test;

        child.setObject(child.new InnerClass(){
            @Override public void display(){System.out.println("Hey!");}
        });
        test = child.getObject();

        System.out.println(test.equals(child.receiveObject));
        SuperClass.NestedClass.display();
        SuperClass.NestedClass2.display();
        test.display();
        child.display();
        local.message();
    }

    public static class NestedClass {
        public static void display()
        {
            System.out.println("x before first static context change: " + x);
            x = 25;
            System.out.println("x after first static context change: " + x);
        }
        public void message()
        {
            System.out.print("Nested Class Field Access Test: " + "before(" + y + ") | ");
            y = 20;
            System.out.println("after(" + y + ")");
        }
    }

    public static class NestedClass2 {
        public static void display()
        {
            System.out.println("x before second static context change: " + x);
            x = 30;
            System.out.println("x after second static context change: " + x);
        }
    }

    public class InnerClass {
        public void display(){}
    }
}

abstract class SupplementalClass extends SuperClass {
    protected String test = "Parent Class String";
    protected InnerClass receiveObject;
}

interface SupplementalInterface {
    public static final int test = 3;
    public abstract void display();
}

class SupplementalClass2 extends SupplementalClass implements SupplementalInterface {
    public void display()
    {
        System.out.println("Supplemental Interface Field Access Test: " + SupplementalInterface.test);
        System.out.println("Supplemental Parent Field Access Test: " + super.test);
    }
    public void setObject(InnerClass in){
        receiveObject = in;
    }

    public InnerClass getObject()
    {
        return receiveObject;
    }
}

This is the fixed version: InnerClass is given a method display() to override the method in SupplementalClass2.

Before, InnerClass was empty and I tried to set the display method in the Anonymous Class instance, instead of the class itself, because I believed the inner class would inherit the abstract display method implemented through SupplementalInterface.

So the question that I have is how do nested and inner classes access data in their holders if not through inheritance?

1

There are 1 answers

3
JB Nizet On BEST ANSWER

Inner class instances access the fields and methods of their outer class instance like any object accesses fields and methods of another object. The only difference is that, to be able to access private members, the compiler generates synthetic bridge methods (that are not private), called by the inner class, to access the private members.

See for example thus class:

public class Outer {
    private int privateField;
    public int publicField;

    private void privateFoo() {}
    public void publicFoo() {}

    private class Inner {
        void bar() {
            privateFoo();
            publicFoo();
            System.out.println("privateField = " + privateField);
            System.out.println("publicField = " + publicField);
        }
    }
}

If you compile it and call javap -c Outer Outer.Inner, you'll get the following output:

Compiled from "Outer.java"
public class com.foo.Outer {
  public int publicField;

  public com.foo.Outer();
    Code:
       0: aload_0       
       1: invokespecial #3                  // Method java/lang/Object."<init>":()V
       4: return        

  public void publicFoo();
    Code:
       0: return        

  static void access$000(com.foo.Outer);
    Code:
       0: aload_0       
       1: invokespecial #2                  // Method privateFoo:()V
       4: return        

  static int access$100(com.foo.Outer);
    Code:
       0: aload_0       
       1: getfield      #1                  // Field privateField:I
       4: ireturn       
}
Compiled from "Outer.java"
class com.foo.Outer$Inner {
  final com.foo.Outer this$0;

  void bar();
    Code:
       0: aload_0       
       1: getfield      #1                  // Field this$0:Lcom/foo/Outer;
       4: invokestatic  #3                  // Method com/foo/Outer.access$000:(Lcom/foo/Outer;)V
       7: aload_0       
       8: getfield      #1                  // Field this$0:Lcom/foo/Outer;
      11: invokevirtual #4                  // Method com/foo/Outer.publicFoo:()V
      14: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: new           #6                  // class java/lang/StringBuilder
      20: dup           
      21: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      24: ldc           #8                  // String privateField = 
      26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      29: aload_0       
      30: getfield      #1                  // Field this$0:Lcom/foo/Outer;
      33: invokestatic  #10                 // Method com/foo/Outer.access$100:(Lcom/foo/Outer;)I
      36: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      39: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      42: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      45: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      48: new           #6                  // class java/lang/StringBuilder
      51: dup           
      52: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      55: ldc           #14                 // String publicField = 
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: aload_0       
      61: getfield      #1                  // Field this$0:Lcom/foo/Outer;
      64: getfield      #15                 // Field com/foo/Outer.publicField:I
      67: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      70: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      73: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      76: return        
}

As you see, the Outer class has two additional static methods: access$000() and access$100(), which respectively call the private method and return the value of the private field. And the inner class goes through these methods to call the private method and access the private field.

The public method and fields are accessed in the usual way, though, since nothing prevents an object to access a public member of another object.

I'll let you do the same experiment on nested classes and static members to see how it works precisely.