Costruttori Java
Usa i costruttori per inizializzare oggetti Java — costruttori default, parametrizzati e sovraccaricati, più il chaining di costruttori.
Un costruttore è un metodo speciale che viene eseguito una sola volta alla creazione di un oggetto. Il suo compito è portare la nuova istanza da "ogni campo al suo valore predefinito" a "pronta all'uso". Finora hai impostato i campi uno per uno dopo new Dog(); un costruttore ti permette di raggruppare quel lavoro nella chiamata new.
Anatomia di un costruttore
Un costruttore assomiglia a un metodo, con tre differenze: ha lo stesso nome della classe, nessun tipo di ritorno (nemmeno void), e viene chiamato solo da new:
public class Point {
int x, y;
public Point(int x, int y) { // constructor
this.x = x;
this.y = y;
}
}
Point p = new Point(3, 4); // runs the constructorLa lista degli argomenti va dentro le () dopo il nome della classe nell'espressione new. Java abbina quella lista di argomenti a un costruttore della classe, proprio come abbina una chiamata a un metodo.
Il costruttore default
Se non dichiari nessun costruttore, Java fornisce alla classe un costruttore default — un costruttore senza argomenti che non fa nulla:
public class Empty { } // compiler generates a no-arg constructor
Empty e = new Empty(); // worksÈ così che new Dog() funzionava negli esempi precedenti anche se non avevamo mai scritto public Dog() { ... }. Nel momento in cui dichiari qualsiasi costruttore tuo, il costruttore default gratuito scompare:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
}
Point p = new Point(); // ERROR — no no-arg constructor existsSe li vuoi entrambi, dichiarali entrambi — vedi il sovraccarico qui sotto.
Perché usare un costruttore?
Due motivi.
1. Campi obbligatori. Un costruttore ti permette di rendere alcuni campi non opzionali. Con il campo pubblico e poi l'assegnazione, nulla impedisce a un chiamante di creare una Person senza name. Con un costruttore Person(String name), non è possibile.
2. Invarianti. Un costruttore può validare i propri argomenti prima che l'oggetto esista in forma utilizzabile. Se un Circle richiede un raggio positivo, il costruttore può lanciare un'eccezione su uno negativo — l'oggetto difettoso non viene mai creato.
public class Circle {
double radius;
public Circle(double radius) {
if (radius <= 0) throw new IllegalArgumentException("radius must be > 0");
this.radius = radius;
}
}Il chiamante ora non può ottenere un Circle con raggio non positivo.
Costruttori sovraccaricati
Una classe può avere più costruttori con liste di parametri diverse, esattamente come i metodi sovraccaricati:
public class Rectangle {
double width, height;
public Rectangle() { this(1, 1); }
public Rectangle(double side) { this(side, side); } // a square
public Rectangle(double w, double h) { this.width = w; this.height = h; }
}Ora tutti e tre questi compilano:
Rectangle a = new Rectangle(); // 1 x 1
Rectangle b = new Rectangle(5); // 5 x 5
Rectangle c = new Rectangle(3, 4); // 3 x 4Costruttori di copia
Un sovraccarico comune è il costruttore di copia — uno che prende un'altra istanza della stessa classe e ne costruisce una copia indipendente. Java non ha un costruttore di copia integrato (a differenza di C++), quindi ne scrivi uno tuo quando ne hai bisogno:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public Point(Point other) { this(other.x, other.y); } // copy constructor
}
Point a = new Point(3, 4);
Point b = new Point(a); // a separate Point with the same valuesPoiché b è un nuovo oggetto, modificare b.x in seguito non influisce su a. Per le classi i cui campi sono a loro volta oggetti mutabili, copia anche quei campi (una deep copy) se vuoi che la copia sia completamente indipendente.
this(...) — chaining di costruttori
All'interno di un costruttore, this(args) chiama un altro costruttore della stessa classe. È così che l'esempio Rectangle sopra ha evitato di duplicare il codice di assegnazione dei campi: i due costruttori di convenienza delegano a quello completo.
Due regole:
this(...)deve essere la prima istruzione nel corpo del costruttore.- Un costruttore può concatenarsi a un solo altro costruttore.
public Rectangle() { this(1, 1); } // ok
public Rectangle(double s) { System.out.println("hi"); this(s, s); } // ERRORIl motivo della regola "prima istruzione" è che la JVM deve inizializzare completamente l'oggetto esattamente una volta prima che qualsiasi altro codice venga eseguito.
super(...) — chiamare il genitore
Quando una classe estende un'altra, ogni costruttore chiama esplicitamente un costruttore del genitore con super(args), oppure chiama implicitamente il costruttore senza argomenti del genitore. Tratteremo questo argomento in modo completo nei capitoli sull'ereditarietà e sulla parola chiave super; per ora, sappi solo che super(...) esiste e ha la stessa regola della prima istruzione di this(...).
I costruttori non possono restituire un valore
Un costruttore non ha tipo di ritorno — nemmeno void. Se ne scrivi uno:
public void Point(int x, int y) { ... } // not a constructor!…è un metodo normale che per caso si chiama Point. Il compilatore non ti avviserà; new Point(3, 4) fallirà quindi in compilazione perché il vero costruttore che sta cercando non esiste. Questo è un errore di battitura sorprendentemente comune.
Ordine di inizializzazione
Per una singola classe, l'ordine è:
- I valori predefiniti dei campi vengono assegnati (ad es. i campi
intdiventano0). - Gli inizializzatori di campo inline (
int count = 5;) e gli eventuali blocchi di inizializzazione di istanza vengono eseguiti nell'ordine in cui compaiono. - Il corpo del costruttore viene eseguito.
public class Demo {
int a = compute("a-init", 1);
int b;
{ b = compute("b-block", 2); } // instance initializer
public Demo() {
System.out.println("constructor; a=" + a + ", b=" + b);
}
static int compute(String label, int v) {
System.out.println(label);
return v;
}
}new Demo() stampa a-init, b-block, poi constructor; a=1, b=2.
In pratica, quasi tutta l'inizializzazione appartiene al corpo del costruttore. I blocchi di inizializzazione di istanza sono rari nel codice reale; esistono principalmente per situazioni come le classi anonime (trattate più avanti).
Un esempio pratico
Cosa c'è dopo
Nei costruttori hai già visto this.field = field per disambiguare un parametro da un campo. Il capitolo sulla parola chiave this chiarisce cosa è this e i pochi posti in cui devi scriverlo esplicitamente.