2013-08-21 19 views
22

Che cosa rende possibile creare un'istanza di classe all'interno della classe stessa?Come funziona la creazione di un'istanza di classe all'interno della classe stessa?

public class My_Class 
{ 

     My_Class new_class= new My_Class(); 
} 

So che è possibile e hanno fatto io, ma non posso ancora farmi credere che questo non è qualcosa di simile "che è stato il primo -? Pollo o uovo" tipo di problema Potrei essere felice di ricevere una risposta che chiarirà questo dalla prospettiva della programmazione e dalla prospettiva JVM/compilatore. Penso che comprendere questo mi aiuterà a chiarire alcuni concetti di collo di bottiglia molto importanti della programmazione OO.

Ho ricevuto alcune risposte ma nessuna è chiara al livello che mi aspettavo.

+2

Finché non si sta creando un 'MyClass 'nel costruttore' MyClass' '(Yay per la ricorsione infinita) non ci sono assolutamente problemi nel farlo. Anche il pattern di design composito è basato su questo. Non capisco davvero di cosa si tratta. Inoltre, 'public void class' non verrà mai compilato. –

+0

So che può essere fatto ma la mia domanda è: non è come se si stesse usando la funzione senza creare la funzione in primo luogo. Come puoi spiegare questo a qualcuno che conosce solo la programmazione funzionale? –

+0

Quale parte del processo di caricamento e istanziazione della classe non è ancora chiara? – Joni

risposta

31

Non c'è alcun problema nella creazione di istanze di una classe nella classe stessa. L'apparente problema di pollo o uovo viene risolto in diversi modi mentre il programma viene compilato e quando viene eseguito.

fase di compilazione

Quando una classe che crea un'istanza della stessa viene compilato, il compilatore rileva che la classe ha un circular dependency su se stessa. Questa dipendenza è facile da risolvere: il compilatore sa che la classe è già in fase di compilazione, quindi non tenterà di compilarla nuovamente. Invece, finge che la classe già esistente generi codice di conseguenza.

di run-time

Il problema più grande di pollo-o-uovo con una classe creando un oggetto di per sé è quando la classe non ha nemmeno esisteva ancora; cioè, quando la classe viene caricata. Questo problema viene risolto interrompendo il caricamento della classe in due passaggi: prima la classe è definita e quindi è inizializzata.

Definire significa registrare la classe con il sistema di runtime (JVM o CLR), in modo che conosca la struttura che hanno gli oggetti della classe e quale codice debba essere eseguito quando vengono chiamati i suoi costruttori e metodi.

Una volta definita la classe, viene inizializzata. Questo viene fatto inizializzando i membri statici ed eseguendo blocchi di inizializzazione statici e altre cose definite nella lingua specifica. Ricorda che la classe è già definita a questo punto, quindi il runtime sa quali oggetti della classe assomigliano e quale codice deve essere eseguito per crearli. Ciò significa che non c'è alcun problema a creare oggetti della classe durante l'inizializzazione.

Ecco un esempio che illustra come l'inizializzazione di classe e instanziazione interagiscono in Java:

class Test { 
    static Test instance = new Test(); 
    static int x = 1; 

    public Test() { 
     System.out.printf("x=%d\n", x); 
    } 

    public static void main(String[] args) { 
     Test t = new Test(); 
    } 
} 

Facciamo un passo attraverso il modo JVM sarebbe eseguire questo programma. Per prima cosa la JVM carica la classe Test.Ciò significa che la classe è prima definita, in modo che la JVM sa che

  1. una classe denominata Test esiste e che ha un metodo main e un costruttore, e che
  2. classe Test ha due statico variabili, una chiamata x e un'altra chiamata instance e
  3. qual è il layout dell'oggetto della classe Test. In altre parole: come appare un oggetto; che attributi ha. In questo caso, Test non ha attributi di istanza.

