Map question

L

laredotornado

Hi,

I'm using Java 6. The Javadocs suggest that Map uses containsKey, and
hence an Object's equals method to determine if the object is already
a key in the map. However, I'm noticing that when I define this
equals method on an object I'm inserting as a key in my Map (a
HashMap):

public boolean equals(Object obj) {
SearchResultHotels hotel = (SearchResultHotels) obj;
boolean ret = obj != null && ((SearchResultHotels) obj).getId() ==
getId();
log.debug("\t comparing " + hotel.getName() + " to " + getName() +
":" + ret);
return ret;
}

I never see my log statement printed out when I call "map.put". What
am I missing?

Thanks, - Dave
 
T

Tom Anderson

I'm using Java 6. The Javadocs suggest that Map uses containsKey,

Map can't use anything, because it's an interface. I will assume you are
talking about HashMap.
and hence an Object's equals method to determine if the object is
already a key in the map. However, I'm noticing that when I define this
equals method on an object I'm inserting as a key in my Map (a HashMap):

public boolean equals(Object obj) {
SearchResultHotels hotel = (SearchResultHotels) obj;

An aside: if someone passes in an object which is not a
SearchResultHotels, you need to return false; this will throw a
ClassCastException, which is wrong. A common opening to equals
implementations is:

if (obj == this) return true;
if ((obj == null) || !(obj instanceof SearchResultHotels)) return false;
SearchResultHotels hotel = (SearchResultHotels)obj;

Or code to that effect.
boolean ret = obj != null && ((SearchResultHotels) obj).getId() == getId();
log.debug("\t comparing " + hotel.getName() + " to " + getName() + ":" + ret);
return ret;
}

I never see my log statement printed out when I call "map.put". What
am I missing?

You don't show anywhere near enough code to answer that. Post the code
that makes the map and does the put.

Assuming you're talking about a situation like:

Map<SearchResultHotels, Object> m = new HashMap<SearchResultHotels, Object>();
SearchResultHotels hotel = somehowCreateSearchResultHotels();
m.put(hotel, "foo");
m.put(hotel, "bar");

Then i have a feeling HashMap checks for object identity (with ==) before
calling equals, as a defensive optimisation in case the key objects have
equals methods that do not efficiently detect identity.

Try doing a put with a different but equal key:

SearchResultHotels hotel = somehowCreateSearchResultHotels();
m.put(hotel, "foo");
SearchResultHotels equalHotel = somehowCreateSearchResultHotels();
m.put(equalHotel, "bar");

tom
 
M

Michal Kleczek

laredotornado said:
Hi,

I'm using Java 6. The Javadocs suggest that Map uses containsKey, and
hence an Object's equals method to determine if the object is already
a key in the map. However, I'm noticing that when I define this
equals method on an object I'm inserting as a key in my Map (a
HashMap):

public boolean equals(Object obj) {
SearchResultHotels hotel = (SearchResultHotels) obj;
boolean ret = obj != null && ((SearchResultHotels) obj).getId() ==
getId();
log.debug("\t comparing " + hotel.getName() + " to " + getName() +
":" + ret);
return ret;
}

I never see my log statement printed out when I call "map.put". What
am I missing?

Probably int hashCode() (if a hashing map implementation is used). Or
apropriate Comparable/Comparator implementation if you use a tree based map.
 
E

Eric Sosman

Hi,

I'm using Java 6. The Javadocs suggest that Map uses containsKey, and
hence an Object's equals method to determine if the object is already
a key in the map.

Where do you find this suggestion? The Javadoc says that the
Map interface *requires* that any class implementing it *provide*
a containsKey() method, but doesn't say anything about how the class
uses the method internally, or even whether it's used internally.
However, I'm noticing that when I define this
equals method on an object I'm inserting as a key in my Map (a
HashMap):

public boolean equals(Object obj) {
SearchResultHotels hotel = (SearchResultHotels) obj;
boolean ret = obj != null&& ((SearchResultHotels) obj).getId() ==
getId();
log.debug("\t comparing " + hotel.getName() + " to " + getName() +
":" + ret);
return ret;
}

I never see my log statement printed out when I call "map.put". What
am I missing?

