Costruttori di Record in Java
- I record in Java possono avere costruttori canonici, compatti e non-canonici.
- Il costruttore canonico è il costruttore predefinito che inizializza i campi del record.
- Il costruttore compatto è una forma semplificata del costruttore canonico che consente di rimuovere spazi iniziali e finali dai campi.
- I costruttori non-canonici possono essere utilizzati per creare record con valori predefiniti o segnaposto.
Creare Costruttori di Record
Anche se spesso il costruttore canonico fornito automaticamente per i record è sufficiente per gli scopi di un programma, è comunque possibile definire uno o più costruttori personalizzati.
Si può anche definire la propria implementazione del costruttore canonico.
Esistono varie motivazioni per la creazione di costruttori personalizzati per un record. Ad esempio, il costruttore potrebbe verificare che un valore sia all'interno di un intervallo richiesto, verificare che un valore sia nel formato corretto, assicurarsi che un oggetto implementi funzionalità opzionali, o confermare che un argomento non sia null
.
Per un record, ci sono due tipi generali di costruttori che si possono creare esplicitamente: canonico e non-canonico, e ci sono alcune differenze tra i due.
La creazione di ogni tipo viene esaminata qui, iniziando con la definizione della implementazione del costruttore canonico.
Dichiarare un Costruttore Canonico
Sebbene il costruttore canonico abbia una forma specifica e predefinita, ci sono due modi in cui è possibile codificare la propria implementazione:
- Primo, è possibile dichiarare esplicitamente la forma completa del costruttore canonico.
- Secondo, è possibile utilizzare quello che viene chiamato un costruttore record compatto. Ogni approccio viene esaminato qui, iniziando con la forma completa.
Per definire la propria implementazione di un costruttore canonico, è sufficiente farlo come si farebbe con qualsiasi altro costruttore, specificando il nome del record e la sua lista di parametri. È importante sottolineare che per il costruttore canonico, i tipi e i nomi dei parametri devono essere gli stessi di quelli specificati dalla dichiarazione record
. Questo perché i nomi dei parametri sono collegati ai campi creati automaticamente e ai metodi di accesso definiti dalla dichiarazione record
. Pertanto, devono concordare sia nel tipo che nel nome. Inoltre, ogni componente deve essere completamente inizializzato al completamento del costruttore. Si applicano anche le seguenti restrizioni: il costruttore deve essere almeno accessibile quanto la sua dichiarazione record
. Pertanto, se il modificatore di accesso per il record
è public
, anche il costruttore deve essere specificato public
. Un costruttore non può essere generico e non può includere una clausola throws
. Inoltre non può invocare un altro costruttore definito per il record.
Ecco un esempio del record Impiegato che definisce esplicitamente il costruttore canonico. Questo costruttore rimuove eventuali spazi iniziali o finali da un nome. Questo garantisce che i nomi siano memorizzati in modo coerente.
record Impiegato(String nome, int numId) {
// Usa un costruttore canonico per rimuovere
// eventuali spazi iniziali e finali
// che potrebbero essere nella stringa nome.
// Questo garantisce che i nomi siano memorizzati
// in modo coerente.
public Impiegato(String nome, int numId) {
// Rimuove eventuali spazi iniziali e finali.
this.nome = nome.trim();
this.numId = numId;
}
}
Nel costruttore, gli spazi iniziali e/o finali vengono rimossi da una chiamata a trim()
. Definito dalla classe String
, trim()
elimina tutti gli spazi iniziali e finali da una stringa e restituisce il risultato. Se non ci sono spazi iniziali o finali, la stringa originale viene restituita inalterata. La stringa risultante viene assegnata al campo this.nome
. Pertanto, nessun nome
del record Impiegato
conterrà spazi iniziali o finali. Successivamente, il valore di numId viene assegnato a this.numId
. Poiché gli identificatori nome
e numId
sono gli stessi sia per i campi corrispondenti ai componenti Impiegato
che per i nomi utilizzati dai parametri del costruttore canonico, i nomi dei campi devono essere qualificati da this
.
Sebbene non ci sia certamente nulla di sbagliato nel creare un costruttore canonico come appena mostrato, spesso c'è un modo più semplice: attraverso l'uso di un costruttore compatto. Un costruttore record compatto viene dichiarato specificando il nome del record, ma senza parametri. Il costruttore compatto ha implicitamente parametri che sono gli stessi dei componenti del record, e i suoi componenti vengono automaticamente assegnati ai valori degli argomenti passati al costruttore. All'interno del costruttore compatto è possibile, tuttavia, alterare uno o più degli argomenti prima che il loro valore venga assegnato al record.
L'esempio seguente converte il precedente costruttore canonico nella sua forma compatta:
// Usa un costruttore canonico compatto
// per rimuovere eventuali spazi iniziali e
// finali dalla stringa nome.
public Impiegato {
// Rimuove eventuali spazi iniziali e finali.
nome = nome.trim();
}
Qui, il risultato di trim()
viene chiamato sul parametro nome
(che è dichiarato implicitamente dal costruttore compatto) e il risultato viene assegnato nuovamente al parametro nome
. Alla fine del costruttore compatto, il valore di nome
viene automaticamente assegnato al suo campo corrispondente. Il valore del parametro implicito numId
viene anche assegnato al suo campo corrispondente alla fine del costruttore. Poiché i parametri vengono implicitamente assegnati ai loro campi corrispondenti quando il costruttore termina, non c'è bisogno di inizializzare i campi esplicitamente. Inoltre, non sarebbe legale farlo.
Ecco una versione rielaborata del programma visto nella lezione precedente che dimostra il costruttore canonico compatto:
// Un semplice esempio di Record.
// Dichiara un record impiegato.
record Impiegato(String nome, int numeroId) {
// Costruttore canonico compatto.
public Impiegato {
// Rimuove eventuali spazi iniziali e finali.
nome = nome.trim();
}
}
class DemoRecord {
public static void main(String[] args) {
// Crea un array di record Impiegato.
Impiegato[] listaImp = new Impiegato[4];
// Crea una lista di impiegati che usa il record Impiegato.
// Notate come ogni record è costruito. Gli argomenti
// sono automaticamente assegnati ai campi nome e numeroId nel
// record che sta essere creato.
listaImp[0] = new Impiegato(" Giovanni Rossi ", 1047);
listaImp[1] = new Impiegato(" Michele Verdi", 1048);
listaImp[2] = new Impiegato(" Serena Bianchi ", 1049);
listaImp[3] = new Impiegato(" Silvia Neri", 1050);
// Usa gli accessor del record per visualizzare nomi e ID.
for (Impiegato e : listaImp)
System.out.println("L'ID impiegato per " + e.nome() + " è " +
e.numeroId());
}
}
L'output è mostrato di seguito:
L'ID impiegato per Giovanni Rossi è 1047
L'ID impiegato per Michele Verdi è 1048
L'ID impiegato per Serena Bianchi è 1049
L'ID impiegato per Silvia Neri è 1050
Come si può vedere, i nomi sono stati standardizzati con gli spazi iniziali e finali rimossi. Per convincersi che la chiamata a trim()
sia necessaria per ottenere questo risultato, è sufficiente rimuovere il costruttore compatto, ricompilare ed eseguire il programma. Gli spazi iniziali e finali saranno ancora nei nomi.
Dichiarare un Costruttore Non-canonico
Sebbene il costruttore canonico sia spesso sufficiente, possiamo dichiarare altri costruttori. Il requisito chiave è che qualsiasi costruttore non-canonico deve prima chiamare un altro costruttore nel record tramite this
. Il costruttore invocato sarà spesso il costruttore canonico. Facendo questo si garantisce che tutti i campi vengano assegnati. Dichiarare un costruttore non-canonico ci consente di creare record per casi speciali. Ad esempio, potremmo usare un tale costruttore per creare un record in cui uno o più componenti ricevono un valore segnaposto predefinito.
Il seguente programma dichiara un costruttore non-canonico per Impiegato che inizializza il nome a un valore noto, ma assegna al campo numeroId
il valore speciale idInSospeso
(che è –1) per indicare che un valore ID non è disponibile quando il record viene creato:
// Un semplice esempio di Record.
// Dichiara un record impiegato.
record Impiegato(String nome, int numeroId) {
// Usa un campo static in un record.
static int idInSospeso = -1;
// Costruttore canonico compatto.
public Impiegato {
// Rimuove eventuali spazi iniziali e finali.
nome = nome.trim();
}
// Questo è un costruttore non-canonico.
// Si noti che non riceve
// un numero ID. Invece, passa idInSospeso al
// costruttore canonico per creare il record.
public Impiegato(String nome) {
this(nome, idInSospeso);
}
}
class DemoRecord {
public static void main(String[] args) {
// Crea un array di record Impiegato.
Impiegato[] listaImp = new Impiegato[4];
// Crea una lista di impiegati che usa il record Impiegato.
// Notate come ogni record è costruito. Gli argomenti
// sono automaticamente assegnati ai campi nome e numeroId nel
// record che sta essere creato.
listaImp[0] = new Impiegato(" Giovanni Rossi ", 1047);
listaImp[1] = new Impiegato(" Michele Verdi", 1048);
listaImp[2] = new Impiegato(" Serena Bianchi ", 1049);
listaImp[3] = new Impiegato(" Silvia Neri");
// Usa gli accessor del record per visualizzare nomi e ID.
for (Impiegato e : listaImp)
System.out.println("L'ID impiegato per " + e.nome() + " è " +
e.numeroId());
}
}
L'output è mostrato di seguito:
L'ID impiegato per Giovanni Rossi è 1047
L'ID impiegato per Michele Verdi è 1048
L'ID impiegato per Serena Bianchi è 1049
L'ID impiegato per Silvia Neri è -1
Si presti particolare attenzione al modo in cui il record per "Silvia Neri"
viene creato usando il costruttore non-canonico. Quel costruttore passa l'argomento nome
al costruttore canonico, ma specifica il valore idInSospeso
come valore numeroId
. Questo consente di creare un record segnaposto senza dover specificare un numero ID. Un altro punto: si noti che il valore idInSospeso
è dichiarato come campo static
in Impiegato
. Come spiegato in precedenza, i campi di istanza non sono consentiti in una dichiarazione record
, ma un campo static
è legale.
Si noti che questa versione di Impiegato
dichiara sia un costruttore canonico che un costruttore non-canonico. Questo è perfettamente valido. Un record può definire tutti i diversi costruttori di cui ha bisogno, purché tutti aderiscano alle regole definite per record
.
È importante sottolineare che i record sono immutabili. Per quanto riguarda questo esempio, significa che quando si ottiene un valore ID per Martin, Dave, il vecchio record deve essere sostituito da un nuovo record che contiene il numero ID. Non è possibile alterare il record per aggiornare l'ID. L'immutabilità dei record è un attributo primario.
Un Altro Esempio di Costruttore Record
Concludiamo questa lezione sui costruttori di un record con un altro esempio.
Poiché un record
viene utilizzato per aggregare dati, un uso comune di un costruttore record
è verificare la validità o l'applicabilità di un argomento. Ad esempio, prima di costruire il record, il costruttore potrebbe dover determinare se un valore è fuori intervallo, in un formato improprio, o altrimenti inadatto per l'uso. Se viene trovata una condizione non valida, il costruttore potrebbe creare un'istanza predefinita o di errore. Tuttavia, spesso sarebbe meglio che il costruttore lanciasse un'eccezione. In questo modo, l'utente del record
sarebbe immediatamente consapevole dell'errore e potrebbe prendere provvedimenti per correggerlo.
Negli esempi precedenti del record Impiegato, i nomi sono stati specificati utilizzando la convenzione comune di nome, cognome, come "Mario Rossi"
. Tuttavia, non c'era alcun meccanismo per verificare o imporre che questo formato fosse utilizzato. La seguente versione del costruttore canonico compatto fornisce un controllo limitato che vi sia almeno uno spazio tra nome e cognome. Lo fa confermando che ci sia un solo spazio e che ci sia almeno un carattere (diverso dallo spazio) prima e dopo lo spazio. Sebbene una verifica molto più approfondita e attenta sarebbe necessaria per un programma del mondo reale, questo controllo minimo è sufficiente per servire come esempio del ruolo di validazione che un costruttore record potrebbe svolgere.
Ecco una versione del record Impiegato
in cui il costruttore canonico compatto lancia un'eccezione se il componente nome
non soddisfa i criteri minimi richiesti per il formato "nome cognome"
:
// Un semplice esempio di Record.
// Dichiara un record impiegato.
record Impiegato(String nome, int numeroId) {
// Usa un campo static in un record.
static int idInSospeso = -1;
// Costruttore canonico
public Impiegato {
// Rimuove eventuali spazi iniziali e finali.
nome = nome.trim();
// Esegue una verifica minima per il formato del nome.
// 1. Controlla che il nome non sia vuoto.
// 2. Controlla che ci sia almeno uno spazio tra nome e cognome.
// 3. Controlla che ci sia almeno un carattere (diverso dallo spazio)
// prima e dopo lo spazio.
if (nome.isEmpty())
throw new IllegalArgumentException("Il nome non può essere vuoto.");
if (!nome.contains(" ")
|| nome.indexOf(" ") == 0
|| nome.indexOf(" ") == nome.length() - 1) {
throw new IllegalArgumentException("Il nome deve essere nel formato 'nome cognome'.");
}
}
// Questo è un costruttore non-canonico.
// Si noti che non riceve
// un numero ID. Invece, passa idInSospeso al
// costruttore canonico per creare il record.
public Impiegato(String nome) {
this(nome, idInSospeso);
}
}
class DemoRecord {
public static void main(String[] args) {
// Crea un array di record Impiegato.
Impiegato[] listaImp = new Impiegato[4];
// Crea una lista di impiegati che usa il record Impiegato.
// Notate come ogni record è costruito. Gli argomenti
// sono automaticamente assegnati ai campi nome e numeroId nel
// record che sta essere creato.
listaImp[0] = new Impiegato(" Giovanni Rossi ", 1047);
listaImp[1] = new Impiegato(" Michele", 1048);
listaImp[2] = new Impiegato(" Serena Bianchi Gialli ", 1049);
listaImp[3] = new Impiegato(" Silvia , Neri");
// Usa gli accessor del record per visualizzare nomi e ID.
for (Impiegato e : listaImp)
System.out.println("L'ID impiegato per " + e.nome() + " è " +
e.numeroId());
}
}
Quando si utilizza questo costruttore, la seguente istruzione è ancora corretta:
listaImp[0] = new Impiegato(" Giovanni Rossi ", 1047);
Tuttavia, le seguenti tre sono non valide e risulteranno in un'eccezione:
// Errore: Contiene solo il nome
listaImp[1] = new Impiegato(" Michele", 1048);
// Errore: Contiene Due Cognomi
listaImp[2] = new Impiegato(" Serena Bianchi Gialli ", 1049);
// Errore: C'è una virgola nel nome
listaImp[3] = new Impiegato(" Silvia , Neri");