Ora che la classe è definita, è inizializzata. Prima di tutto, il valore predefinito 0 o null viene assegnato a ogni attributo statico. Questo imposta x a 0. Quindi la JVM esegue gli inizializzatori del campo statico nell'ordine del codice sorgente. Ci sono due:

  1. Creare un'istanza della classe Test e assegnarlo a instance. Esistono due passaggi per la creazione dell'istanza:
    1. La prima memoria è allocata per l'oggetto. La JVM può farlo perché conosce già il layout dell'oggetto dalla fase di definizione della classe.
    2. Il costruttore Test() viene chiamato per inizializzare l'oggetto. La JVM può farlo perché ha già il codice per il costruttore dalla fase di definizione della classe. Il costruttore stampa il valore corrente di x, che è 0.
  2. Imposta variabile statica x a 1.

Solo ora la classe ha terminato il caricamento. Si noti che JVM ha creato un'istanza della classe, anche se non è ancora stata caricata completamente. Hai la prova di questo fatto perché il costruttore ha stampato il valore predefinito iniziale 0 per x.

Ora che la JVM ha caricato questa classe, chiama il metodo main per eseguire il programma. Il metodo main crea un altro oggetto della classe Test - il secondo nell'esecuzione del programma. Anche in questo caso il costruttore stampa il valore corrente di x, che ora è 1. L'uscita completa del programma è:

x=0 
x=1 

Come potete vedere non v'è alcun problema di pollo-o-uovo: la separazione di classe di carico nelle fasi di definizione e di inizializzazione evita completamente il problema.

E quando un'istanza dell'oggetto vuole creare un'altra istanza, come nel codice qui sotto?

class Test { 
    Test buggy = new Test(); 
} 

Quando si crea un oggetto di questa classe, di nuovo non vi è alcun problema inerente. La JVM sa come deve essere disposto l'oggetto in memoria in modo che possa allocare memoria per esso. Imposta tutti gli attributi sui valori predefiniti, quindi buggy è impostato su null. Quindi la JVM avvia l'inizializzazione dell'oggetto.Per fare ciò deve creare un altro oggetto della classe Test. Come in precedenza, la JVM sa già come farlo: assegna la memoria, imposta l'attributo a null e inizia l'inizializzazione del nuovo oggetto ... il che significa che deve creare un terzo oggetto della stessa classe e quindi un quarto, un quinto, e così via, finché non esaurisce lo spazio di stack o la memoria heap.

Non c'è nessun problema concettuale qui a mente: questo è solo un caso comune di una ricorsione infinita in un programma scritto male. La ricorsione può essere controllata ad esempio usando un contatore; il costruttore di questa classe utilizza la ricorsione a fare una catena di oggetti:

class Chain { 
    Chain link = null; 
    public Chain(int length) { 
     if (length > 1) link = new Chain(length-1); 
    } 
} 
+0

Significa che le espressioni all'interno della definizione della classe che crea l'istanza della propria classe non vengono eseguite/compilate prima delle altre espressioni? Hai detto che la classe è già definita a questo punto e questo è vero, ma come può l'istanza essere in grado di accedere al metodo che non è definito fino a quel punto. Potete per favore elaborare la vostra risposta poco per spiegare questo a qualcuno che ha sempre usato un linguaggio interpretato (il codice viene eseguito riga per riga) –

+0

Tutti i metodi, i costruttori e gli altri membri sono definiti quando la classe è definita, prima di qualsiasi codice in la classe viene eseguita. – Joni

+0

Si prega di cancellare la mia confusione qui, non è l'istanza della classe della propria classe anche il membro della classe nel mio codice sopra? Sto cercando di cancellare i miei pensieri, Joni. –

-1

Creazione di un'istanza di un oggetto all'interno dell'oggetto potrebbe portare in uno StackOverflowError poiché ogni volta che si crea un'istanza da questa "Test" classe che si creerà un'altra istanza e un'altra istanza e così via. Cerca di evitare questa pratica!

public class Test { 

    public Test() { 
     Test ob = new Test();  
    } 

