1. Inizializzazione delle variabili

Come già sai, puoi dichiarare diverse variabili nella tua classe, e non solo dichiararle, ma anche inizializzarle immediatamente con i loro valori iniziali.

E queste stesse variabili possono essere inizializzate anche in un costruttore. Ciò significa che, in teoria, a queste variabili potrebbero essere assegnati valori due volte. Esempio

Codice Nota
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



Alla agevariabile viene assegnato un valore iniziale




Il valore iniziale viene sovrascritto


La variabile age memorizza il suo valore iniziale.
 Cat cat = new Cat("Whiskers", 2);
Questo è consentito: verrà chiamato il primo costruttore
 Cat cat = new Cat();
Questo è consentito: verrà chiamato il secondo costruttore

Questo è ciò che accade quando Cat cat = new Cat("Whiskers", 2);viene eseguito:

  • CatViene creato un oggetto
  • Tutte le variabili di istanza vengono inizializzate con i loro valori iniziali
  • Il costruttore viene chiamato e il suo codice viene eseguito.

In altre parole, le variabili ottengono prima i loro valori iniziali e solo allora viene eseguito il codice del costruttore.


2. Ordine di inizializzazione delle variabili in una classe

Le variabili non vengono semplicemente inizializzate prima dell'esecuzione del costruttore, ma vengono inizializzate in un ordine ben definito: l'ordine in cui vengono dichiarate nella classe.

Diamo un'occhiata a un codice interessante:

Codice Nota
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Questo codice non verrà compilato, poiché al momento adella creazione della variabile non ci sono  ancora variabili b e . cMa puoi scrivere il tuo codice come segue: questo codice verrà compilato e funzionerà correttamente.

Codice Nota
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


00
+20
+2+3

Ma ricorda che il tuo codice deve essere trasparente per gli altri sviluppatori. È meglio non utilizzare tecniche di questo tipo, poiché compromettono la leggibilità del codice.

Qui dobbiamo ricordare che prima che alle variabili venga assegnato un valore hanno un valore predefinito . Per il inttipo, questo è zero.

Quando la JVM inizializza la avariabile, assegnerà semplicemente il valore predefinito per il tipo int: 0.

Quando raggiunge b, la variabile a sarà già nota e avrà un valore, quindi la JVM le assegnerà il valore 2.

E quando raggiunge la cvariabile, le variabili ae bsaranno già inizializzate, quindi la JVM calcolerà facilmente il valore iniziale per c: 0+2+3.

Se crei una variabile all'interno di un metodo, non puoi usarla a meno che tu non le abbia precedentemente assegnato un valore. Ma questo non è vero per le variabili di una classe! Se un valore iniziale non è assegnato a una variabile di una classe, allora viene assegnato un valore predefinito.


3. Costanti

Mentre analizziamo come vengono creati gli oggetti, vale la pena soffermarsi sull'inizializzazione delle costanti, cioè delle variabili con il finalmodificatore.

Se una variabile ha il finalmodificatore, deve essere assegnato un valore iniziale. Lo sai già e non c'è nulla di sorprendente in questo.

Ma quello che non sai è che non devi assegnare subito il valore iniziale se lo assegni nel costruttore. Questo funzionerà bene per una variabile finale. L'unico requisito è che se si dispone di più costruttori, a una variabile finale deve essere assegnato un valore in ciascun costruttore.

Esempio:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. Codice in un costruttore

E alcune note più importanti sui costruttori. Successivamente, mentre continui ad imparare Java, ti imbatterai in cose come ereditarietà, serializzazione, eccezioni, ecc. Tutte influenzano il lavoro dei costruttori a vari livelli. Non ha senso approfondire questi argomenti ora, ma siamo obbligati almeno a toccarli.

Ad esempio, ecco un'osservazione importante sui costruttori. In teoria, puoi scrivere codice di qualsiasi complessità in un costruttore. Ma non farlo. Esempio:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






Aprire un file read stream
Leggere il file in un array di byte
Salvare l'array di byte come una stringa




Visualizzare il contenuto del file sullo schermo

