Warum ist Long.valueOf (0) .equals (Integer.valueOf (0)) falsch?
Frage
Diese Frage wird von seltsam HashMap.put () Verhalten
Ich glaube, ich verstehe, warum Map<K,V>.put
eine K
nimmt aber Map<K,V>.get
nimmt einen Object
, scheint es nicht so zu tun brechen zu viel vorhandenen Code.
Jetzt kommen wir in ein sehr fehleranfällig Szenario:
java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five")
m.contains(5); // no complains from compiler, but returns false
Könnte dies nicht durch Rückgabe true, wenn der Wert Long
gelöst worden war int
Bereich withing und die Werte gleich sind?
Lösung
Hier ist die Quelle von Long.java
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
d. es braucht ein langer Typen gleich zu sein. Ich glaube, der entscheidende Unterschied zwischen:
long l = 42L
int i = 42;
l == i
und Ihr Beispiel oben ist, dass mit Primitiven kann eine implizite Erweiterung des int-Wert auftreten, jedoch mit Objekttypen gibt es keine Regeln für von Integer auf eine lange implizit umgewandelt wird.
Überprüfen Sie auch Java Puzzlers es viele Beispiele ähnlich wie diese hat.
Andere Tipps
Im Allgemeinen obwohl es nicht unbedingt im Vertrag gleich () sollten Objekte nicht betrachten sich auf ein anderes Objekt gleich, die nicht von der exakt gleichen Klasse ist (auch wenn es sich um eine Unterklasse) ist. Betrachten wir die symmetrische Eigenschaft -. Wenn a.equals (b) wahr ist, dann b.equals (a) muss auch wahr sein
Lassen Sie uns zwei Objekte haben, foo
der Klasse Super
und bar
der Klasse Sub
, die Super
erstreckt. Betrachten wir nun die Umsetzung der equals()
in Super, insbesondere wenn sie als foo.equals(bar)
genannt wird. Foo weiß nur, dass bar stark als Object
eingegeben wird, so einen genauen Vergleich, um es es ist eine Instanz von Super prüfen muss, und wenn nicht false zurück. Es ist, so dass dieser Teil ist in Ordnung. Er vergleicht nun alle Instanzfelder usw. (oder was auch immer die Ist-Vergleich Implementierung ist), und findet sie gleich. So weit, so gut.
jedoch durch den Vertrag kann es nur wahr zurück, wenn es weiß, dass bar.equals (foo) als auch zurückkehren wird wahr. Da bar jede Unterklasse von Super sein kann, ist es nicht klar, ob die Methode equals () außer Kraft gesetzt werden soll (und wahrscheinlich sein wird, wenn). So sicher sein, dass Ihre Implementierung korrekt ist, müssen Sie es symmetrisch schreiben und zu gewährleisten, dass die beiden Objekte der gleichen Klasse sind.
Grundsätzlicher Objekte verschiedener Klassen können nicht wirklich als gleich betrachtet werden. - da in diesem Fall nur einer von ihnen kann in eine HashSet<Sub>
eingesetzt werden, zum Beispiel
Ja, aber es kommt alles auf den Vergleichsalgorithmus und wie weit die Conversions zu nehmen. Zum Beispiel, was wollen Sie passieren, wenn Sie m.Contains("5")
versuchen? Oder wenn Sie geben es ein Array mit 5 als erstes Element? Einfach gesagt, scheint es verdrahtet zu werden „wenn die Typen unterschiedlich sind, sind die Tasten anders“.
Dann nehmen Sie eine Sammlung mit einem object
als Schlüssel. Was wollen Sie passieren, wenn Sie einen put
5L
, versuchen dann 5
, "5"
, zu bekommen ...? Was passiert, wenn Sie put
eine 5L
und 5
und "5"
und Sie wollen für eine 5F
überprüfen?
Da es sich um eine generische Auflistung ist (oder als Templat, oder was auch immer Sie es nennen wollen), müsste es zu überprüfen und einige spezielle Vergleich für bestimmte Werttypen zu tun. Wenn K int
dann prüfen, ob das Objekt übergeben wird, ist long
, short
, float
, double
, ..., dann konvertieren und vergleichen. Wenn K float
dann prüfen, ob das Objekt übergeben wird, ist ...
Sie erhalten den Punkt.
Eine weitere Implementierung hätte eine Ausnahme ausgelöst, wenn die Typen nicht übereinstimmen, aber, und ich wünschte, oft es getan hat.
Ihre Frage scheint auf den ersten Blick sinnvoll, aber es wäre ein Verstoß gegen die allgemeinen Konventionen für equals (), wenn nicht seinen Vertrag, sein true zurück für zwei verschiedene Typen.
Ein Teil der Design-Sprache Java war für Objekte niemals implizit auf andere Typen zu konvertieren, im Gegensatz zu C ++. Dies war Teil macht Java eine kleine, einfache Sprache. Ein angemessener Teil der C ++ 's Komplexität kommt von impliziten Konvertierungen und ihre Wechselwirkungen mit anderen Funktionen.
Auch hat Java eine scharfe und sichtbare Dichotomie zwischen Primitiven und Objekten. Dies unterscheidet sich von anderen Sprachen, in denen diese Differenz unter die Decke als Optimierungs verborgen ist. Das bedeutet, dass Sie können nicht erwarten, lange und Integer wie lange und int zu handeln.
In der Bibliothek Code geschrieben werden kann, diese Unterschiede zu verbergen, aber das kann, indem die Programmierumgebung weniger konsistent Schaden tatsächlich tun.
So Sie Code sollte sein ....
java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(5L)); // true
Sie vergessen, dass Java-Code wird Autoboxing, so dass der obige Code würde
equivelenet werdenjava.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(new Long(5))); // true
System.out.println(m.containsKey(new Long(5L))); // true
So ein Teil des Problems ist die Autoboxing. Der andere Teil ist, dass man verschiedene Typen haben wie andere Plakate angegeben haben.
Die anderen Antworten hinreichend erklären, warum es nicht, aber keiner von ihnen angesprochen werden, wie Code zu schreiben, weniger Fehler, um dieses Problem anfällig sind. Mit erinnert Typ-Casts (keine Compiler-Hilfe), Suffix Primitiven mit L usw. ist einfach nicht akzeptabel IMHO.
hinzufügenIch empfehle, die GNU Grube Bibliothek von Sammlungen zu verwenden, wenn Sie Primitiven haben (und in vielen anderen Fällen). Zum Beispiel gibt es eine TLongLongHashMap, die Dinge interally als primitive longs speichert. Als Ergebnis beenden Sie nie mit Boxen / Unboxing-up, und nie mit unerwartetem Verhalten am Ende:
TLongLongHashMap map = new TLongLongHashMap();
map.put(1L, 45L);
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler
int x = map.get(1); // Helpful compiler error. x is not a long
int x = (int)map.get(1); // OK. cast reassures compiler that you know
long x = map.get(1); // Better.
und so weiter. Es besteht keine Notwendigkeit, die Art Recht zu bekommen, und der Compiler gibt einen Fehler (die Sie korrigieren oder überschreiben können), wenn Sie etwas tun, dumm (versuchen, eine lange in einem int zu speichern).
Die Regeln des Auto-Casting bedeuten, dass Vergleiche richtig so gut funktionieren:
if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well
Als Bonus die Speicher-Overhead und die Laufzeitleistung ist viel besser.