2016-12-24 1 views
1

Je devrais mentionner que j'avais compris le problème de programmation spécifique après avoir pensé à poser cette question, donc c'est moins un problème de programmation et plus une question sur les raisons derrière le problème.Comment les classes internes accèdent-elles exactement aux éléments d'une classe externe?

J'avais testé les limites de Java lors de l'utilisation de modificateurs d'accès, et j'ai commencé à appliquer ces tests aux concepts d'héritage de base.

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

Ceci est la version fixe: InnerClass est donnée une méthode display() pour remplacer la méthode SupplementalClass2. Avant, InnerClass était vide et j'ai essayé de définir la méthode d'affichage dans l'instance de classe anonyme, au lieu de la classe elle-même, car je pensais que la classe interne hériterait de la méthode d'affichage abstraite implémentée par SupplementalInterface.

Donc la question que j'ai est de savoir comment les classes imbriquées et internes accèdent aux données dans leurs détenteurs si ce n'est par l'héritage?

Répondre

3

Les instances de classe interne accèdent aux champs et aux méthodes de leur instance de classe externe comme tout objet accède aux champs et aux méthodes d'un autre objet. La seule différence est que, pour pouvoir accéder aux membres privés, le compilateur génère des méthodes de pont synthétique (qui ne sont pas privées), appelées par la classe interne, pour accéder aux membres privés.

Voir par exemple ainsi classe:

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

Si vous compilez et appelez javap -c Outer Outer.Inner, vous obtiendrez la sortie suivante:

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   
} 

Comme vous le voyez, la classe externe a deux autres méthodes statiques: access$000() et access$100(), qui appellent respectivement la méthode privée et retournent la valeur du champ privé. Et la classe interne passe par ces méthodes pour appeler la méthode privée et accéder au champ privé.

La méthode publique et les champs sont accessibles de la manière habituelle, cependant, puisque rien n'empêche un objet d'accéder à un membre public d'un autre objet.

Je vais vous laisser faire la même expérience sur les classes imbriquées et les membres statiques pour voir comment cela fonctionne précisément.

+0

@JB Nizet Wow. Cette réponse était très difficile à comprendre. Je n'ai utilisé aucun des exécutables de Java autres que 'java' et' javac' – i0h3

+0

Je l'ai testé et de ce que vous dites, il semble que le compilateur crée des méthodes d'accès pour envoyer les références de champs privés à la classe intérieure. Il s'agit donc essentiellement de créer des méthodes get que la classe interne appelle automatiquement lorsqu'une requête d'accès à un champ privé est trouvée. – i0h3

+0

Oui. Le compilateur transforme l'accès au champ en un appel à la méthode de pont synthétique qui renvoie le champ. –