I have an ArrayList
of Test
objects, which use a string as the equivalency check. I want to be able to use List.contains()
to check whether or not the list contains an object that uses a certain string.
Simply:
Test a = new Test("a");
a.equals("a"); // True
List<Test> test = new ArrayList<Test>();
test.add(a);
test.contains("a"); // False!
Equals and Hash function:
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (!(o instanceof Test)) {
return (o instanceof String) && (name.equals(o));
}
Test t = (Test)o;
return name.equals(t.GetName());
}
@Override
public int hashCode() {
return name.hashCode();
}
I read that to make sure contains
works for a custom class, it needs to override equals
. Thus it's super strange to me that while equals
returns true, contains
returns false.
How can I make this work?
Full code
From Java Doc, the contains() method returns true if and only if this set contains an element e such that (o==null ? e==null : o. equals(e)). So the contains() method actually use equals() method to check equality.
Using contains with custom object This program demonstrates how to check the ArrayList contains the custom object. It checks the presence by calling equals() method of our object. That's all about the contains method.
contains() in Java. ArrayList contains() method in Java is used for checking if the specified element exists in the given list or not. Returns: It returns true if the specified element is found in the list else it returns false.
Note: The contains() method internally uses the equals() method to find the element. Hence, if the specified element matches with the element in arraylist, the method returns true .
Just because your Test
's equals
may return true when you pass a String to it doesn't mean that String
's equals
will ever return true when you pass a Test
instance to it. In fact, String
's equals
can only return true
when the instance passed to it is another String
:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) { // the passed instance must be a String
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
ArrayList
's contains
calls indexOf
which uses the equals
method of the searched instance (the String
"a" in your example), not the element type of the List
(which is Test
in your case) :
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) // o in your case is a String while
// elementData[i] is a Test
// so String's equals returns false
return i;
}
return -1;
}
equals()
should always be commutative, i.e. a.equals(b)
and b.equals(a)
should always return the same value. Or symmetric, as the javadoc of equals()
calls it:
The
equals
method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value
x
,x.equals(x)
should returntrue
.- It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.- It is transitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
.- It is consistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified.- For any non-null reference value
x
,x.equals(null)
should returnfalse
.
Unfortunately, even the Java Runtime Library gets this one wrong. Date.equals(Timestamp)
will compare the millisecond values, ignoring the nanoseconds present in the Timestamp
, while Timestamp.equals(Date)
returns false
.
The problem is that List<E>.contains(object o)
is documented to return true:
if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
(From https://docs.oracle.com/javase/8/docs/api/java/util/List.html#contains-java.lang.Object-)
Note that it doesn't perform the test as e.equals(o)
which is what would be necessary for your test to work. Your equals method fails to work commutatively ('symmetrically' using the terms from the Java docs).
Java documents that the equals()
method for a class must follow these rules:
The equals method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value
x
,x.equals(x)
should return true.- It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should return true if and only ify.equals(x)
returns true.- It is transitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returns true andy.equals(z)
returns true, thenx.equals(z)
should return true.- It is consistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.- For any non-null reference value
x
,x.equals(null)
should return false.
If you write
test.contains(new Test("a"));
then it will surely return true. You are checking for string object in list of Test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With