【问题标题】:How exactly do inner classes access elements in an outer class?内部类究竟如何访问外部类中的元素?
【发布时间】:2016-12-24 16:02:42
【问题描述】:

我应该提一下,在我想问这个问题之后,我已经弄清楚了具体的编程问题,所以这不是一个编程问题,而是一个关于问题背后原因的问题。

我一直在测试 Java 在使用访问修饰符时的局限性,并开始将这些测试应用于基本的继承概念。

代码如下:

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;
    }
}

这是固定版本:InnerClass 被赋予一个方法 display() 来覆盖 SupplementalClass2 中的方法。

之前InnerClass是空的,我试图在匿名类实例中设置显示方法,而不是类本身,因为我相信内部类会继承通过SupplementalInterface实现的抽象显示方法。

所以我的问题是,如果不通过继承,嵌套类和内部类如何访问其持有者中的数据?

【问题讨论】:

    标签: java inheritance anonymous anonymous-class


    【解决方案1】:

    内部类实例访问其外部类实例的字段和方法,就像任何对象访问另一个对象的字段和方法一样。唯一的区别是,为了能够访问私有成员,编译器生成由内部类调用的合成桥接方法(不是私有的)来访问私有成员。

    例如查看类:

    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);
            }
        }
    }
    

    如果你编译它并调用javap -c Outer Outer.Inner,你会得到如下输出:

    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        
    }
    

    如您所见,Outer 类有两个额外的静态方法:access$000()access$100(),它们分别调用私有方法并返回私有字段的值。而内部类通过这些方法调用私有方法,访问私有字段。

    但是,公共方法和字段以通常的方式访问,因为没有什么可以阻止对象访问另一个对象的公共成员。

    我会让你对嵌套类和静态成员做同样的实验,看看它是如何精确工作的。

    【讨论】:

    • @JB Nizet 哇。这个答案让我很难理解。除了javajavac,我没有使用任何Java 提示可执行文件
    • 我已经对此进行了测试,根据您的说法,编译器似乎正在创建访问方法以将私有字段引用发送到内部类。所以它基本上是创建 get 方法,只要找到访问私有字段的请求,内部类就会自动调用这些方法。
    • 是的。编译器将字段访问转换为对返回字段的合成桥方法的调用。
    猜你喜欢
    • 2011-01-02
    • 2021-11-27
    • 1970-01-01
    • 2011-02-13
    • 2018-04-25
    • 1970-01-01
    • 2017-11-29
    • 2021-03-13
    相关资源
    最近更新 更多