2013-03-30 18 views
100

In Java, ho appena scoperto che il seguente codice è legale:Cosa fa `someObject.new` in Java?

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

Cordiali saluti, il ricevitore è solo una classe di supporto con la seguente firma:

public class receiver extends Thread { /* code_inside */ } 

non ho mai visto la XYZ.new notazione prima. Come funziona? C'è un modo per codificarlo in modo più convenzionale?

+7

Per riferimento, [classe interna] (http://docs.oracle.com/javase /tutorial/java/javaOO/nested.html). –

+1

Inoltre, credevo che 'new' fosse un operatore in molte lingue. (Ho pensato che potresti anche sovraccaricare 'new' in C++?) La classe interna di Java è un po 'strana per me, comunque. –

+5

Non ci sono domande stupide su StackOverflow! –

risposta

120

È il modo per creare un'istanza di una classe interna non statica esterna al corpo della classe contenente, come descritto nello Oracle docs.

Ogni istanza di classe interna è associata a un'istanza della classe contenente. Quando si new una classe interna da all'interno sua classe che lo contiene utilizza l'istanza this del contenitore di default:

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

Ma se si desidera creare un'istanza di bar esterno Foo, o associare una nuova istanza con un'istanza contenente diversa da this quindi è necessario utilizzare la notazione di prefisso.

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

Ah Si sta chiarendo adesso - Grazie mille Ian! – Coffee

+18

E, come puoi vedere, questo può essere incredibilmente confuso. Idealmente, le classi interne dovrebbero essere dettagli di implementazione della classe esterna e non essere esposte al mondo esterno. –

+10

@EricJablow infatti, è uno di quei bit di sintassi che devono esistere per mantenere le specifiche coerenti ma il 99,999% del tempo in cui non è effettivamente necessario utilizzarlo. Se gli outsider hanno davvero bisogno di creare istanze di Bar, allora fornirei un metodo factory su Foo piuttosto che usare 'f.new'. –

4

Pensate new receiver come un unico token. Un po 'come un nome di funzione con uno spazio al suo interno.

Naturalmente, la classe KnockKnockServer non ha letteralmente una funzione denominata new receiver, ma suppongo che la sintassi abbia lo scopo di suggerirlo. Sembra che tu stia chiamando una funzione che crea una nuova istanza di KnockKnockServer.receiver utilizzando una particolare istanza di KnockKnockServer per qualsiasi accesso alla classe che li include.

+0

Grazie, sì - mi aiuta ora a pensare a 'nuovo ricevitore' come un token! grazie mille! – Coffee

18

Date un'occhiata a questo esempio:

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

Usando javap possiamo vedere istruzioni generate per questo codice

metodo Main:

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 
} 

costruttore della classe interna:

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 

} 

Tutto è semplice: quando si richiama il costruttore TestInner, java passa l'istanza di test come primo argomento principale: 12. Non considerando che TestInner dovrebbe avere un costruttore senza argomenti. TestInner, a sua volta, salva il riferimento all'oggetto principale, Test $ TestInner: 2. Quando si richiama il costruttore della classe interna da un metodo di istanza, il riferimento all'oggetto padre viene passato automaticamente, quindi non è necessario specificarlo. In realtà il suo passa ogni volta, ma quando si invoca dall'esterno dovrebbe essere passato esplicitamente.

t.new TestInner(); - è solo un modo per specificare il primo argomento nascosto a TestInner costruttore, non un tipo

metodo() è pari a:

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

TestInner è pari a:

class TestInner{ 
    private Test this$0; 

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

Grazie mille! – Coffee

7

Quando le classi interne sono state aggiunte a Java in ver sione 1.1 della lingua sono stati originariamente definiti come una trasformazione in codice compatibile 1.0. Se guardi un esempio di questa trasformazione, penso che renderà molto più chiaro come funzioni effettivamente una classe interiore.

Si consideri il codice dalla risposta Ian Roberts':

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

Quando trasformato in 1.0 codice compatibile, che classe interna Bar sarebbe diventato qualcosa di simile:

class Foo$Bar { 
    private Foo this$0; 

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

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

Il nome della classe interna è prefissato con il nome della classe esterna in modo da renderlo unico. Viene aggiunto un membro privato nascosto this$0 che contiene una copia esterna this. E viene creato un costruttore nascosto per inizializzare quel membro.

E se si guarda il metodo createBar, sarebbe essere trasformato in qualcosa di simile:

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

Quindi vediamo cosa succede quando si esegue il seguente codice.

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

Innanzitutto istanziare Foo e intialise l'organo val a 5 (cioè f.val = 5).

Successivo chiamiamo f.createBar(), che istanzia un'istanza di Foo$Bar e inizializza l'elemento this$0 al valore di this passato dalla createBar (cioè b.this$0 = f).

Infine chiamiamo b.printVal() che cerca di stampare b.this$0.val che è f.val quali è 5.

Ora che era un'esemplificazione regolare di una classe interna. Diamo un'occhiata a cosa succede quando si instilla l'Bar dall'esterno dello Foo.

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

Applicando nuovamente il nostro 1.0 trasformazione, che la seconda linea sarebbe diventato qualcosa di simile:

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

Questo è quasi identica alla chiamata f.createBar(). Di nuovo stiamo istanziando un'istanza di Foo$Bar e inizializzando il membro this$0 a f. Quindi, di nuovo, b.this$0 = f.

E di nuovo quando si chiama b.printVal(), si sta stampando b.thi$0.val che è f.val quali è 5.

La cosa fondamentale da ricordare è che la classe interna ha un membro nascosto in possesso di una copia del this dalla classe esterna. Quando si crea un'istanza di una classe interna all'interno della classe esterna, viene inizializzata implicitamente con il valore corrente di this. Quando istanziate la classe interna dall'esterno della classe esterna, specificate esplicitamente quale istanza della classe esterna utilizzare, tramite il prefisso sulla parola chiave new.

1

shadowing

Se una dichiarazione di un tipo (ad esempio una variabile membro o un nome di parametro) in un particolare campo di applicazione (ad esempio una classe interna o una definizione di metodo) ha lo stesso nome di un'altra dichiarazione Nell'ambito allegato, la dichiarazione ombreggia la dichiarazione dell'ambito che lo racchiude. Non è possibile fare riferimento a una dichiarazione ombreggiata solo dal suo nome. Nel seguente esempio, ShadowTest, illustrato questo:

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

Il seguente è l'output di questo esempio:

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

Questo esempio definisce tre variabili denominate x: la variabile membro della ShadowTest classe, la variabile membro della classe interna FirstLevel e il parametro nel metodo methodInFirstLevel. La variabile x definita come parametro del metodo methodInFirstLevel ombreggia la variabile della classe interna FirstLevel. Di conseguenza, quando si utilizza la variabile x nel metodo methodInFirstLevel, fa riferimento al parametro method. Per fare riferimento alla variabile membro della classe interna FirstLevel, utilizzare la parola chiave this per rappresentare l'ambito di inclusione:

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

riferimento a variabili utente che racchiudono ambiti grandi dal nome della classe di appartenenza. Ad esempio, l'istruzione seguente accede alla variabile membro della classe di ShadowTest dal metodo methodInFirstLevel:

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

Refer to the docs