tags:

views:

962

answers:

6

This questions is prompted by http://stackoverflow.com/questions/444638/strange-hashmap-put-behaviour#444757

I think I understand why Map<K,V>.put takes a K but Map<K,V>.get takes an Object, it seems not doing so will break too much existing code.

Now we get into a very error-prone scenario:

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

Couldn't this have been solved by returning true if the Long value was withing int range and the values are equal?

+4  A: 

Yes, but it all comes down to the comparing algorithm and how far to take the conversions. For example, what do you want to happen when you try m.Contains("5")? Or if you pass it an array with 5 as the first element? Simply speaking, it appears to be wired up "if the types are different, the keys are different".

Then take a collection with an object as the key. What do you want to happen if you put a 5L, then try to get 5, "5", ...? What if you put a 5L and a 5 and a "5" and you want to check for a 5F?

Since it's a generic collection (or templated, or whatever you wish to call it), it would have to check and do some special comparing for certain value types. If K is int then check if the object passed is long, short, float, double, ..., then convert and compare. If K is float then check if the object passed is ...

You get the point.

Another implementation could have been to throw an exception if the types didn't match, however, and I often wish it did.

lc
I don't get the point. Surely there is a difference between a Long being equal to an Inter and a String being equal to an Integer? There is implicit conversion for example.Exception might have helped to catch this case, yes.
Hemal Pandya
+9  A: 

Here is the source from Long.java

public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

I.e. it needs to be a Long type to be equal. I think the key difference between:

long l = 42L
int i = 42;
l == i

and your example above is that with primitives an implicit widening of the int value can occur, however with object types there are no rules for implicitly converting from Integer to a Long.

Also check out Java Puzzlers, it has a lot of examples similar to this.

Michael Barker
Perhaps I was not clear. I know why it happens as in the source code that makes it happen that way, I had read the code before posting. My question was why was it decided that it should be this way?
Hemal Pandya
http://stackoverflow.com/questions/445990/why-is-long-valueof0-equalsinteger-valueof0-false#446911 explains it more properly. Since comparing Long to Integer for equality can violate symmetry.
Spencer K
+4  A: 

Your question seems reasonable on its face, but it would be a violation of the general conventions for equals(), if not its contract, to return true for two different types.

Phil
A: 

Part of the Java language design was for Objects to never implicitly convert to other types, unlike C++. This was part of making Java a small, simple language. A reasonable portion of C++'s complexity comes from implicit conversions and their interactions with other features.

Also, Java has a sharp and visible dichotomy between primitives and objects. This is different from other languages where this difference is hidden under the covers as an optimization. This means that you can't expect Long and Integer to act like long and int.

Library code can be written to hide these differences, but that can actually do harm by making the programming environment less consistent.

Darron
+4  A: 

Generally speaking, although it is not strictly expressed in the contract for equals(), objects should not consider themselves equal to another object that is not of the exact same class (even if it is a subclass). Consider the symmetric property - if a.equals(b) is true, then b.equals(a) must also be true.

Let's have two objects, foo of class Super, and bar of class Sub, which extends Super. Now consider the implementation of equals() in Super, specifically when it's called as foo.equals(bar). Foo only knows that bar is strongly typed as an Object, so to get an accurate comparison it needs to check it's an instance of Super and if not return false. It is, so this part is fine. It now compares all the instance fields, etc. (or whatever the actual comparison implementation is), and finds them equal. So far, so good.

However, by the contract it can only return true if it know that bar.equals(foo) is going to return true as well. Since bar can be any subclass of Super, it's not clear whether the equals() method is going to be overridden (and if probably will be). Thus to be sure that your implementation is correct, you need to write it symmetrically and ensure that the two objects are the same class.

More fundamentally, objects of different classes cannot really be considered equal - since in this case, only one of them can be inserted into a HashSet<Sub>, for example.

Andrzej Doyle
Long and Integer are different class types, and thus comparing them using equals will always return false.
Steve Kuo
For Long l = 0 and Integer i = 0 if l.equals(i) can be true then i.equals(l) can also be true. The symmetry is preserved.
Hemal Pandya
@dtsazza, Steve Kuo: I strongly disagree. In fact, in the Java library there are places where objects of different classes are considered to be equal. For example, the List interface specifies that lists of different classes be equal if their contents are equal, regardless of their classes; so an ArrayList can be .equals() to a LinkedList.
newacct
A: 

So you code should be....

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

You are forgetting that java is autoboxing your code, so the above code would be equivelenet to

java.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 a part of your problem is the autoboxing. The other part is that you have different types as other posters have stated.

Milhous
I do understand autoboxing. I suspect you haven't got my point, which is that it is easy to forget the L when calling containsKey.
Hemal Pandya