1. Einführung in die Klasse Objects
Los geht’s! Wenn Sie es leid sind, Werte von Hand auf null zu prüfen und für jedes Feld den Hash zu berechnen, hilft der Utility-Typ java.util.Objects. Seine Aufgabe ist es, die Arbeit mit Objekten einfacher, prägnanter und sicherer zu machen.
Er ist tatsächlich ein „Schweizer Taschenmesser“: Die Klasse kann Objekte sicher auf Gleichheit vergleichen (ohne Risiko einer NullPointerException), Hashcodes bequem berechnen, über einen Comparator vergleichen und Argumente auf null prüfen.
Objects.equals: sicheres Vergleichen unter Berücksichtigung von null
Wenn man einfach a.equals(b) schreibt und a ist null, gibt es eine NullPointerException. Manuelle Prüfungen sind umständlich. Objects.equals(a, b) erledigt alles für Sie:
- Wenn beide null sind – wird true zurückgegeben.
- Wenn genau eines null ist – wird false zurückgegeben.
- Wenn beide nicht null sind – wird das normale equals aufgerufen.
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
Warum ist das praktisch? Der Code ist kürzer, sauberer und vor zufälligen NPEs geschützt.
2. Objects.hash und hashCode: prägnante Hash-Berechnung
Beim Überschreiben von hashCode zusammen mit equals macht man leicht Fehler, besonders wenn es viele Felder gibt. Manueller Code sieht oft umständlich aus und ist fehleranfällig:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
Die Methode Objects.hash löst das Problem – kurz, sicher und mit Unterstützung für null:
import java.util.Objects;
public class Person {
private String name;
private int age;
// ... Konstruktor, Getter usw.
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Wichtiger Punkt: Objects.hash verwendet Varargs und erstellt ein Array – in seltenen leistungskritischen Abschnitten kann ein manueller hashCode schneller sein. Für die meisten Anwendungen ist der Unterschied unerheblich.
3. Objects.compare: den Vergleich an den Comparator delegieren
Manchmal muss man zwei Objekte über einen vorbereiteten Comparator vergleichen. Statt comparator.compare(a, b) direkt aufzurufen, kann man verwenden:
int result = Objects.compare(a, b, comparator);
Diese Methode:
- Gibt 0 zurück, wenn die Objekte gleich sind.
- Betrachtet null als „kleiner“ als jedes nicht-null-Objekt.
- In allen anderen Fällen delegiert sie die Logik an den übergebenen Comparator.
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, da "Anna" < "Boris"
System.out.println(Objects.compare(a, null, byName)); // >0, da a != null ist
System.out.println(Objects.compare(null, b, byName)); // <0, da null < b
System.out.println(Objects.compare(null, null, byName)); // 0
}
}
4. Objects.requireNonNull: Schutz vor „unsichtbaren“ Fehlern
Wenn eine Methode nur nicht-null-Werte akzeptieren darf, prüfen Sie das sofort. Objects.requireNonNull wirft eine NullPointerException mit Ihrer Nachricht:
public void setName(String name) {
this.name = Objects.requireNonNull(name, "Name darf nicht null sein");
}
5. Beispiel: korrekte Implementierung von equals, hashCode und compareTo mit 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, "Name darf nicht null sein");
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // Vergleich per Referenz
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
// Sicherer Vergleich mit null
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Kompakt und sicher
}
@Override
public int compareTo(Person other) {
// Zuerst nach Namen, dann nach Alter vergleichen
int cmp = name.compareTo(other.name);
if (cmp != 0) return cmp;
return Integer.compare(age, other.age);
}
}
Nun kann man Objekte in einem HashSet speichern, sie als Schlüssel in einer HashMap verwenden, auf Gleichheit vergleichen und Listen sortieren (zum Beispiel über Collections.sort).
6. Einsatz in realen Aufgaben: weniger Code und weniger Fehler
Beispiel: Benutzerliste in einer Anwendung
Dank eines korrekten Paars aus equals/hashCode funktioniert die Suche in Collections vorhersagbar:
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
Beispiel: Arbeit mit Feldern, die null sein können
Wenn eine Klasse Felder hat, die null sein können (zum Beispiel der zweite Vorname), verwenden Sie Objects.equals und Objects.hash:
import java.util.Objects;
public class User {
private String firstName;
private String middleName; // Kann null sein
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. Tabelle: zentrale Methoden der Klasse Objects
| Methode | Zweck | Beispiel |
|---|---|---|
|
Sicherer Vergleich zweier Objekte unter Berücksichtigung von null | |
|
Prägnante Hashcode-Berechnung über mehrere Felder | |
|
Vergleich über einen Comparator, null-sicher | |
|
Null-Prüfung, wirft NullPointerException | |
|
Prüfung auf null/nicht null (praktisch im Stream-API) | |
8. Typische Fehler bei der Verwendung der Methoden der Klasse Objects
Fehler Nr. 1: vergessen, Objects.equals für nullbare Felder zu verwenden. Wenn man Felder direkt über equals vergleicht, kann eine NullPointerException auftreten. Verwenden Sie Objects.equals(middleName, other.middleName).
Fehler Nr. 2: nicht alle Felder sind in hashCode berücksichtigt. Felder, die in equals berücksichtigt werden, müssen auch in hashCode einfließen, sonst wird das Verhalten von HashSet/HashMap unvorhersehbar.
Fehler Nr. 3: fehlerhafter manueller hashCode. Faktoren und Null-Prüfungen korrekt einzuhalten, ist nicht trivial. Objects.hash übernimmt das für Sie; verwenden Sie es, sofern es keine strikten Performance-Anforderungen gibt.
Fehler Nr. 4: Objects.requireNonNull nicht dort einsetzen, wo es der Klassenvertrag verlangt. Wenn ein Feld kein null zulässt, prüfen Sie das im Konstruktor/Setter – der Fehler zeigt sich sofort und nicht irgendwo tief im Aufruf-Stack.
Fehler Nr. 5: Objects.hash für Arrays verwenden. Für Arrays ist Arrays.hashCode geeignet, für verschachtelte Arrays Arrays.deepHashCode; analog zum Vergleich des Inhalts gibt es Arrays.equals/Arrays.deepEquals.
GO TO FULL VERSION