hash und equals für DTO-Objekte...

RoCMe

Erfahrenes Mitglied
Hallo,

Bei uns im Team ist eine heisse Diskussion um das Überschreiben der oben genannten Funktionen entbrannt.

Speizell geht es momentan um Data-Transfer-Objekte (DTOs). Jede solche Klasse hat eine ID (Long) und dann - je nach fachlicher Verwendung - unterschiedliche weitere Attribute.

Technisch gesehen könnte man zwei Instanzen der gleichen Klasse als "äquivalent" betrachten, wenn sie die gleiche ID haben. Deswegen kam die Idee auf, hashCode und equals Methode über die entsprechenden Methoden der ID zu implementieren.
Dabei gibt es aber zwei Probleme:

1. Sind sie dann fachlich auch gleich? Beispiel: Änderungen durch einen Nutzer werden in ein Dto gespeichert. Ist das geänderte DTO dann noch äquivalent zur ursprünglichen Variante?
2. "Neue", noch nicht persistierte Objekte haben noch keine Id, diese wird erst beim Persistieren durch Hibernate (bzw. die Datenbank-Sequenz) gesetzt. Wie behandelt man also solche Objekte?

Ich persönlich sehe bei 1. kein wirkliches Problem - dieser Fall tritt momentan so nicht auf, und wenn er auftreten sollte, ist auch nicht mehr Aufwand notwendig als bisher - man müsste manuell auf Äquivalenz prüfen.

Zu 2. ist mir bislang allerdings noch keine einleuchtende Strategie eingefallen.

Ich hoffe an dieser Stelle erst mal auf eine Anregungen, Links, Ideen, etc., die ich hier noch in die Runde einbringen kann...

Mich nervt es z.B. tierisch, nicht einfach die contains Methode diverser Collections nutzen zu können. Stattdessen iteriere ich ständig über die Collection und prüfe jeweils, ob die Id mit der gesuchten übereinstimmt, oder erzeuge zunächst ein HashSet aller Ids in der Collection, und führe dann ein contains mit der gesuchten Id durch...
Das muss doch auch einfacher gehen?!

Gruß,
RoCMe
 
zu 1.

Ich würde zunächst einmal eclipse dazu benutzen, um die beiden Methoden zu erzeugen.

Die Gleichheit von Objekten ist imo allerdings vielmehr als nur der technische Schlüsselvergleich.

Wie kannst Du feststellen, dass zwei Instanzen Deiner Klasse mit der gleichen ID und unterschiedlichen Attributen ungleich sind? Und wie soll z.B. ein OR-Mapper das machen?

zu 2.
Du kannst die hashCode-Methode Deiner Objekte (fachlich) sauber implementieren und mit contains suchen ;-

Zum Thema Null-Check siehe 1.

Ein technisches Key-Surrogat ist KEIN Ersatz für den fachlichen Identifikator.

just my 2 cents ;-)
 
Grundsätzlich erst einmal immer beide überschreiben, niemals eins allein. Sonst kommt es unter Umständen zu seltsamen Effekten bei Collections.

Zudem haben beide Methoden einen Contract, den deine Implementierung erfüllen muß, denn die Collection-Klassen verlassen sich darauf. Der steht in der Doku zu den Funktionen.
Daher ist es auch eine gute Idee, die Generierung maschinell erledigen zu lassen, zum beispiel von Eclipse, wie mein Vorposter schon erwähnte.

Es sollten alle relevanten Felder für die die beiden Methoden ausgewählt werden. Du merkst ja selbst, daß dein Ansatz "Sind gleich, wenn die ID gleich ist" schon nicht funktioniert, wenn du ein Objekt neu erstellst. Ich würde vermutlich alle Felder ausser der ID heranziehen. Damit ich auch mit "contains" prüfen kann, ob ich den gerade erstellten Datensatz schon in der Datenbank habe. :)

Warum möchtest du eigentlich nur auf ID prüfen? Wenn ich das richtig verstanden habe magst du ein "Query by Example" durchführen. Sprich, bei folgender Klasse

Code:
class Person {
	private Long id;
	private String name;
...
}
magst du schreiben können:
Code:
Person example = new Person();
example.setId(1L);
Collection<Person> persons = getPersons();
if (persons.contains(example)) { ... }

Das kannst du auch durch eine Klasse ausmodellieren:

Code:
class PersonQuery {

	private Long personId = null;
	private String personName = null;

	public void setId(Long id) {personId = id;}
	public void setName(String name) {personName = name;}

	public boolean containedIn(final Collection<Person> persons) {
		Collection<Person> tmp = new HashSet<Person>(persons); //Internal copy
		if (personId != null) {
			Iterator<Person> it = tmp.iterator();
			while(it.hasNext()) {
				if (!it.next().getId().equals(personId)) {
					it.remove();
				}
			}
		}
		if (personName != null) {
			Iterator<Person> it = tmp.iterator();
			while(it.hasNext()) {
				if (!it.next().getName().equals(personName)) {
					it.remove();
				}
			}
		}
	return tmp.size() > 0;
	}
}

Das kannst du dann so benutzen:
Code:
PersonQuery example = new PersonQuery();
example.setId(1L); 
if (example.containedIn(getPersons())) {... }

Das ist auch sauberer, weil ein solches Query-Objekt nicht in die Datenbank gelangen kann, wenn die "schreibe in die Datenbank"-Operation nicht für sowas ausgelegt ist.

Also, zusammenfassend: ich würde mich nicht nur auf die ID des Objektes beschränken, denn der Status des Objekts hängt nicht nur von der ID ab. Ich würde ein Query-by-Example in eine eigene Klasse ausgliedern wie oben beschrieben.

Übrigens, für HashCode und Equals ist das Buch von Joshua Bloch - "Effective Java" auch zu empfehlen. Sollte man als Java-programmierer mal gelesen haben (finde ich)

Thinker
 
Zuletzt bearbeitet:
Zurück