Comparing two different objects using contains(Object o) returns false, when equals(Object o) returns true

[FIXED PROBLEM BELOW]

I have this class:

Class Doc() {
  private String D;

  Doc(String d) {
    D = d;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceOf String) {
      if (o.equals(D)) return true;
      else return false;
    } else return false;
  }

  @Override
  public int hashCode() {
    ...
  }
}

If I run a test, initiating a Doc() using e.g. "abc" as parameter and then use Doc().equals("abc") it returns true - as I would expect. If I use:

ArrayList docs = new ArrayList<Doc>();
Doc d = new Doc("123");
Docs.add(d);
System.out.println(docs.contains("123"));

it returns false, and I am quite unsure of why. I have tried adding output to the .equals() method to check if it is used, and it is. The code for the hashCode() is just some bogus (I know I should do something proper, I'm just not sure how) - but I am only ever going to use equals().

I read somewhere that equals() also checks hashCode(), however; if I add a System.out.println(".") as first line in hashCode() it doesn't output anything, so it doesn't seem like it is called. My best guess is that I can't compare two Objects of different class - is this correct? Or am I overlooking something else?


For future reference, this problem was solved using the suggestion of @JonSkeet - code is now:

Class Doc() {
  private String D;
  Doc(String d) {
    D = d;
  }

  public String getD() {
    return D;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceOf Doc) {
      Doc tmp = (Doc) o;
      if (tmp.getD().equals(D)) return true;
      else return false;
    } else return false;
  }

  @Override
  public int hashCode() {
    ...
  }
}
Jon Skeet
people
quotationmark

You're assuming that contains() will call memberInCollection.equals(candidate) rather than candidate.equals(memberInCollection). The documentation goes against that:

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

Fundamentally, your equals method breaks the normal contract, because it isn't symmetric - for any non-null values of x and y, x.equals(y) == y.equals(x) should be true. That's not the case with your code:

new Doc("123").equals("123") // True
"123".equals(new Doc("123")) // False

(Additionally, your equals method isn't even reflexive - x.equals(x) will never be true if x is a reference to an instance of Doc.)

Basically, you shouldn't try to make an instance of one type equal to an instance of an unrelated type. It will always end badly. It's hard enough to make equality work nicely within a type hierarchy - but for unrelated types it's a really bad idea.

people

See more on this question at Stackoverflow