Parola chiave di registrazione Java 14

1. Introduzione

Il passaggio di dati immutabili tra oggetti è una delle attività più comuni, ma banali in molte applicazioni Java.

Prima di Java 14, ciò richiedeva la creazione di una classe con campi e metodi standard, suscettibili a errori banali e intenzioni confuse.

Con il rilascio di Java 14, ora possiamo utilizzare i record per risolvere questi problemi.

In questo tutorial, esamineremo i fondamenti dei record , incluso il loro scopo, i metodi generati e le tecniche di personalizzazione .

2. Scopo

Di solito, scriviamo classi per contenere semplicemente dati, come risultati di database, risultati di query o informazioni da un servizio.

In molti casi, questi dati sono immutabili, poiché l' immutabilità garantisce la validità dei dati senza sincronizzazione .

Per fare ciò, creiamo classi di dati con quanto segue:

  1. privato , finale campo per ogni pezzo di dati
  2. getter per ogni campo
  3. costruttore pubblico con un argomento corrispondente per ogni campo
  4. equals metodo che restituisce true per oggetti della stessa classe quando tutti i campi corrispondono
  5. hashCode che restituisce lo stesso valore quando tutti i campi corrispondono
  6. toString metodo che include il nome della classe e il nome di ogni campo e il valore corrispondente

Ad esempio, possiamo creare una semplice classe di dati Person , con un nome e un indirizzo:

public class Person { private final String name; private final String address; public Person(String name, String address) { this.name = name; this.address = address; } @Override public int hashCode() { return Objects.hash(name, address); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof Person)) { return false; } else { Person other = (Person) obj; return Objects.equals(name, other.name) && Objects.equals(address, other.address); } } @Override public String toString() { return "Person [name=" + name + ", address=" + address + "]"; } // standard getters }

Anche se questo raggiunge il nostro obiettivo, ci sono due problemi con esso:

  1. C'è molto codice boilerplate
  2. Oscuriamo lo scopo della nostra classe: rappresentare una persona con un nome e un indirizzo

Nel primo caso, dobbiamo ripetere lo stesso noioso processo per ogni classe di dati, creando in modo monotono un nuovo campo per ogni pezzo di dati, creando metodi uguali , hashCode e toString e creando un costruttore che accetti ogni campo.

Sebbene gli IDE possano generare automaticamente molte di queste classi, non riescono ad aggiornare automaticamente le nostre classi quando aggiungiamo un nuovo campo . Ad esempio, se aggiungiamo un nuovo campo, dobbiamo aggiornare il nostro metodo uguale per incorporare questo campo.

Nel secondo caso, il codice extra nasconde che la nostra classe è semplicemente una classe di dati che ha due campi String : nome e indirizzo .

Un approccio migliore sarebbe dichiarare esplicitamente che la nostra classe è una classe di dati.

3. Le basi

A partire da JDK 14, possiamo sostituire le nostre classi di dati ripetitive con i record. I record sono classi di dati immutabili che richiedono solo il tipo e il nome dei campi.

I metodi equals , hashCode e toString , nonché i campi privati , finali e il costruttore pubblico , vengono generati dal compilatore Java.

Per creare un record Persona , utilizziamo la parola chiave record :

public record Person (String name, String address) {}

3.1. Costruttore

Usando i record, viene generato un costruttore pubblico, con un argomento per ogni campo.

Nel caso del nostro record Person , il costruttore equivalente è:

public Person(String name, String address) { this.name = name; this.address = address; }

Questo costruttore può essere utilizzato allo stesso modo di una classe per istanziare oggetti dal record:

Person person = new Person("John Doe", "100 Linda Ln.");

3.2. Getters

Riceviamo anche metodi getter pubblici, i cui nomi corrispondono al nome del nostro campo, gratuitamente.

Nel nostro record Persona , questo significa un getter di nome () e indirizzo () :

@Test public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() { String name = "John Doe"; String address = "100 Linda Ln."; Person person = new Person(name, address); assertEquals(name, person.name()); assertEquals(address, person.address()); }

3.3. equivale

Inoltre, viene generato un metodo uguale per noi.

Questo metodo restituisce true se l'oggetto fornito è dello stesso tipo e i valori di tutti i suoi campi corrispondono:

@Test public void givenSameNameAndAddress_whenEquals_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertTrue(person1.equals(person2)); }

Se uno qualsiasi dei campi differisce tra due istanze di Person , il metodo equals restituirà false .

3.4. codice hash

Simile al nostro metodo uguale , viene generato anche un metodo hashCode corrispondente .

Il nostro metodo hashCode restituisce lo stesso valore per due oggetti Person se tutti i valori dei campi per entrambi gli oggetti corrispondono (salvo collisioni dovute al paradosso del compleanno) :

@Test public void givenSameNameAndAddress_whenHashCode_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertEquals(person1.hashCode(), person2.hashCode()); } 

Il valore hashCode sarà diverso se uno qualsiasi dei valori del campo è diverso.

3.5. toString

Lastly, we also receive atoString method that results in a string containing the name of the record, followed by the name of each field and its corresponding value in square brackets.

Therefore, instantiating a Person with a name of “John Doe” and an address of “100 Linda Ln.” results in the following toString result:

Person[name=John Doe, address=100 Linda Ln.]

4. Constructors

While a public constructor is generated for us, we can still customize our constructor implementation.

This customization is intended to be used for validation and should be kept as simple as possible.

For example, we can ensure that the name and address provided to our Person record are not null using the following constructor implementation:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } }

We can also create new constructors with different arguments by supplying a different argument list:

public record Person(String name, String address) { public Person(String name) { this(name, "Unknown"); } }

As with class constructors, the fields can be referenced using the this keyword (for example, this.name and this.address) and the arguments match the name of the fields (that is, name and address).

Note that creating a constructor with the same arguments as the generated public constructor is valid, but this requires that each field be manually initialized:

public record Person(String name, String address) { public Person(String name, String address) { this.name = name; this.address = address; } }

Additionally, declaring a no-argument constructor and one with an argument list matching the generated constructor results in a compilation error.

Therefore, the following will not compile:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } public Person(String name, String address) { this.name = name; this.address = address; } }

5. Static Variables & Methods

As with regular Java classes, we can also include static variables and methods in our records.

We declare static variables using the same syntax as a class:

public record Person(String name, String address) { public static String UNKNOWN_ADDRESS = "Unknown"; }

Likewise, we declare static methods using the same syntax as a class:

public record Person(String name, String address) { public static Person unnamed(String address) { return new Person("Unnamed", address); } }

We can then reference both static variables and static methods using the name of the record:

Person.UNKNOWN_ADDRESS Person.unnamed("100 Linda Ln.");

6. Conclusion

In questo articolo, abbiamo esaminato la parola chiave record introdotta in Java 14, inclusi i concetti e le complessità fondamentali.

Usando i record - con i loro metodi generati dal compilatore - possiamo ridurre il codice boilerplate e migliorare l'affidabilità delle nostre classi immutabili.

Il codice e gli esempi per questo tutorial possono essere trovati su GitHub.