How does a ArrayList's contains() method evaluate objects?

JavaObjectArraylistEvaluation

Java Problem Overview


Say I create one object and add it to my ArrayList. If I then create another object with exactly the same constructor input, will the contains() method evaluate the two objects to be the same? Assume the constructor doesn't do anything funny with the input, and the variables stored in both objects are identical.

ArrayList<Thing> basket = new ArrayList<Thing>();  
Thing thing = new Thing(100);  
basket.add(thing);  
Thing another = new Thing(100);  
basket.contains(another); // true or false?

class Thing {  
    public int value;  

    public Thing (int x) {
        value = x;
    }

    equals (Thing x) {
        if (x.value == value) return true;
        return false;
    }
}

Is this how the class should be implemented to have contains() return true?

Java Solutions


Solution 1 - Java

ArrayList implements the List Interface.

If you look at the Javadoc for List at the contains method you will see that it uses the equals() method to evaluate if two objects are the same.

Solution 2 - Java

I think that right implementations should be

public class Thing
{
    public int value;  

    public Thing (int x)
    {
        this.value = x;
    }

    @Override
    public boolean equals(Object object)
    {
        boolean sameSame = false;

        if (object != null && object instanceof Thing)
        {
            sameSame = this.value == ((Thing) object).value;
        }

        return sameSame;
    }
}

Solution 3 - Java

The ArrayList uses the equals method implemented in the class (your case Thing class) to do the equals comparison.

Solution 4 - Java

Generally you should also override hashCode() each time you override equals(), even if just for the performance boost. HashCode() decides which 'bucket' your object gets sorted into when doing a comparison, so any two objects which equal() evaluates to true should return the same hashCode value(). I cannot remember the default behavior of hashCode() (if it returns 0 then your code should work but slowly, but if it returns the address then your code will fail). I do remember a bunch of times when my code failed because I forgot to override hashCode() though. :)

Solution 5 - Java

It uses the equals method on the objects. So unless Thing overrides equals and uses the variables stored in the objects for comparison, it will not return true on the contains() method.

Solution 6 - Java

class Thing {  
    public int value;  

    public Thing (int x) {
        value = x;
    }

    equals (Thing x) {
        if (x.value == value) return true;
        return false;
    }
}

You must write:

class Thing {  
    public int value;  

    public Thing (int x) {
        value = x;
    }

    public boolean equals (Object o) {
    Thing x = (Thing) o;
        if (x.value == value) return true;
        return false;
    }
}

Now it works ;)

Solution 7 - Java

Just wanted to note that the following implementation is wrong when value is not a primitive type:

public class Thing
{
    public Object value;  

    public Thing (Object x)
    {
        this.value = x;
    }

    @Override
    public boolean equals(Object object)
    {
        boolean sameSame = false;

        if (object != null && object instanceof Thing)
        {
            sameSame = this.value == ((Thing) object).value;
        }

        return sameSame;
    }
}

In that case I propose the following:

public class Thing {
    public Object value;  

    public Thing (Object x) {
        value = x;
    }

    @Override
    public boolean equals(Object object) {

        if (object != null && object instanceof Thing) {
            Thing thing = (Thing) object;
            if (value == null) {
                return (thing.value == null);
            }
            else {
                return value.equals(thing.value);
            }
        }

        return false;
    }
}

Solution 8 - Java

Other posters have addressed the question about how contains() works.

An equally important aspect of your question is how to properly implement equals(). And the answer to this is really dependent on what constitutes object equality for this particular class. In the example you provided, if you have two different objects that both have x=5, are they equal? It really depends on what you are trying to do.

If you are only interested in object equality, then the default implementation of .equals() (the one provided by Object) uses identity only (i.e. this == other). If that's what you want, then just don't implement equals() on your class (let it inherit from Object). The code you wrote, while kind of correct if you are going for identity, would never appear in a real class b/c it provides no benefit over using the default Object.equals() implementation.

If you are just getting started with this stuff, I strongly recommend the Effective Java book by Joshua Bloch. It's a great read, and covers this sort of thing (plus how to correctly implement equals() when you are trying to do more than identity based comparisons)

Solution 9 - Java

Shortcut from JavaDoc:

boolean contains(Object o)

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))

Solution 10 - Java

record overrides equals

You said:

>another object with exactly the same constructor input

… and …

>Assume the constructor doesn't do anything funny with the input, and the variables stored in both objects are identical.

As other Answers explain, you must override the Object#equals method for List#contains to work.

In Java 16+, the record feature automatically overrides that method for you.

A record is a brief way to write a class whose main purpose is to communicate data transparently and immutably. By default, you simply declare the member fields. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.

The logic of equals by default is to compare each and every member field of one object to the counterpart in another object of the same class. Likewise, the default implementations of hashCode and toString methods also consider each and every member field.

record Thing( int amount ) {} ;

That’s it, that is all the code you need for a fully-functioning read-only class with none of the usual boilerplate code.

Example usage.

Thing x = new Thing( 100 ) ; 
Thing y = new Thing( 100 ) ; 
boolean parity = x.equals( y ) ;

When run.

>parity = true

Back to your List#contains question.

Thing x = new Thing( 100 );
List < Thing > things =
        List.of(
                new Thing( 100 ) ,
                new Thing( 200 ) ,
                new Thing( 300 )
        );

boolean foundX = things.contains( x );

When run.

>foundX = true


Bonus feature: A record can be declared locally, within a method. Or like a conventional class you can declare a record as a nested class, or as a separate class.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMantas VidutisView Question on Stackoverflow
Solution 1 - JavaBinary NerdView Answer on Stackoverflow
Solution 2 - JavaChristopheCVBView Answer on Stackoverflow
Solution 3 - JavaBhushan BhangaleView Answer on Stackoverflow
Solution 4 - JavaalexlohView Answer on Stackoverflow
Solution 5 - JavaYishaiView Answer on Stackoverflow
Solution 6 - JavaDavideView Answer on Stackoverflow
Solution 7 - JavaCanerView Answer on Stackoverflow
Solution 8 - JavaKevin DayView Answer on Stackoverflow
Solution 9 - JavaDenisKolodinView Answer on Stackoverflow
Solution 10 - JavaBasil BourqueView Answer on Stackoverflow