CodeGym /Corsi /JAVA 25 SELF /Classe Objects: metodi equals, hashCode, hash

Classe Objects: metodi equals, hashCode, hash

JAVA 25 SELF
Livello 29 , Lezione 1
Disponibile

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
Objects.equals(a, b)
Confronto sicuro di due oggetti con gestione di null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Calcolo conciso dell'hash code su più campi
Objects.hash(name, age)
Objects.compare(a, b, comparator)
Confronto tramite comparatore, sicuro per null
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Verifica di null, lancia NullPointerException
Objects.requireNonNull(name, "Il nome non può essere null")
Objects.isNull(obj) / Objects.nonNull(obj)
Verifica di null/non null (comodo con lo Stream API)
list.stream().filter(Objects::nonNull)

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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION