2013-03-30 4 views
100

In Java, ich habe gerade herausgefunden, dass der folgende Code legal ist: nur eine Hilfsklasse mit der folgenden SignaturWas macht `someObject.new` in Java?</p> <pre><code>public class receiver extends Thread { /* code_inside */ } </code></pre> <p>ich nie gesehen habe:

KnockKnockServer newServer = new KnockKnockServer();      
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket); 

FYI, Empfänger XYZ.new Notation vor. Wie funktioniert das? Gibt es eine Möglichkeit, das konventioneller zu programmieren?

+7

für Ihre Referenz, [innere Klasse] (http://docs.oracle.com/javase /tutorial/java/javaOO/nested.html). –

+1

Ich hatte auch geglaubt, dass "neu" ein Operator in vielen Sprachen ist. (Ich dachte, Sie könnten auch "neu" in C++ überladen?) Javas innere Klasse ist für mich ein bisschen komisch. –

+5

Es gibt keine dummen Fragen zu StackOverflow! –

Antwort

120

Es ist die Möglichkeit, eine nicht statische innere Klasse von außerhalb des enthaltenden Klassenkörpers zu instanziieren, wie in Oracle docs beschrieben.

Jede innere Klasseninstanz ist einer Instanz ihrer enthaltenden Klasse zugeordnet. Wenn Sie new eine innere Klasse von innerhalb seiner enthaltenden Klasse verwendet es die this Instanz des Behälters durch Standard:

public class Foo { 
    int val; 
    public Foo(int v) { val = v; } 

    class Bar { 
    public void printVal() { 
     // this is the val belonging to our containing instance 
     System.out.println(val); 
    } 
    } 

    public Bar createBar() { 
    return new Bar(); // equivalent of this.new Bar() 
    } 
} 

Aber wenn Sie eine Instanz von Bar außerhalb Foo erstellen oder eine neue Instanz assoziieren mit Wenn eine andere Instanz als this enthalten ist, müssen Sie die Präfixnotation verwenden.

Foo f = new Foo(5); 
Foo.Bar b = f.new Bar(); 
b.printVal(); // prints 5 
+0

Ah Es ist jetzt aufräumen - Vielen Dank Ian! – Coffee

+18

Und wie Sie sehen können, kann dies unglaublich verwirrend sein. Idealerweise sollten innere Klassen Implementierungsdetails der äußeren Klasse sein und nicht der Außenwelt ausgesetzt sein. –

+10

@EricJablow tatsächlich, es ist eine jener Bits der Syntax, die vorhanden sein müssen, um die Spezifikation konsistent zu halten, aber 99.9999% der Zeit, die Sie tatsächlich nicht benötigen, um es zu verwenden. Wenn Außenstehende wirklich Bar-Instanzen erstellen müssen, würde ich eine Factory-Methode auf Foo bereitstellen, anstatt sie 'f.new' zu verwenden. –

4

Denken Sie an new receiver als ein einzelnes Token. Ein bisschen wie ein Funktionsname mit einem Leerzeichen drin.

Natürlich hat die Klasse KnockKnockServer nicht buchstäblich eine Funktion mit dem Namen new receiver, aber ich schätze, die Syntax soll das vorschlagen. Es soll so aussehen, als ob Sie eine Funktion aufrufen, die eine neue Instanz von KnockKnockServer.receiver unter Verwendung einer bestimmten Instanz von KnockKnockServer für alle Zugriffe auf die einschließende Klasse erstellt.

+0

Danke, ja - es hilft mir jetzt, an "neuen Empfänger" als ein Token zu denken! vielen Dank! – Coffee

18

Werfen Sie einen Blick auf dieses Beispiel:

public class Test { 

    class TestInner{ 

    } 

    public TestInner method(){ 
     return new TestInner(); 
    } 

    public static void main(String[] args) throws Exception{ 
     Test t = new Test(); 
     Test.TestInner ti = t.new TestInner(); 
    } 
} 

Mit javap wir für diesen Code anzeigen können

erzeugten Anweisungen

Main-Methode:

public static void main(java.lang.String[]) throws java.lang.Exception; 
    Code: 
    0: new  #2; //class Test 
    3: dup 
    4: invokespecial #3; //Method "<init>":()V 
    7: astore_1 
    8: new  #4; //class Test$TestInner 
    11: dup 
    12: aload_1 
    13: dup 
    14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class; 
    17: pop 
    18: invokespecial #6; //Method Test$TestInner."<init>":(LTest;)V 
    21: astore_2 
    22: return 
} 

Inner Klassenkonstruktors:

Test$TestInner(Test); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield  #1; //Field this$0:LTest; 
    5: aload_0 
    6: invokespecial #2; //Method java/lang/Object."<init>":()V 
    9: return 

} 

Alles ist einfach - beim Aufruf des TestInner-Konstruktors übergibt Java die Test-Instanz als erstes Argument main: 12. Wenn man diesen TestInner nicht betrachtet, sollte er keinen Konstruktor haben. TestInner wiederum speichert nur die Referenz auf das übergeordnete Objekt, Test $ TestInner: 2. Wenn Sie den inneren Klassenkonstruktor von einer Instanzmethode aufrufen, wird der Verweis auf das übergeordnete Objekt automatisch übergeben, sodass Sie ihn nicht angeben müssen. Tatsächlich wird es jedes Mal weitergegeben, aber wenn es von außen aufgerufen wird, sollte es explizit übergeben werden.

t.new TestInner(); - ist nur ein Weg, um die erste verborgene Argument TestInner Konstruktor angeben, kein Typ

Methode() ist gleich:

public TestInner method(){ 
    return this.new TestInner(); 
} 

TestInner ist gleich:

class TestInner{ 
    private Test this$0; 

