323

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?

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
Mantas Vidutis
  • 16,376
  • 20
  • 76
  • 92

10 Answers10

369

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.

PurkkaKoodari
  • 6,703
  • 6
  • 37
  • 58
Binary Nerd
  • 13,872
  • 4
  • 42
  • 44
  • 63
    Just in case you plan to override equals(), make sure you override hashcode() method as well. If you wont, things may not work as expected while using Collections? – Mohd Farid Apr 15 '10 at 09:06
  • 36
    This is a correct answer, but note that you need to change your equals method to accept an `Object` rather than a `Thing`. If you don't, your equals method won't be used. :) – mdierker Apr 23 '13 at 20:13
  • 1
    Just discovered for myself that eclipse has "Generate hashCode() and equals" under Source menu. – Volodymyr Krupach Aug 16 '16 at 15:54
  • 1
    This answers the question in the title, but not the question in the description, i.e. "If I then create another object with exactly the same constructor input, will the contains() method evaluate the two objects to be the same?" – robguinness Oct 27 '17 at 15:41
  • 5
    `Collections` do their stuff in an optimized way, meaning that `contains()` firstly checks the `hashCode`s of the two objects, and only then calls `equals()`. If the `hashCode`s are different (which is always the case for two different instances of `Thing`), the `equals()` method won't be called. As a rule of thumb, when you override `equals()`, you should not forget to override `hashCode()` also. – Sevastyan Savanyuk Jan 18 '18 at 13:17
  • But why should we override hascode() method, ArrayList is not a hash based collection, i saw implementaion of contains(-), no whr it checks hashcode.please clarify. I think only for maintaining contract we should overidehashCode(), it wont be used with ArrayList?If we can convert the ArrayList to HashSet, than it will be definetely required. – Diana May 30 '18 at 15:08
  • @SevastyanSavanyuk are you saying the Javadocs are incorrect? The `equals` method is said to returns `true` if and only if the list contains at least one element `e` such that `Objects.equals(o, e)`. Meanwhile, the Javadocs say that for `o` non-null, `Objects.equals(o,e)` is determined by using the equals method of the first argument. So according to the Javadocs, `hashCode` is irrelevant here. – Guillaume F. May 13 '20 at 15:57
57

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;
    }
}
ChristopheCVB
  • 7,269
  • 1
  • 29
  • 54
14

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

Bhushan Bhangale
  • 10,921
  • 5
  • 43
  • 71
12

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

Prophet
  • 32,350
  • 22
  • 54
  • 79
alexloh
  • 1,606
  • 2
  • 15
  • 29
7

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.

Yishai
  • 90,445
  • 31
  • 189
  • 263
6
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 ;)

j0k
  • 22,600
  • 28
  • 79
  • 90
Davide
  • 77
  • 1
  • 1
6

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;
    }
}
Caner
  • 57,267
  • 35
  • 174
  • 180
4

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)

Kevin Day
  • 16,067
  • 8
  • 44
  • 68
  • For my purpose I was trying to see if an object of equal value was in the ArrayList. I suppose it is a sort of hack. Thank you for the book recommendation – Mantas Vidutis Apr 15 '10 at 06:01
3

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

DenisKolodin
  • 13,501
  • 3
  • 62
  • 65
2

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154