2016-12-24 20 views
1

Я должен упомянуть, что я выяснил конкретную проблему программирования после того, как я подумал задать этот вопрос, так что это меньше проблема программирования и больше вопрос о причинах проблемы.Как именно внутренние классы получают доступ к внешним элементам?

Я тестировал ограничения 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.

Итак, вопрос, который у меня есть, заключается в том, как вложенные и внутренние классы получают доступ к данным у своих владельцев, если не через наследование?

ответ

3

Внутренние экземпляры класса получают доступ к полям и методам их экземпляра внешнего класса, как и любой объект, который обращается к полям и методам другого объекта. Единственное отличие состоит в том, что для доступа к частным членам компилятор генерирует синтетические модные методы (которые не являются частными), вызываемые внутренним классом, для доступа к закрытым членам.

Смотрите, например, таким образом, класс:

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   
} 

Как вы видите, внешний класс имеет два дополнительных статические методы: access$000() и access$100(), которые соответственно вызывают частный метод и возвращают значение частного поля. И внутренний класс использует эти методы для вызова частного метода и доступа к частному полю.

Доступ к общедоступному методу и полям осуществляется обычным способом, поскольку ничто не мешает объекту получить доступ к публичному члену другого объекта.

Я позволю вам сделать тот же эксперимент по вложенным классам и статическим членам, чтобы увидеть, как он работает точно.

+0

@JB Nizet Wow. Этот ответ был очень тяжелым, чтобы склонить голову. Я не использовал ни один из исполняемых файлов для Java, кроме 'java' и' javac' – i0h3

+0

Я тестировал это и из того, что вы говорите, похоже, что компилятор создает методы доступа для отправки частных полей к внутреннему классу. Таким образом, это в основном создает методы получения, которые внутренний класс автоматически вызывает всякий раз, когда запрос на доступ к закрытому полю найден. – i0h3

+0

Да. Компилятор преобразует доступ к полю в вызов метода синтетического моста, который возвращает поле. –