    TestInner(Test parent){ 
     this.this$0 = parent; 
    } 
} 
+1

Vielen Dank! – Coffee

7

Wenn innere Klassen zu Java in ver. Hinzugefügt wurden 1.1 der Sprache wurden sie ursprünglich als eine Umwandlung in 1.0 kompatiblen Code definiert. Wenn Sie sich ein Beispiel dieser Transformation ansehen, wird es meiner Meinung nach viel klarer machen, wie eine innere Klasse tatsächlich funktioniert.

Betrachten Sie den Code aus Ian Roberts' Antwort:

public class Foo { 
    int val; 
    public Foo(int v) { val = v; } 

    class Bar { 
    public void printVal() { 
     System.out.println(val); 
    } 
    } 

    public Bar createBar() { 
    return new Bar(); 
    } 
} 

Wenn auf 1,0 kompatiblen Code transformiert, dass innere Klasse Bar so etwas wie dieses werden würde:

das Präfix
class Foo$Bar { 
    private Foo this$0; 

    Foo$Bar(Foo outerThis) { 
    this.this$0 = outerThis; 
    } 

    public void printVal() { 
    System.out.println(this$0.val); 
    } 
} 

Die innere Klassennamen mit dem Namen der äußeren Klasse, um es einzigartig zu machen. Ein verstecktes privates this$0 Mitglied wird hinzugefügt, das eine Kopie des äußeren this enthält. Und ein versteckter Konstruktor wird erstellt, um diesen Member zu initialisieren.

Und wenn man sich die Methode createBar aussehen, wäre es in etwa wie folgt umgewandelt werden:

public Foo$Bar createBar() { 
    return new Foo$Bar(this); 
} 

Lassen Sie uns also sehen, was passiert, wenn Sie den folgenden Code ausführen.

Foo f = new Foo(5); 
Foo.Bar b = f.createBar();        
b.printVal(); 

Zuerst instanziieren wir eine Instanz von Foo und intialise die val Element 5 (das heißt f.val = 5).

nächst wir f.createBar() nennen, die eine Instanz von Foo$Bar und initialisiert das this$0 Elemente auf den Wert des in this von createBar (d.h. b.this$0 = f) geleitet instanziiert.

Schließlich nennen wir b.printVal() die b.this$0.val zu drucken versucht, die f.val ist, die ist 5.

nun, dass eine regelmäßige Instanziierung einer inneren Klasse. Lassen Sie uns sehen, was passiert, wenn Bar von außerhalb Foo instanziiert wird.

Foo f = new Foo(5); 
Foo.Bar b = f.new Bar(); 
b.printVal(); 

unsere 1.0-Transformation wieder anwenden, wäre die zweite Linie etwas so worden:

Foo$Bar b = new Foo$Bar(f); 

Dies ist fast identisch mit dem f.createBar() Anruf. Erneut instanziieren wir eine Instanz von Foo$Bar und initialisieren das Element this$0 für f. Also nochmal, b.this$0 = f.

Und wieder, wenn Sie b.printVal() nennen, drucken Sie b.thi$0.val die f.val ist die 5. ist

Der Schlüssel ist daran zu erinnern ist, dass die innere Klasse ein verborgenes Element eine Kopie von this von der äußeren Klasse zu halten. Wenn Sie eine innere Klasse aus der äußeren Klasse instanziieren, wird sie implizit mit dem aktuellen Wert this initialisiert. Wenn Sie die innere Klasse von außerhalb der äußeren Klasse instanziieren, geben Sie über das Präfix new explizit an, welche Instanz der äußeren Klasse verwendet werden soll.

1

Shadowing

Wenn eine Erklärung eines Typs (wie eine Member-Variable oder eines Parameters name) in einem bestimmten Bereich (beispielsweise eine innere Klasse oder ein Definitionsverfahren), um den gleichen Namen wie eine andere Erklärung ist Im einschließenden Bereich schattet die Deklaration die Deklaration des umschließenden Bereichs aus. Sie können auf eine im Schatten stehende Deklaration nicht allein anhand ihres Namens verweisen. Das folgende Beispiel, ShadowTest, zeigt dies:

public class ShadowTest { 

    public int x = 0; 

    class FirstLevel { 

     public int x = 1; 

     void methodInFirstLevel(int x) { 
      System.out.println("x = " + x); 
      System.out.println("this.x = " + this.x); 
      System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 
     } 
    } 

    public static void main(String... args) { 
     ShadowTest st = new ShadowTest(); 
     ShadowTest.FirstLevel fl = st.new FirstLevel(); 
     fl.methodInFirstLevel(23); 
    } 
} 

Die im Anschluss an die Ausgabe dieses Beispiels ist:

x = 23 
this.x = 1 
ShadowTest.this.x = 0 

Dieses Beispiel drei Variablen definiert Namen x: Die Membervariable der Klasse ShadowTest, die Membervariable der inneren Klasse FirstLevel und der Parameter in der Methode methodInFirstLevel. Die Variable x, die als Parameter der Methode methodInFirstLevel definiert ist, schattiert die Variable der inneren Klasse FirstLevel. Wenn Sie die Variable x in der Methode methodInFirstLevel verwenden, verweist sie folglich auf den Methodenparameter. Um beziehen sich auf die Membervariable der inneren Klasse Firstlevel, verwenden Sie das Schlüsselwort this auf den umgebenden Gültigkeitsbereich darstellen:

System.out.println("this.x = " + this.x); 

Siehe Elementvariablen, die größer Bereiche durch den Klassennamen einschließen, der sie angehören. Beispielsweise greift die folgende Anweisung, um die Membervariable der Klasse ShadowTest aus dem Verfahren methodInFirstLevel:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 

Refer to the docs