    public static void main(String[] args) { 
     Test alpha = new Test(); 
    } 
} 
+0

Formatta lo snippet di codice per favore. – leppie

+0

Questo è vero solo se è fatto nel costruttore, per lo stesso costruttore, e incondizionatamente, come risultato in ricorsione infinita. Se è fatto al di fuori del costruttore (ad esempio, in un metodo 'Clone'), questo non si applica. Se è fatto condizionalmente, allora è ricorsione, ma non ricorsione infinita. – Servy

+0

@Servy, creando un'istanza dello stesso oggetto di classe * al momento della dichiarazione causerà anche un'eccezione di ricorsione e stackoverflow infinita. Non è solo il costruttore. Questo codice * (uguale alla domanda) * darà un'eccezione di overflow dello stack in runtime 'public class My_Class {My_Class new_class = new My_Class();}', inoltre non sono sicuro che java si comporti diversamente, ma in C#, sarebbe un eccezione. – Habib

1

Altre risposte hanno principalmente riguardato la domanda. Se aiuta a avvolgere un cervello attorno ad esso, che ne dite di un esempio?

Il problema della gallina e dell'uovo viene risolto come ogni problema ricorsivo: il caso base che non continua a produrre più lavoro/istanze/altro.

Immagina di aver assemblato una classe per gestire automaticamente la chiamata di eventi cross-thread quando necessario. Molto rilevante per i threaded WinForms. Quindi desideri che la classe esponga un evento che si verifica quando qualcosa registra o annulla la registrazione con il gestore e, naturalmente, dovrebbe gestire anche l'invocazione cross-thread.

È possibile scrivere il codice che lo gestisce due volte, una volta per l'evento stesso e una volta per l'evento di stato, oppure scrivere una volta e riutilizzare.

La maggior parte della classe è stata ritagliata in quanto non pertinente alla discussione.

public sealed class AutoInvokingEvent 
{ 
    private AutoInvokingEvent _statuschanged; 

    public event EventHandler StatusChanged 
    { 
     add 
     { 
      _statuschanged.Register(value); 
     } 
     remove 
     { 
      _statuschanged.Unregister(value); 
     } 
    } 

    private void OnStatusChanged() 
    { 
     if (_statuschanged == null) return; 

     _statuschanged.OnEvent(this, EventArgs.Empty); 
    } 


    private AutoInvokingEvent() 
    { 
     //basis case what doesn't allocate the event 
    } 

    /// <summary> 
    /// Creates a new instance of the AutoInvokingEvent. 
    /// </summary> 
    /// <param name="statusevent">If true, the AutoInvokingEvent will generate events which can be used to inform components of its status.</param> 
    public AutoInvokingEvent(bool statusevent) 
    { 
     if (statusevent) _statuschanged = new AutoInvokingEvent(); 
    } 


    public void Register(Delegate value) 
    { 
     //mess what registers event 

     OnStatusChanged(); 
    } 

    public void Unregister(Delegate value) 
    { 
     //mess what unregisters event 

     OnStatusChanged(); 
    } 

    public void OnEvent(params object[] args) 
    { 
     //mess what calls event handlers 
    } 

} 
2

La cosa principale che ho sempre vedo la creazione di un'istanza dall'interno della classe, è quando sto cercando di fare riferimento a un elemento non statico in un contesto statico, come quando sto facendo una cornice per un gioco o qualsiasi altra cosa, io uso il metodo principale per impostare effettivamente la cornice. È inoltre possibile utilizzare per quando c'è qualcosa in un costruttore che si desidera impostare (come nel seguito, io faccio il mio JFrame non uguale a null):

public class Main { 
    private JFrame frame; 

    public Main() { 
     frame = new JFrame("Test"); 
    } 

    public static void main(String[] args) { 
     Main m = new Main(); 

     m.frame.setResizable(false); 
     m.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     m.frame.setLocationRelativeTo(null); 
     m.frame.setVisible(true); 
    } 
} 
Problemi correlati