1. Introduzione alla classe Objects
Passiamo all'argomento! Se vi siete stancati di controllare manualmente i valori per null e di scrivere il calcolo dell'hash per ogni campo, vi aiuterà la classe di utilità java.util.Objects. Il suo scopo — rendere il lavoro con gli oggetti più semplice, conciso e sicuro.
È davvero un «coltellino svizzero»: la classe sa confrontare in modo sicuro gli oggetti per l'uguaglianza (senza il rischio di ottenere NullPointerException), calcolare comodamente gli hash code, confrontare tramite un comparatore e verificare gli argomenti rispetto a null.
Objects.equals: confronto sicuro con null
Se scrivete semplicemente a.equals(b) e a risulta essere null, arriverà una NullPointerException. I controlli manuali sono ingombranti. Objects.equals(a, b) fa tutto al posto vostro:
- Se entrambi sono null — restituisce true.
- Se esattamente uno è null — restituisce false.
- Se entrambi non sono null — chiama il normale equals.
import java.util.Objects;
String a = null;
String b = "Java";
System.out.println(Objects.equals(a, b)); // false
String c = null;
System.out.println(Objects.equals(a, c)); // true
String d = "Java";
String e = "Java";
System.out.println(Objects.equals(d, e)); // true
Perché è comodo? Il codice è più corto, più pulito e protetto da NPE accidentali.
2. Objects.hash e hashCode: calcolo conciso dell'hash
Sovrascrivendo hashCode insieme a equals, è facile sbagliare, soprattutto quando i campi sono molti. Il codice manuale spesso appare ingombrante e fragile:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
Il metodo Objects.hash risolve il problema — breve, sicuro e con supporto di null:
import java.util.Objects;
public class Person {
private String name;
private int age;
// ... costruttore, getter, ecc.
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Punto importante: Objects.hash usa varargs e crea un array — in rari punti ad altissimo carico un hashCode manuale può essere più veloce. Per la maggior parte delle applicazioni la differenza è trascurabile.
3. Objects.compare: delegare il confronto al comparatore
A volte è necessario confrontare due oggetti tramite un Comparator preparato in anticipo. Invece di chiamare direttamente comparator.compare(a, b) si può usare:
int result = Objects.compare(a, b, comparator);
Questo metodo:
- Restituisce 0, se gli oggetti sono uguali.
- Considera null «minore» di qualsiasi oggetto non-null.
- Negli altri casi delega la logica al comparatore passato.
import java.util.Comparator;
import java.util.Objects;
class Person {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Person a = new Person("Anna");
Person b = new Person("Boris");
Comparator<Person> byName = Comparator.comparing(Person::getName);
System.out.println(Objects.compare(a, b, byName)); // <0, perché "Anna" < "Boris"
System.out.println(Objects.compare(a, null, byName)); // >0, perché a != null
System.out.println(Objects.compare(null, b, byName)); // <0, perché null < b
System.out.println(Objects.compare(null, null, byName)); // 0
}
}
4. Objects.requireNonNull: assicurazione contro errori «invisibili»
Se un metodo deve accettare solo valori non-null, verificate questo subito. Objects.requireNonNull lancerà una NullPointerException con il vostro messaggio:
public void setName(String name) {
this.name = Objects.requireNonNull(name, "Il nome non può essere null");
}
5. Esempio: implementazione corretta di equals, hashCode e compareTo con Objects
import java.util.Objects;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = Objects.requireNonNull(name, "Il nome non può essere null");
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // Confronto per riferimento
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
// Confronto sicuro con null
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Conciso e sicuro
}
@Override
public int compareTo(Person other) {
// Prima confrontiamo per nome, poi per età
int cmp = name.compareTo(other.name);
if (cmp != 0) return cmp;
return Integer.compare(age, other.age);
}
}
Ora è possibile archiviare gli oggetti in un HashSet, usarli come chiavi in una HashMap, confrontarli per uguaglianza e ordinare le liste (ad esempio tramite Collections.sort).
6. Applicazione in casi reali: riduzione del codice e meno errori
Esempio: elenco di utenti in un'applicazione
Grazie alla coppia corretta equals/hashCode la ricerca nelle collezioni funziona in modo prevedibile:
import java.util.ArrayList;
import java.util.List;
List<Person> users = new ArrayList<>();
users.add(new Person("Anna", 25));
users.add(new Person("Boris", 30));
Person search = new Person("Anna", 25);
System.out.println(users.contains(search)); // true
Esempio: lavoro con campi nullable
Se una classe ha campi che possono essere null (ad esempio il secondo nome/patronimico), usate Objects.equals e Objects.hash:
import java.util.Objects;
public class User {
private String firstName;
private String middleName; // Può essere null
private String lastName;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(firstName, user.firstName)
&& Objects.equals(middleName, user.middleName)
&& Objects.equals(lastName, user.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, middleName, lastName);
}
}
7. Tabella: metodi principali della classe Objects
| Metodo | Scopo | Esempio d'uso |
|---|---|---|
|
Confronto sicuro di due oggetti con gestione di null | |
|
Calcolo conciso dell'hash code su più campi | |
|
Confronto tramite comparatore, sicuro per null | |
|
Verifica di null, lancia NullPointerException | |
|
Verifica di null/non null (comodo con lo Stream API) | |
8. Errori tipici nell'uso dei metodi della classe Objects
Errore n. 1: avete dimenticato di usare Objects.equals per i campi nullable. Se confrontate i campi direttamente tramite equals, potete incappare in una NullPointerException. Usate Objects.equals(middleName, other.middleName).
Errore n. 2: non tutti i campi sono considerati in hashCode. I campi che partecipano a equals devono partecipare anche a hashCode, altrimenti il comportamento di HashSet/HashMap diventa imprevedibile.
Errore n. 3: hashCode manuale con un errore. Rispettare i coefficienti e i controlli su null non è banale. Objects.hash lo fa per voi; usatelo se non ci sono requisiti stringenti di prestazioni.
Errore n. 4: non usate Objects.requireNonNull dove è parte del contratto della classe. Se un campo non ammette null, verificate in costruttore/setter — l'errore si manifesterà subito, e non «nelle profondità» dello stack di chiamate.
Errore n. 5: usate Objects.hash per gli array. Per gli array serve Arrays.hashCode, e per gli array annidati — Arrays.deepHashCode; analogamente per confrontare i contenuti esistono Arrays.equals/Arrays.deepEquals.
GO TO FULL VERSION