hashCode()/equals() mit Lambdas

Korrekte hashCode() und equals() Implementierungen sind sehr wichtig, aber es macht keinen Spaß, sie zu schreiben. Glücklicherweise generieren moderne IDEs die Methoden automatisch, also muss man nicht zu viele Gedanken daran verschwenden. Leider hat dieser Ansatz zwei Nachteile:

  1. in eigentlich sehr einfachen Klassen (e.g. DTOs or JPA entities) entstehen mindestens 20 Zeilen komplex wirkenden Codes, der zum Einen die Lesbarkeit reduziert und bei statischer Code Analyse negativ auffallen kann
  2. jede IDE generiert die Implementierung etwas anders, daher unterscheidet sich der Code z.B. Eclipse und IntelliJ

Lambdas als Alternative

Eine Alternative, um das Generieren der hashCode() und equals() Methoden zu vereinfachen, sind die mit Java 8 eingeführten Lambdas. Ich habe mir also folgenden Ansatz überlegt:

public class HashCodeEqualsBuilder {

    public static  int buildHashCode(T t, Function<? super T, ?>... functions) {
        final int prime = 31;
        int result = 1;
        for (Function<? super T, ?> f : functions) {
            Object o = f.apply(t);
            result = prime * result + ((o == null) ? 0 : o.hashCode());
        }
        return result;
    }

    public static  boolean isEqual(T t, Object obj, Function<? super T, ?>... functions) {
        if (t == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (t.getClass() != obj.getClass()) {
            return false;
        }
        T other = (T) obj;
        for (Function<? super T, ?> f : functions) {
            Object own = f.apply(t);
            Object foreign = f.apply(other);
            if (own == null) {
                if (foreign != null) {
                    return false;
                }
            } else if (!own.equals(foreign)) {
                return false;
            }
        }
        return true;
    }
}

Diese Hilfklasse kann wie folgt bei der Implementierung einer Klasse (z.B. ein DTO) verwendet werden:

@Override
public int hashCode() {
    return HashCodeEqualsBuilder.buildHashCode(this, MyClass::getA, MyClass::getB);
}

@Override
public boolean equals(Object obj) {
    return HashCodeEqualsBuilder.isEqual(this, obj, MyClass::getA, MyClass::getB);
}

Basierend auf einigen Microbenchmarks, unterscheidet sich dieser Ansatz nicht messbar zu einer autogenerierten Implementation (z.B. von Eclipse). Ich werde zukünftig diesen Ansatz verwenden und kann ihn auch nur wärmstens empfehlen::

  1. es werden keine unnötigen Code-Zeilen zum Projekt hinzugefügt, die evtl. noch negativ in der Code Coverage durch Unit Tests bewertet werden
  2. eine Änderung an der Implementierung (falls je notwendig) muss an exakt einer Stelle durchgeführt werden

Copyright © christophbrill.de, 2002-2017.