PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java contains(), equals(), hashCode()


Eggcake
2012-12-18, 21:04:52
So...bin langsam am durchdrehen, ist aber auch schon spät :ugly:

Ich habe bei meiner eigenen Klasse StrokeSegment sowohl die equals() als auch die hashCode()-Methode überschrieben. Diese Klasse wrappt sozusagen eine andere Klasse und beinhaltet ansonsten keine Membervariablen, daher prüfe ich in der equals Methode (neben == und instanceof-checks) quasi nur ob die gewrappte Klasse dieselbe ist. Dort wiederum prüfe ich zwei Integers in der equals Methode (sind zwei IDs).
Nun, ich bin mir zu 99% sicher, dass diese beiden Methoden funktionieren. Ebenfalls klappt der hashCode davon.

Zur Frage (damit ich die Suche definitiv einschränken kann :ugly: ) :
Wenn ich jetzt also eine HashMap<StrokeSegment, Stroke> habe und mit der containsKey Methode prüfen will, ob das übergebene StrokeSegment nun in dieser HashMap als Key enthalten ist, dann sollte das ohne Probleme funktionieren, sofern die equals und die hashCode Methode von StrokeSegment korrekt sind. Sehe ich das richtig?

Exxtreme
2012-12-18, 21:11:07
Vorsicht bei Generics mit equals() und instanceof(). Der Punkt ist, Generics sind in Java keine "echten" Generics. Intern wird das Ding auf Object umgewandelt.

Eggcake
2012-12-18, 21:13:41
Meine Equals Methode der "Basisklasse" sieht so aus:

@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof Edge)) {
return false;
}

Edge e = (Edge)obj;
return EqualsUtil.areEqual(id, e.id) &&
EqualsUtil.areEqual(feature.getID(), e.feature.getID());
}

@Override
public int hashCode() {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash(result, id);
result = HashCodeUtil.hash(result, feature.getID());
return result;
}

Die Equals/Hashcode methode der Wrapperklasse:
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof StrokeSegment)) {
return false;
}

StrokeSegment other = (StrokeSegment) obj;
return EqualsUtil.areEqual(edge, other.getEdge());
}

@Override
public int hashCode() {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash(result, edge);
return result;
}


Dabei benutze ich die Utils von dieser Seite:
http://www.javapractices.com/topic/TopicAction.do?Id=17

Exxtreme
2012-12-18, 21:40:46
Mag ja alles ohne Generics funktionieren. Nur wenn du das Ding in eine HashMap reintust dann ist es innerhalb der HashMap kein StrokeSegment mehr sondern ein Object. Und dann funktioniert auch kein equals() oder hashCode(). :)

Eggcake
2012-12-18, 21:47:07
Mh willst du mir jetzt sagen ich muss durch die ganze HashMap durchiterieren und prüfen ob mein Segment.equals(otherSegment) und der Ganze Vorteil einer HashMap geht flöten? Kann ich mir jetzt nicht ganz vorstellen...oder verstehe ich dich falsch?
Das würde ja auch bedeuten dass alle contains-Methoden mit eigenen Klassen nicht funktionieren (da diese equals verwenden)? :>

Exxtreme
2012-12-18, 21:59:29
Ich will sagen, Java führt bei Generics eine Typlöschung durch und konvertiert intern alles nach Object. Damit sind Methoden wie equals() nicht machbar weil zur Laufzeit der ursprüngliche Typ nicht mehr bekannt ist. :freak: Ja, das schränkt die Verwendung von Generics schon ein. Bloß weil Sun paar Schreihälsen nachgegeben hat.

Gast
2012-12-19, 01:19:31
wow, Extrem viel Text und Posts ohne viel Sinn.
Generics haben erstmal gar nichts mit seinen Equals und hashCode Methoden und seinem Problem mit der HashMap zu tun.

Topic:
Die Attribute, welche du in den Methoden verwendest, dürfen sich nicht ändern, nach dem du die Objekte in die Map packst und diese verwendest.
Ansonsten sehe ich grad keinen Fehler. Komplettes Code Beispiel wäre hilfreicher.

Monger
2012-12-19, 01:34:23
Also... wenn man in einer Oberklasse Equals bzw. HashCode überschrieben hat, ist es eigentlich eh verboten, diese in einer Unterklasse nochmals zu überschreiben.

Man verletzt damit Symmetriegesetze. Man nehme zwei Objekte, A und B, letzteres leitet vom ersteren ab.
Wenn A = B, dann muss auch gelten: B = A. Analog dazu: Wenn B=A, dann Hash(B) = Hash(A). Wenn A=B und B=A unterschiedliche Hashcodes rauswerfen, dann hast du plötzlich eine asymmetrische Operation, und dann gibts Kuddelmuddel in den Hash Buckets.

Oder anschaulicher gesprochen: du kannst nur einen natürlichen Vergleich zwischen zwei Objekten herstellen wenn sie eine gemeinsame Wurzel haben. Du kannst Äpfel und Birnen nur miteinander vergleichen, wenn du nach einer gemeinsamen Eigenschaft fragst (meinetwegen Form und Farbe. Beide haben diese Eigenschaften, legen sie nur unterschiedlich aus).

PatkIllA
2012-12-19, 07:14:17
Wirklich gleich können die doch eigentlich eh nur sein wenn sie den gleichen Typ haben. Damit kann man das in Vererbungsstrukturen gar nicht vernünftig benutzen. Was zu .NETs EqualityComparer Vergleichbares gibt es in Java immer noch nicht? Ich benutze das regelmäßig.