Nel costruttore della classe FilePrinter, abbiamo immediatamente aperto un flusso di byte su un file e letto il suo contenuto. Questo è un comportamento complesso e può causare errori.

E se non esistesse un file del genere? E se ci fossero problemi con la lettura del file? E se fosse troppo grande?

La logica complessa implica un'alta probabilità di errori e ciò significa che il codice deve gestire correttamente le eccezioni.

Esempio 1 — Serializzazione

In un programma Java standard, ci sono molte situazioni in cui non sei tu a creare oggetti della tua classe. Ad esempio, supponiamo che tu decida di inviare un oggetto sulla rete: in questo caso, la macchina Java stessa convertirà il tuo oggetto in un insieme di byte, lo invierà e ricreerà l'oggetto dall'insieme di byte.

Ma poi supponiamo che il tuo file non esista sull'altro computer. Ci sarà un errore nel costruttore e nessuno lo gestirà. E questo è perfettamente in grado di causare la chiusura del programma.

Esempio 2 — Inizializzazione dei campi di una classe

Se il tuo costruttore di classe può lanciare eccezioni controllate, cioè è contrassegnato con la parola chiave throws, allora devi intercettare le eccezioni indicate nel metodo che crea il tuo oggetto.

Ma cosa succede se non esiste un tale metodo? Esempio:

Codice  Nota
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Questo codice non verrà compilato.

Il FilePrintercostruttore della classe può generare un'eccezione verificata , il che significa che non è possibile creare un FilePrinteroggetto senza racchiuderlo in un blocco try-catch. E un blocco try-catch può essere scritto solo in un metodo



5. Costruttore della classe base

Nelle lezioni precedenti abbiamo parlato un po' dell'ereditarietà. Sfortunatamente, la nostra discussione completa su ereditarietà e OOP è riservata al livello dedicato a OOP e l'ereditarietà dei costruttori è già rilevante per noi.

Se la tua classe eredita un'altra classe, un oggetto della classe genitore verrà incorporato all'interno di un oggetto della tua classe. Inoltre, la classe genitore ha le proprie variabili ei propri costruttori.

Ciò significa che è molto importante per te sapere e capire come vengono inizializzate le variabili e come vengono chiamati i costruttori quando la tua classe ha una classe genitore e ne erediti le variabili e i metodi.

Classi

Come facciamo a sapere l'ordine in cui le variabili vengono inizializzate e vengono chiamati i costruttori? Iniziamo scrivendo il codice per due classi. Uno erediterà l'altro:

Codice Nota
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










La ChildClass classe eredita la ParentClassclasse.

Dobbiamo determinare l'ordine in cui le variabili vengono inizializzate e vengono chiamati i costruttori. La registrazione ci aiuterà a farlo.

Registrazione

La registrazione è il processo di registrazione delle azioni eseguite da un programma durante l'esecuzione, scrivendole nella console o in un file.

È abbastanza semplice determinare che il costruttore è stato chiamato: nel corpo del costruttore, scrivi un messaggio alla console. Ma come puoi sapere se una variabile è stata inizializzata?

In realtà, anche questo non è molto difficile: scrivere un metodo speciale che restituirà il valore utilizzato per inizializzare la variabile e registrare l'inizializzazione. Ecco come potrebbe apparire il codice:

Codice finale

public class Main
{
   public static void main(String[] args)
   {
      ChildClass obj = new ChildClass();
   }

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




Crea un ChildClassoggetto


Questo metodo scrive il testo passato alla console e lo restituisce.





Dichiara la ParentClassclasse

Display text e inizializza anche le variabili con essa.




Scrivi un messaggio che il costruttore è stato chiamato. Ignora il valore restituito.


Dichiara la ChildClassclasse

Display text e inizializza anche le variabili con essa.




Scrivi un messaggio che il costruttore è stato chiamato. Ignora il valore restituito.

Se esegui questo codice, il testo verrà visualizzato sullo schermo come segue:

Output della console del metodoMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Quindi puoi sempre assicurarti personalmente che le variabili di una classe vengano inizializzate prima che venga chiamato il costruttore. Una classe base viene inizializzata completamente prima dell'inizializzazione della classe ereditata.