LowProfile
LowProfile

Reputation: 31

ArrayList's contains() method always returns false with custom object

I'm having some troubles with getting on with my code, I'll give you an simple example (though it's going to be a little more complex, this simple code doesn't work properly either).

class Sign {

  private String char;
  private Integer freq;

  public Sign(String c) {
  this.char = c; 
  }

  @Override
  public boolean equals(Object o) {

   String check = (String)o;
   return check.equals(this.char);
  }

  @Override
  public int hashCode() {

    int hash = 7;
    hash = 31 * hash + this.char.hashCode();
    return hash;
}

}

I assume that there's always will be a String in equals method for simplicity reasons. There's some hashCode() also to make sure that the contains() method will work and here's the test itself:

    ArrayList<Sign> queueOfSigns = new ArrayList<>();

    Sign test = new Sign("C");
    String c = "C";
    queueOfSigns.add(test);

    if(queueOfSigns.contains("C"))
        System.out.println("I am here!");

No matter what, this simple test-code always returns false in that case - so "I'm here" message never appears. I've been trying some different ways of approach my code but it was because the idea of this is to get single characters from String text and check whether the single character is already present in the ArrayList. Nevertheless - without getting this simple test working properly I can't move on, so I would like to ask you - what am I missing. It's my first time actually with using equals() and hashCode() methods to get my own object working properly with contains() method.

Upvotes: 1

Views: 3642

Answers (2)

Sweeper
Sweeper

Reputation: 270780

Your equals method is implemented incorrectly. It violates the general contract of Object.equals:

  • It is not reflexive - since it throws an exception when the argument is not a string, x.equals(x) where x is a Sign will crash with an exception.
  • It is not symmetric - x.equals(y) does not return the same value as y.equals(x), if y is a string and x is a Sign
  • It is not consistent - since it can possibly throw exceptions when the argument is not a string, not just return true or false.

On a low level of abstraction, the cause of this problem is the implementation of contains. As per the docs:

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

ArrayList actually calls o.equals(e) with o being the string you passed in. So it actually calls the equals method in String.

If contains called e.equals(o), then your program would have printed "I'm here", but your equals still violates the contract.

A better equals implementation is something like this:

@Override
public boolean equals(Object o) {
    if (o == null) {
        return false;
    }

    if (o.getClass() == this.getClass()) {
        Sign other = (Sign)o;
        return other.$char.equals($char); // I have renamed 'char' to '$char' since the former is not a valid identifier
    } else {
        return false;
    }
}

And your client code:

    ArrayList<Sign> queueOfSigns = new ArrayList<>();

    Sign test = new Sign("C");
    Sign c = new Sign("C");
    queueOfSigns.add(test);

    if(queueOfSigns.contains(c))
        System.out.println("I am here!");

EDIT:

I think this is what you are looking for:

arrayList.stream()
    .filter(x -> x.getChar().equals("C"))
    .findFirst().isPresent() // this returns true if a sign with C is found in the array list

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1073968

Your equals implementation is incorrect. equals has a specific contract; that code attempts to violate that contract. From the documentation:

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 and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.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.

There's no way to make an instance of your Sign class equals to a string.

Upvotes: 5

Related Questions