I\'m having a problem with getting an ArrayList to correctly use an overriden equals. the problem is that I\'m trying to use the equals to only test for a single key field, and
This post was first written before Java 8 was available but now that it's 2017 instead of using the List.containts(...) method you can use the new Java 8 way like this:
System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());
And give your TestClass a getter for your testKey field:
public String getTestKey() {
return testKey;
}
The benefit of this approach is that you don't have to modify the equals or hash method and you'll look like a boss to your peers!
As many posts have said, the problem is that list.indexOf(obj) function calls "equals" of the obj, not the items on the list.
I had the same problem and "contains()" didn't satisfy me, as I need to know where is the element!. My aproach is to create an empty element with just the parameter to compare, and then call indexOf.
Implement a function like this,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
And then, call indexOf like this:
ind position = list.indexOf(InnerClass.empty(key));
Generally, you need to also override hashCode()
but this is not the main problem here. You are having an asymmetric equals(..)
method. The docs make it clear that it should be symmetric:
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
And what you observe is an unexpected behaviour due to broken contract.
Create an utility method that iterates all items and verifies with equals(..)
on the string:
public static boolean containsString(List<InnerClass> items, String str) {
for (InnerClass item : items) {
if (item.getTestKey().equals(str)) {
return true;
}
}
return false;
}
You can do a similar thing with guava's Iterables.any(..)
method:
final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
@Override
public boolean apply(InnerClass input){
return input.getTestKey().equals(str);
}
}
According to the JavaDoc of List.contains(o), it is defined to return true
if and only if this list contains at least one element
e
such that(o==null ? e==null : o.equals(e))
.
Note that this definition calls equals
on o
, which is the parameter and not the element that is in the List
.
Therefore String.equals()
will be called and not InnerClass.equals()
.
Also note that the contract for Object.equals() states that
It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.
But you violate this constraint, since new TestClass("foo", 1).equals("foo")
returns true
but "foo".equals(new TestClass("foo", 1))
will always return false
.
Unfortunately this means that your use case (a custom class that can be equal to another standard class) can not be implemented in a completely conforming way.
If you still want to do something like this, you'll have to read the specification (and sometimes the implementation) of all your collection classes very carefully and check for pitfalls such as this.
There are two errors in your code.
First: The "contains" method called on "objectList" object should pass a new InnerClass object as the parameter.
Second: The equals method (should accept the parameter as Object, and is correct) should handle the code properly according to the received object. Like this:
@Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof InnerClass) {
String inString = ((InnerClass)in).testKey;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
There are a few issues with your code. My suggestion would be to avoid overriding the equals entirely if you are not familiar with it and extend it into a new implementation like so...
class MyCustomArrayList extends ArrayList<InnerClass>{
public boolean containsString(String value){
for(InnerClass item : this){
if (item.getString().equals(value){
return true;
}
}
return false;
}
}
Then you can do something like
List myList = new MyCustomArrayList()
myList.containsString("some string");
I suggest this because if you override the equals should also override the hashCode and it seems you are lacking a little knowledge in this area - so i would just avoid it.
Also, the contains method calls the equals method which is why you are seeing the "reached here". Again if you don't understand the call flow i would just avoid it.