Riferimenti a Costruttori in Java
- I riferimenti ai costruttori in Java permettono di creare istanze di classi in modo conciso e funzionale.
- Si possono creare riferimenti ai costruttori sia per classi normali che per classi generiche.
- I riferimenti ai costruttori possono essere utilizzati per creare array di oggetti, migliorando la leggibilità e la manutenibilità del codice.
Riferimenti ai Costruttori
Similmente al modo in cui si possono creare riferimenti ai metodi, è possibile creare riferimenti ai costruttori.
Ecco la forma generale della sintassi che bisogna adoperare:
nomeclasse::new
Questo riferimento può essere assegnato a qualsiasi riferimento di interfaccia funzionale che definisce un metodo compatibile con il costruttore.
Ecco un semplice esempio:
// Dimostra un riferimento a Costruttore.
// MiaFunc è un'interfaccia funzionale il cui metodo restituisce
// un riferimento MiaClasse.
interface MiaFunc {
MiaClasse func(int n);
}
class MiaClasse {
// Questo è un campo di istanza.
private int val;
// Questo costruttore prende un argomento.
MiaClasse(int v) {
val = v;
}
// Questo è il costruttore predefinito.
MiaClasse() {
val = 0;
}
// Restituisce il valore di val.
int getVal() {
return val;
}
}
class DemoRifCostruttore {
public static void main(String[] args) {
// Crea un riferimento al costruttore MiaClasse.
// Poiché func() in MiaFunc prende un argomento, new
// si riferisce al costruttore parametrizzato in MiaClasse,
// non al costruttore predefinito.
MiaFunc mioCostruttoreClasse = MiaClasse::new;
// Crea un'istanza di MiaClasse tramite quel riferimento al costruttore.
MiaClasse mc = mioCostruttoreClasse.func(100);
// Usa l'istanza di MiaClasse appena creata.
System.out.println("val in mc è " + mc.getVal());
}
}
L'output è mostrato qui:
val in mc è 100
Nel programma, notiamo che il metodo func()
di MiaFunc
restituisce un riferimento di tipo MiaClasse
e ha un parametro int
. Successivamente, notiamo che MiaClasse
definisce due costruttori. Il primo specifica un parametro di tipo int
.
Il secondo è il costruttore predefinito, senza parametri. Ora, esaminiamo la seguente riga:
MiaFunc mioCostruttoreClasse = MiaClasse::new;
Qui, l'espressione MiaClasse::new
crea un riferimento al costruttore di un costruttore MiaClasse
. In questo caso, poiché il metodo func()
di MiaFunc
prende un parametro int
, il costruttore a cui si fa riferimento è MiaClasse(int v)
perché è quello che corrisponde. Notiamo anche che il riferimento a questo costruttore è assegnato a un riferimento MiaFunc
chiamato mioCostruttoreClasse
. Dopo che questa istruzione viene eseguita, mioCostruttoreClasse
può essere utilizzato per creare un'istanza di MiaClasse
, come mostra questa riga:
MiaClasse mc = mioCostruttoreClasse.func(100);
In sostanza, mioCostruttoreClasse
è diventato un altro modo per chiamare MiaClasse(int v)
.
Riferimenti ai Costruttori per Classi Generiche
I riferimenti ai costruttori per classi generiche vengono creati nello stesso modo. L'unica differenza è che l'argomento di tipo può essere specificato. Questo funziona allo stesso modo di quando si usa una classe generica per creare un riferimento al metodo: basta specificare l'argomento di tipo dopo il nome della classe. Il seguente lo illustra modificando l'esempio precedente in modo che MiaFunc
e MiaClasse
siano generiche.
// Dimostra un riferimento al costruttore con una classe generica.
// MiaFunc è ora un'interfaccia funzionale generica.
interface MiaFunc<T> {
MiaClasse<T> func(T n);
}
class MiaClasse<T> {
// La classe è generica e ha un campo di tipo T.
private T val;
// Un costruttore che prende un argomento.
MiaClasse(T v) {
val = v;
}
// Questo è il costruttore predefinito.
MiaClasse() {
val = null;
}
// Un metodo per ottenere il valore.
T getVal() {
return val;
}
}
class DemoRifCostruttore2 {
public static void main(String[] args) {
// Crea un riferimento al costruttore MiaClasse<T>.
MiaFunc<Integer> mioCostruttoreClasse = MiaClasse<Integer>::new;
// Crea un'istanza di MiaClasse<T>
// tramite quel riferimento al costruttore.
MiaClasse<Integer> mc = mioCostruttoreClasse.func(100);
// Usa l'istanza di MiaClasse<T> appena creata.
System.out.println("val in mc is " + mc.getVal());
}
}
Questo programma produce lo stesso output della versione precedente. La differenza è che ora sia MiaFunc
che MiaClasse
sono generiche. Quindi, la sequenza che crea un riferimento al costruttore può includere un argomento di tipo (anche se non è sempre necessario), come mostrato qui:
MiaFunc<Integer> mioCostruttoreClasse = MiaClasse<Integer>::new;
Poiché l'argomento di tipo Integer
è già stato specificato quando mioCostruttoreClasse
viene creato, può essere utilizzato per creare un oggetto MiaClasse<Integer>
, come mostra la riga successiva:
MiaClasse<Integer> mc = mioCostruttoreClasse.func(100);
Anche se gli esempi precedenti dimostrano la meccanica dell'uso di un riferimento al costruttore, nessuno userebbe un riferimento al costruttore come appena mostrato perché non si ottiene nulla.
Inoltre, avere quello che equivale a due nomi per lo stesso costruttore crea una situazione confusa (per usare un eufemismo). Tuttavia, per avere idea di un utilizzo più pratico, il seguente programma usa un metodo static
, chiamato fabbricalaMiaClasse()
, che è una fabbrica per oggetti di qualsiasi tipo di oggetti MiaFunc
. Può essere utilizzato per creare qualsiasi tipo di oggetto che abbia un costruttore compatibile con il suo primo parametro.
// Implementa una semplice factory di classi
// usando un riferimento al costruttore.
// Un'interfaccia funzionale generica che rappresenta un costruttore.
// R è il tipo di oggetto restituito dal costruttore.
// T è il tipo dell'argomento passato al costruttore.
interface MiaFunc<R, T> {
R func(T n);
}
// Una semplice classe generica.
class MiaClasse<T> {
private T val;
// Un costruttore che prende un argomento.
MiaClasse(T v) {
val = v;
}
// Il costruttore predefinito. Questo costruttore
// NON è utilizzato da questo programma.
MiaClasse() {
val = null;
}
// Un metodo che restituisce il valore.
T getVal() {
return val;
}
}
// Una semplice classe non generica.
class MiaClasse2 {
String str;
// Un costruttore che prende un argomento.
MiaClasse2(String s) {
str = s;
}
// Il costruttore predefinito. Questo
// costruttore NON è utilizzato da questo programma.
MiaClasse2() {
str = "";
}
// Un metodo che restituisce il valore.
String getVal() {
return str;
}
}
class DemoRifCostruttore3 {
// Un metodo factory per oggetti di classe. La classe deve
// avere un costruttore che prenda un parametro di tipo T.
// R specifica il tipo di oggetto che viene creato.
static <R, T> R fabbricalaMiaClasse(MiaFunc<R, T> cons, T v) {
return cons.func(v);
}
public static void main(String[] args) {
// Crea un riferimento a un costruttore MiaClasse.
// In questo caso, new si riferisce al costruttore che
// prende un argomento.
MiaFunc<MiaClasse<Double>, Double> mioCostruttoreClasse =
MiaClasse<Double>::new;
// Crea un'istanza di MiaClasse utilizzando il metodo fabbrica.
MiaClasse<Double> mc = fabbricalaMiaClasse(mioCostruttoreClasse, 100.1);
// Usa l'istanza di MiaClasse appena creata.
System.out.println("val in mc è " + mc.getVal());
// Ora, crea una classe diversa utilizzando fabbricalaMiaClasse().
MiaFunc<MiaClasse2, String> mioCostruttoreClasse2 = MiaClasse2::new;
// Crea un'istanza di MiaClasse2 utilizzando il metodo fabbrica.
MiaClasse2 mc2 = fabbricalaMiaClasse(mioCostruttoreClasse2, "Lambda");
// Usa l'istanza di MiaClasse appena creata.
System.out.println("str in mc2 è " + mc2.getVal());
}
}
L'output è mostrato qui:
val in mc è 100.1
str in mc2 è Lambda
Come si può osservare, fabbricalaMiaClasse()
viene utilizzato per creare oggetti di tipo MiaClasse<Double>
e MiaClasse2
.
Anche se entrambe le classi differiscono, ad esempio MiaClasse
è generica e MiaClasse2
non lo è, entrambe possono essere create da fabbricalaMiaClasse()
perché entrambe hanno costruttori che sono compatibili con func()
in MiaFunc
.
Questo funziona perché a fabbricalaMiaClasse()
viene passato il costruttore per l'oggetto che costruisce.
Possiamo sperimentare un po' con questo programma, provando diverse classi da creare. Si può provare anche a creare istanze di diversi tipi di oggetti MiaClasse
. Ci si può rendere facilmente conto che fabbricalaMiaClasse()
può creare qualsiasi tipo di oggetto la cui classe abbia un costruttore che sia compatibile con func()
in MiaFunc
. Anche se questo esempio è abbastanza semplice, suggerisce la potenza che i riferimenti ai costruttori portano a Java.
Riferimenti ai Costruttori per Array
Prima di procedere, è importante menzionare una seconda forma della sintassi dei riferimenti ai costruttori che viene utilizzata per gli array. Per creare un riferimento al costruttore per un array, usa questo costrutto:
tipo[]::new
Qui, tipo
specifica il tipo di oggetto che viene creato. Ad esempio, assumendo la forma di MiaClasse
come mostrato nel primo esempio di riferimento al costruttore (DemoRifCostruttore
) e data l'interfaccia MioCreatoreArray
mostrata qui:
interface MioCreatoreArray<T> {
T func(int n);
}
il seguente crea un array di due elementi di oggetti MiaClasse
e dà a ogni elemento un valore iniziale:
MioCreatoreArray<MiaClasse[]> mioCostruttoreArrayMc =
MiaClasse[]::new;
MiaClasse[] a = mioCostruttoreArrayMc.func(2);
a[0] = new MiaClasse(1);
a[1] = new MiaClasse(2);
Qui, la chiamata a func(2)
causa la creazione di un array di due elementi. In generale, un'interfaccia funzionale deve contenere un metodo che prenda un singolo parametro int
se deve essere utilizzata per fare riferimento a un costruttore di array.