Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArrayList.containsAll does not use my custom equals function

The following code is a JUnit test function which fails upon execution.

List<KGramPostingsEntry> a = new ArrayList<KGramPostingsEntry>();
List<KGramPostingsEntry> b = new ArrayList<KGramPostingsEntry>();
KGramPostingsEntry entry = new KGramPostingsEntry(1);
a.add(entry);

entry = new KGramPostingsEntry(1);
b.add(entry);

assertTrue(a.containsAll(b));

It uses the KGramPostingsEntry class:

package ir;

public class KGramPostingsEntry {
    int tokenID;

    public KGramPostingsEntry(int tokenID) {
        this.tokenID = tokenID;
    }

    public KGramPostingsEntry(KGramPostingsEntry other) {
        this.tokenID = other.tokenID;
    }

    public String toString() {
        return tokenID + "";
    }
    public boolean equals(KGramPostingsEntry other) {
        if(other.tokenID == this.tokenID) {
            return true;
        }
        return false;
    }
}

As you can see, there is an equals() function in the class that compares the tokenID of the different KGramPostingsEntry objects. It seems to me that this function is not used when calling containsAll() in the test. Further experimentation seems to verify this to be true:

List<KGramPostingsEntry> a = new ArrayList<KGramPostingsEntry>();
List<KGramPostingsEntry> b = new ArrayList<KGramPostingsEntry>();
KGramPostingsEntry entry = new KGramPostingsEntry(1);
a.add(entry);
b.add(entry);

assertTrue(a.containsAll(b));

Here, I'm inserting the same object in both lists. This test does not fail. As far as I've gathered, ArrayList makes a copy object of the object sent to add(), before storing a reference to that object. This means that the objects in the two Lists are not the same (even though they have the same tokenID), and that containsAll() does not check for object reference equality. But if it does not check for object reference equality and does not check the equals() function defined in my code, what does it check? The only plausible option to me is that it checks for object value equality, and that the two objects stored in the first test example are somehow different (even though their only property is tokenID, which is the same in both objects).

What is going on here? How can I make this test succeed the way I want it to?

like image 459
Sahand Avatar asked Jan 29 '23 06:01

Sahand


1 Answers

Here the equals declaration of Object:

public boolean equals(Object obj)

(documentation). You're trying to override this method, but instead you overloaded it:

public boolean equals(KGramPostingsEntry other)

Notice how the argument type in your method is KGramPostingsEntry, which differs from the argument type in Object.equals, namely Object. When a method has the same name but different argument types, it is overloaded, not overridden.

When the ArrayList tries to compare its contents with equals, it'll use the most applicable overridden version of Object.equals. That unfortunately doesn't include your method.

Luckily the fix is easy: you need to implement your equals method with an Object argument:

public boolean equals(Object obj) {
    if(obj == null || !(obj instanceof KGramPostingsEntry)) {
        return false;
    }
    KGramPostingsEntry other = (KGramPostingsEntry) obj;
    if(other.tokenID == this.tokenID) {
        return true;
    }
    return false;
}
like image 58
k_ssb Avatar answered Feb 01 '23 15:02

k_ssb