views:

3696

answers:

5

Hello

I have an ArrayList in Java which is made up of a type containing two strings and an integer. I can successfully test if one element of this ArrayList equals another but I find that the contains method fails. I believe this is due to the fact that my type is not primitive.

Now I see two alternatives to this and I wonder which is the best option:

  1. To implement my own contains method by iterating through the ArrayList and testing equality of each element against the one I'm looking for and then breaking the loop.

  2. Or to use a HashMap of my type as key with an integer as value instead of the ArrayList. Here I can use method containsKey to check if an element already exists in the HashMap.

The only caveat with approach to #2 is that the value is largely redundant in my case.

+15  A: 

Most likely, you have simply forgotten to override equals() and hashCode() in your type. equals() is what contains() checks for.

From the Javadoc:

Returns true if this list contains the specified element. More formally, returns true if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).

Since the default implementation of equals tests for reference equality, it's not suitable for custom data types like this one.

(And if you didn't override equals and hashCode, using your types as keys in a HashMap would be equally futile.)


Edit: Note that to override, you must provide the exact signature.

class MyDataType {
    public boolean equals(MyDataType other) { // WRONG!
        ...
    }
    public boolean equals(Object other) { // Right!
        ...
    }
}

This is a very strong argument for using the @Override annotation; the first example would have failed at compile time if annotated with @Override.

Michael Myers
In my class I have specified an equals method:private boolean equals(HierarchicalTreemapElement hte) { if ((this.getChild().equals(hte.getChild()) } else { return false; } }But contains still fails if an arraylist is used. What need I do for hashCode?
You have overridden equals() incorrectly, like Jon Skeet says. Put an @Override annotation in front of the method signature just to be sure (if it's not correct, it will generate a compile error).
Michael Myers
For hashCode, if you've already overridden it, it's probably sufficient. If you haven't, you definitely should; overriding equals() should *always* imply overriding hashCode().
Michael Myers
In addition to always using the @Override annotation, I'd recommend adjusting your compiler settings so that it generates and error if you don't use it.
James
note there are two problems with your equals signature:scope incorrect (must be public), type incorrect (must accept Object).
Tetsujin no Oni
@Tetsujin no Oni: Funny thing is, if he'd gotten the parameter right, the scope problem would have kept it from compiling even without the @Override.
Michael Myers
I have it now with correctly overriden equals and hashcode methods.
Thanks to you all.
+3  A: 

Did you override the equals method? This is required to make contains work correctly.

amarillion
A: 

maybe use the Integer class instead? then you can do object comparison

Rickster
+9  A: 

My guess is that you've only written a "strongly typed" equals method instead of overriding equals(Object). In other words, if you've got:

public boolean equals(Foo f)

you need

public boolean equals(Object o)

as well to override Object.equals.

That would fit with "equals works but contains doesn't because your tests probably call the strongly-typed equals, but ArrayList doesn't.

Jon Skeet
Good call. Psychic debugging saves the day again!
Michael Myers
To expand on the 'passes your tests but fails' comment, note that if you're testing MyClass#equals, you should do it using calls against Object#equals, passing an Object. (Makes a note to go fix his unit tests, as he thinks he's not doing this right).
Tetsujin no Oni
Hmm... I'm not sure my answer actually adds anything over the answer by mmyers. Tetsujin no Oni: do you want to copy your comment over to that answer, and then I can delete this one?
Jon Skeet
+1  A: 

Remember that if you don't override the equals() method, then two objects of your type are only equal if they are the same instance of that object. The ArrayList class uses this method to check that it contains the given object. Also, you need to match the signature exactly, which means that it must take an Object as a parameter and not a Foo.

Also, the Object contract stipulates that you must override hashCode() whenever you override equals(). If you don't do this, then a HashMap or HashSet won't identify your two objects as being equal, even if the ArrayList does (HashMap checks for identical hashes and then calls equals() on them to check for actual equality). Thus, if ArrayList says that two items aren't equal, then there is no way that HashMap would either. This means that your second solution does not work.

My recommendation is to check that you actually override equals() and hashCode() properly and that their signatures match the ones in the Object class.

James