Cosa fa `someObject.new` in Java?


100

In Java, ho appena scoperto che il seguente codice è legale:

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

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

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

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


7
Per tuo riferimento, classe interiore .
Alvin Wong

1
Inoltre, avevo creduto che newfosse un operatore in molte lingue. (Pensavo potessi anche sovraccaricare newin C ++?) La classe interna di Java è un po 'strana per me, però.
Alvin Wong

5
Non ci sono domande stupide su StackOverflow!
Isaac Rabinovitch

2
@IsaacRabinovitch - Non ci sono domande stupide. Tuttavia, ce ne sono molti sciocchi. (E anche una risposta sciocca occasionale.)
Hot Licks

2
@HotLicks E qual è la tua definizione di domanda stupida? Uno che sei troppo intelligente per aver bisogno di chiederlo, suppongo. È un bene che tu abbia così tanta autostima.
Isaac Rabinovitch

Risposte:


120

È il modo per creare un'istanza di una classe interna non statica dall'esterno del corpo della classe contenitore, come descritto nei documenti Oracle .

Ogni istanza della classe interna è associata a un'istanza della sua classe contenente. Quando newuna classe interna dall'interno della sua classe contenitore utilizza l' thisistanza del contenitore per impostazione predefinita:

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 vuoi creare un'istanza di Bar al di fuori di Foo, o associare una nuova istanza a un'istanza contenitore diversa da quella, thisdevi usare la notazione del prefisso.

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

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.
Eric Jablow

10
@EricJablow in effetti, è uno di quei bit di sintassi che devono esistere per mantenere le specifiche coerenti, ma per il 99,9999% delle volte non è effettivamente necessario utilizzarlo. Se gli estranei hanno davvero bisogno di creare istanze di Bar, fornirei un metodo di fabbrica su Foo piuttosto che usarli f.new.
Ian Roberts

Correggimi se sbaglio, ma se il publiclivello di accesso su KnockKnockServer.receiverfosse impostato privatesarebbe impossibile creare un'istanza in questo modo, giusto? Per estendere il commento di @EricJablow, le classi interne dovrebbero generalmente sempre essere impostate su un privatelivello di accesso.
Andrew Bissell

1
@AndrewBissell sì, ma sarebbe anche impossibile fare riferimento alla receiverclasse dall'esterno. Se lo stessi progettando, probabilmente avrei la classe pubblica ma il suo costruttore protetto o privato del pacchetto e avrei un metodo KnockKnockServerper creare istanze del ricevitore.
Ian Roberts

1
@emory Non sto dicendo questo, so che ci possono essere ragioni perfettamente valide per voler rendere pubblica una classe interna e restituire istanze della classe interna dai metodi di quella esterna, ma tenderei a progettare il mio codice in modo tale che " outsider "non hanno bisogno di costruire istanze della classe interna utilizzando direttamente x.new.
Ian Roberts

18

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

Utilizzando javap possiamo visualizzare le istruzioni generate per questo codice

Metodo principale:

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 di classi interne:

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 main: 12 . Non guardare quel TestInner dovrebbe avere un costruttore senza argomenti. TestInner a sua volta salva solo il riferimento all'oggetto genitore, Test $ TestInner: 2 . Quando si richiama il costruttore della classe interna da un metodo di istanza, il riferimento all'oggetto genitore viene passato automaticamente, quindi non è necessario specificarlo. In realtà passa ogni volta, ma quando si richiama dall'esterno dovrebbe essere passato esplicitamente.

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

method () è uguale a:

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

TestInner è uguale a:

class TestInner{
    private Test this$0;

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

7

Quando le classi interne sono state aggiunte a Java nella versione 1.1 del linguaggio, sono state originariamente definite come una trasformazione in codice compatibile 1.0. Se guardi un esempio di questa trasformazione, penso che renderà molto più chiaro come funziona effettivamente una classe interna.

Considera il codice dalla risposta di 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 trasformata in codice compatibile 1.0, quella classe interna Bardiventerebbe qualcosa del genere:

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 è preceduto dal nome della classe esterna in modo da renderlo univoco. Viene this$0aggiunto un membro privato nascosto che contiene una copia dell'esterno this. E un costruttore nascosto viene creato per inizializzare quel membro.

E se guardi il createBarmetodo, si trasformerebbe in qualcosa del genere:

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

Quindi vediamo cosa succede quando esegui il codice seguente.

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

Per prima cosa istanziamo un'istanza di Fooe inizializziamo il valmembro su 5 (cioè f.val = 5).

Successivamente chiamiamo f.createBar(), che istanzia un'istanza di Foo$Bare inizializza il this$0membro al valore di thispassato da createBar(ie b.this$0 = f).

Infine chiamiamo b.printVal()quale prova a stampare b.this$0.valquale è f.valquale è 5.

Questa era un'istanza regolare di una classe interna. Diamo un'occhiata a cosa succede quando si crea un'istanza Bardall'esterno Foo.

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

Applicando di nuovo la nostra trasformazione 1.0, quella seconda riga diventerebbe qualcosa del genere:

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

Questo è quasi identico alla f.createBar()chiamata. Anche in questo caso stiamo creando un'istanza di Foo$Bare inizializzando il this$0membro su f. Quindi, di nuovo, b.this$0 = f.

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

La cosa fondamentale da ricordare è che la classe interna ha un membro nascosto che contiene una copia di thisdalla classe esterna. Quando si istanzia una classe interna dall'interno della classe esterna, questa viene inizializzata implicitamente con il valore corrente di this. Quando si istanzia la classe interna dall'esterno della classe esterna, si specifica esplicitamente quale istanza della classe esterna utilizzare, tramite il prefisso sulla newparola chiave.


4

Pensa new receivera un singolo token. Un po 'come il nome di una funzione con uno spazio.

Ovviamente, la classe KnockKnockServernon ha letteralmente una funzione denominata new receiver, ma immagino che la sintassi abbia lo scopo di suggerirlo. È pensato per sembrare che tu stia chiamando una funzione che crea una nuova istanza di KnockKnockServer.receiverutilizzo di una particolare istanza di KnockKnockServerper qualsiasi accesso alla classe che lo racchiude.


Grazie, sì - ora mi aiuta a pensare a new receivercome un segno! grazie mille!
Caffè

1

Shadowing

Se una dichiarazione di un tipo (come una variabile membro o un nome di parametro) in un determinato ambito (come una classe interna o una definizione di metodo) ha lo stesso nome di un'altra dichiarazione nell'ambito di inclusione, la dichiarazione ombreggia la dichiarazione del campo di applicazione allegato. Non è possibile fare riferimento a una dichiarazione oscurata solo con il suo nome. Il seguente esempio, ShadowTest, lo dimostra:

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 classe ShadowTest, 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, si fa riferimento al parametro del metodo. 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);

Fare riferimento alle variabili membro che racchiudono ambiti più grandi in base al nome della classe a cui appartengono. Ad esempio, la seguente istruzione accede alla variabile membro della classe ShadowTest dal metodo methodInFirstLevel:

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

Fare riferimento alla documentazione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.