Monger
2012-12-19, 10:20:42
Mag ja alles ohne Generics funktionieren. Nur wenn du das Ding in eine HashMap reintust dann ist es innerhalb der HashMap kein StrokeSegment mehr sondern ein Object. Und dann funktioniert auch kein equals() oder hashCode(). :)
Erm... meine Java Zeiten sind lange hinter mir, aber ich glaube du bringst da was durcheinander.

Der Generic Typ an sich existiert nur zur Compilezeit, kann also anders als in .NET nicht zur Laufzeit ausgewertet werden. Wenn du also (pseudocodemäßig) sowas hast:


MeinTyp<T>
{
void doSth(){
if T==MeinTyp then ...
}
}

Dann geht das nicht. In Java sind Generics nur syntaktischer Zucker, damit man nicht bei jedem Zugriff manuell casten muss.

Aber darum gehts ja in diesem Fall gar nicht. Das Objekt selbst ist ja gar nicht generisch, und weiß ja gar nicht in was für einer Liste es drin steckt. Java wandelt auch keine Objekte um (jetzt mal von Autoboxing abgesehen). Wenn du Hunde und Katzen in eine Liste von Tieren steckst, dann ändert sich an den Hunden und Katzen gar nichts.

Eggcake
2012-12-19, 10:43:24
Also ich habe einen der Fehler nun entdeckt, war, wie so oft, an einem völlig anderen Ort. Aber kennt sicher jeder :> Trotzdem tut es noch nicht ganz wie es sollte.

wow, Extrem viel Text und Posts ohne viel Sinn.
Generics haben erstmal gar nichts mit seinen Equals und hashCode Methoden und seinem Problem mit der HashMap zu tun.

Topic:
Die Attribute, welche du in den Methoden verwendest, dürfen sich nicht ändern, nach dem du die Objekte in die Map packst und diese verwendest.
Ansonsten sehe ich grad keinen Fehler. Komplettes Code Beispiel wäre hilfreicher.
Es könnte tatsächlich an dem liegen, da ich (zwar in einem leicht anderen Teil) das Objekt nochmals verändere, schau's mir mal an. Komplettes CodeBsp. will ich euch nicht unbedingt antun :ugly: Weiss aber dass es so etwas mühsam ist.

Also... wenn man in einer Oberklasse Equals bzw. HashCode überschrieben hat, ist es eigentlich eh verboten, diese in einer Unterklasse nochmals zu überschreiben.

Man verletzt damit Symmetriegesetze. Man nehme zwei Objekte, A und B, letzteres leitet vom ersteren ab.
Wenn A = B, dann muss auch gelten: B = A. Analog dazu: Wenn B=A, dann Hash(B) = Hash(A). Wenn A=B und B=A unterschiedliche Hashcodes rauswerfen, dann hast du plötzlich eine asymmetrische Operation, und dann gibts Kuddelmuddel in den Hash Buckets.

Oder anschaulicher gesprochen: du kannst nur einen natürlichen Vergleich zwischen zwei Objekten herstellen wenn sie eine gemeinsame Wurzel haben. Du kannst Äpfel und Birnen nur miteinander vergleichen, wenn du nach einer gemeinsamen Eigenschaft fragst (meinetwegen Form und Farbe. Beide haben diese Eigenschaften, legen sie nur unterschiedlich aus).

Ich erbe da garnix :) Es ist nur eine Wrapperklasse. Hat also im Prinzip eine has-a Beziehung, keine is-a Beziehung. Ich glaube nun mittlerweile relativ fest daran, dass es gar nicht an den equals/hashCode-Methoden liegt. Ich habe:

Edge
StrokeSegment [Edge]
Stroke [HashSet<StrokeSegments>]

D.h. der HashCode/Equals von Edge wird aus einer ID (int) und FeatureID (int) gewonnen. HashCode von StrokeSegment aus der Edge, welche sie beinhaltet. Stroke aus dem Set aus Segments (d.h. im Prinzip aus dem Set der Edges). Das sollte so [I]grundsätzlich schon in Ordnung sein, oder nicht?


Edit: Ich glaube der Gast lag genau richtig, das hatte ich völlig übersehen. Im Nachhinein auch völlig logisch...
Dankeschön!

Ectoplasma
2012-12-19, 12:23:17
@Extreme, ne ne, es ist nicht so, wie du sagst. In Java sind Objekte nur Referenzen und bleiben daher vollständig erhalten. In C++ z.B. kann das anders sein! Dort kann man mit Referenzen und Werten arbeiten. Hier kann es dir durchaus passieren, dass Teile eines Objektes zerstört werden, wenn es by value an einen Parameter übergeben wird, welcher nicht exakt dem Typ des Objektes entspricht. Übergibt man hingegen ein Zeiger oder eine Referenz, ist das Verhalten wie in Java. Dann passiert mit dem Objekt gar nichts. Es bleibt vollständig erhalten.

Eggcake
2012-12-19, 15:47:05
Joa schlussendlich hatte alles damit zu tun, dass ich keine neuen Objekte erstellt habe wenn ich diese verändert hatte und somit hashCode() nicht mehr den richtigen Hash ausgab...habe ich mich wohl zu fest an ArrayLists gewöhnt :)