One thing you're missing -- it's a side-issue to your question,
but it's important anyhow -- is that this equals() method will throw
ClassCastException if `obj' is a String or a JButton or anything other
than a SearchResultHotels. Throwing CCE from equals() is a no-no; the
method should simply return `false' meaning "The HoundOfTheBaskervilles
object you handed me is unequal to `this'."

To the question at hand, consider how a HashMap works: It computes
the hashCode() of the key, and using that value it chooses a "bucket."
If the bucket already holds a key/value pair with the same key, the
new value replaces the old. If the bucket holds no pair with the given
key, the new pair is added. And ...

... (drum roll, please) ...

.... if the bucket is *empty*, how many times must HashMap call equals()
to determine that the new key is not a duplicate?
 
L

laredotornado

Probably int hashCode() (if a hashing map implementation is used). Or
apropriate Comparable/Comparator implementation if you use a tree based map.

When I defined a hashCode method, then everything was fine -- the
HashMap object did not allow me to insert two keys that I considered
the same.

Per the other points about ClassCastExceptions in the equals method, I
have implemented your suggestions.

Thanks, -
 
L

Lew

laredotornado said:
Michal said:
Probably int hashCode() (if a hashing map implementation is used). Or
appropriate Comparable/Comparator implementation if you use a tree based map.

Don't quote sigs.
When I defined a hashCode method, then everything was fine -- the
HashMap object did not allow me to insert two keys that I considered
the same.

Per the other points about ClassCastExceptions in the equals method, I
have implemented your suggestions.

Always override 'equals()', 'hashCode()' and 'toString()' together or not at
all; keep them consistent with each other. If 'Foo' implements
'Comparable<Foo>' then it must override those three, all consistent with
'compareTo(Foo)'.
 
A

Arne Vajhøj

laredotornado said:
I never see my log statement printed out when I call "map.put". What
am I missing?
Michal said:
Probably int hashCode() (if a hashing map implementation is used). Or
appropriate Comparable/Comparator implementation if you use a tree
based map.

Don't quote sigs.
When I defined a hashCode method, then everything was fine -- the
HashMap object did not allow me to insert two keys that I considered
the same.

Per the other points about ClassCastExceptions in the equals method, I
have implemented your suggestions.

Always override 'equals()', 'hashCode()' and 'toString()' together or
not at all; keep them consistent with each other. If 'Foo' implements
'Comparable<Foo>' then it must override those three, all consistent with
'compareTo(Foo)'.

There are very good reasons to override equals and hashCode together.

I can see many cases where overriding toString alone makes sense,
because its main purpose is to make developer logging easier. And
often you will want to log something without it having to be
comparable.

Arne
 
M

Michael Jung

laredotornado said:
public boolean equals(Object obj) {
SearchResultHotels hotel = (SearchResultHotels) obj;
boolean ret = obj != null && ((SearchResultHotels) obj).getId() ==
getId();
log.debug("\t comparing " + hotel.getName() + " to " + getName() +
":" + ret);
return ret;
}
I never see my log statement printed out when I call "map.put". What
am I missing?

Did you override the hashCode() method?

Michael
 
T

Tom Anderson

The '(obj == null)' clause is superfluous.

Good point.

My brain, for some reason, has a hard time remembering that:

SearchResultHotels x = (SearchResultHotels)null;

is okay but:

null instanceof SearchResultHotels

is false.

tom
 
L

Lew

There are very good reasons to override equals and hashCode together.

I can see many cases where overriding toString alone makes sense,
because its main purpose is to make developer logging easier. And
often you will want to log something without it having to be
comparable.

There's nothing in my advice to require loggable things to be Comparable.

If you're logging information that identifies an instance, by definition you
have to provide a string representation of the identifying attributes of the
instance. If you aren't logging an instance's identifying attributes, you
don't use 'toString()' in the log message. Ergo, 'toString()' has to include
the identifying attributes of the instance to be useful.

The identifying attributes of the instance are precisely those that figue in
to 'equals()' and 'hashCode()', with the possible addition of the object
identifier. Ergo, 'toString()' must be consistent with 'equals()' and
'hashCode()', in the sense that it must represent the values of the attributes
that figure in to those methods.

Q.E.D.
 
R

Robert Klemme

Good point.

My brain, for some reason, has a hard time remembering that:

SearchResultHotels x = (SearchResultHotels)null;

is okay but:

null instanceof SearchResultHotels

is false.

You just need to remember that null does not have a type. At least
that's what helped me to get this straight. :)

Also, "nothing" cannot really be an instance of "something", can it?

Kind regards

robert
 
T

Tom Anderson

You just need to remember that null does not have a type. At least
that's what helped me to get this straight. :)

Yes, when i think about it, it's all perfectly clear. But i get it wrong
when i'm not thinking about it.

In further penance, i should point out that the instanceof test is risky
if the class can have subclasses, and where those subclasses might have
different ideas about equality. A more conservative form might be:

if ((obj == null) || !(obj,getClass().equals(SearchResultHotels.class)) return false;
Also, "nothing" cannot really be an instance of "something", can it?

Ah, but you would agree that nothing can be an instance of everything!

tom
 
M

Mike Schilling

Tom said:
Yes, when i think about it, it's all perfectly clear. But i get it
wrong when i'm not thinking about it.

Me too. Especially since the usage

if (x instanceof Type)
{
Type t = (Type)x;
...

is so common. The instanceof check (among other things) avoids a cast
failure, but the cast would work perfectly well if x were null.
 
R

Roedy Green

he Javadocs suggest that Map uses containsKey, and
hence an Object's equals method to determine if the object is already
a key in the map.

Here is the code for put:

public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key ||
key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}

You can see it using equals, but only as a last resort. That is why it
does not always call your equals method.
 
R

Robert Klemme

Me too. Especially since the usage

if (x instanceof Type)
{
Type t = (Type)x;
...

is so common. The instanceof check (among other things) avoids a cast
failure, but the cast would work perfectly well if x were null.

But then you'd get a NPE as soon as you try to invoke any method on the
reference - so the idiom totally makes sense and the instanceof check
avoids a cast failure *and* a NPE.

Cheers

robert
 
D

Daniel Pitts

There's nothing in my advice to require loggable things to be Comparable.

If you're logging information that identifies an instance, by definition
you have to provide a string representation of the identifying
attributes of the instance. If you aren't logging an instance's
identifying attributes, you don't use 'toString()' in the log message.
Ergo, 'toString()' has to include the identifying attributes of the
instance to be useful.

The identifying attributes of the instance are precisely those that
figue in to 'equals()' and 'hashCode()', with the possible addition of
the object identifier. Ergo, 'toString()' must be consistent with
'equals()' and 'hashCode()', in the sense that it must represent the
values of the attributes that figure in to those methods.

Q.E.D.
Except your premise of the purpose of toString is incorrect. toString
needn't be consistent with equals or hashCode, as it needn't include any
of the same set of information and still fulfill its contract.
 
A

Arved Sandstrom

Daniel said:
Except your premise of the purpose of toString is incorrect. toString
needn't be consistent with equals or hashCode, as it needn't include any
of the same set of information and still fulfill its contract.

From the Object.toString() Javadocs:

"In general, the toString method returns a string that "textually
represents" this object."

[ embedded quotation in the original ]

To me that supports what Lew said, that toString() ought to be
_consistent_ with equals() and hashCode(). I can't think of a "textual
representation" of an object that ignores the information that
identifies it.

AHS
 
D

Daniel Pitts

Daniel said:
Except your premise of the purpose of toString is incorrect. toString
needn't be consistent with equals or hashCode, as it needn't include
any of the same set of information and still fulfill its contract.

From the Object.toString() Javadocs:

"In general, the toString method returns a string that "textually
represents" this object."

[ embedded quotation in the original ]

To me that supports what Lew said, that toString() ought to be
_consistent_ with equals() and hashCode(). I can't think of a "textual
representation" of an object that ignores the information that
identifies it.

AHS
Perhaps a "Timing" object, which has a toString() which returns time in
ms/s/minutes/etc..., but is not a value type (eg, identity is important).

The object is textually represented, but equals/hashCode are not
relevant to the contents of the textual representation.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top