-1

Met an issue with the contains() method, not sure why it behaves differently in the two scenarios, as the following code snippet shows:

import java.awt.*;
import java.util.LinkedHashSet;
import java.util.Set;

public class SetDemo01 {
    public static void main(String[] args) {

        Set<Point> set = new LinkedHashSet<>();
        Point a = new Point(0, 1);
        Point b = new Point(0, 1);

        set.add(a);
        System.out.println(set.contains(b));

        Set<Coordinate> set02 = new LinkedHashSet<>();
        Coordinate c = new Coordinate(0, 1);
        Coordinate d = new Coordinate(0, 1);
        set02.add(c);
        System.out.println(set02.contains(d));

    }

}

class Coordinate {
    int x;
    int y;

    Coordinate (int x, int y) {
        this.x = x;
        this.y = y;
    }

}

The console prints:

true
false

Now I knew I need to override the equals() & hashCode() methods, but could someone pls show the reference for this: when apply the contains() method, the equals() is run, and the 'hashCode' is compared.

MadHatter
  • 301
  • 3
  • 12
  • 5
    Your `Coordinate` class doesn't override `equals` and `hashCode` – Eran Aug 27 '20 at 06:44
  • Thanks for pointing this out. Could you also point out what really is calculated when the method `contains()` is invoked. – MadHatter Aug 27 '20 at 06:48
  • Have a look at the source code for that method ([here's a version](http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/c5d02f908fb2/src/share/classes/java/util/AbstractCollection.java)) and you'll note one important line: `if (o.equals(it.next())) return true;`. Now have a look at what `equals()` is doing by default - it will only be true for _exactly the same_ instance, so you need to implement that (and by contract also implement `hashCode()` consistently as this will be needed for `HashSet` etc.) – Thomas Aug 27 '20 at 06:48
  • If you already are using `Point` class inside your code , unless of some specific reason you could use it instead of defining a new `Coordinate` class. – dariosicily Aug 27 '20 at 06:55
  • The reason for using both `Point` & `Coordinate` is just for demonstration, normally I wouldn't create another class when there is already a similar existing class. I'm trying to figure out what is evaluated when I call the method `contains()`. – MadHatter Aug 27 '20 at 07:00
  • @Thomas Thanks for the link, the code shows it will run the method `equals()`. If I override that method, the output will still be false. I also need to override the `hashCode()` method. Could you pls also help with the reference for that when `contains()` is executed the 'hashCode' will be compared? – MadHatter Aug 27 '20 at 07:40
  • As I already hinted at, `HashSet` will also require you to implement `hashCode()` (which you should always do when implementing `equals()` - and keep both consistent). Internally `HashSet` is using a [`HashMap`](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java) with the elements being the keys so you need to have a look at the `containsKey()` method (summarized: 1) it uses `hashCode()` to get the bucket to look into and 2) it uses `equals()` to find the correct key inside that bucket). – Thomas Aug 27 '20 at 08:39

1 Answers1

4

If you declare your class like that, you would have your expected output:

class Coordinate {
    int x;
    int y;

    Coordinate (int x, int y) {
      this.x = x;
      this.y = y;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Coordinate that = (Coordinate) o;
      return x == that.x &&
          y == that.y;
    }

    @Override
    public int hashCode() {
      return Objects.hash(x, y);
    }
  }

Without overriding the equals method the contains method will only look if the two objects are the same reference.

Please have also a look at: Why do I need to override the equals and hashCode methods in Java?

jmizv
  • 1,172
  • 2
  • 11
  • 28
  • Override `equals()` & `hashCode()` worked. One more thing, how could I know what is done when I run the `contains()` method, I didn't find much info about this, could you please also help? – MadHatter Aug 27 '20 at 07:11
  • @MadHatter - use a debugger to step through the process and see exactly which code gets executed. You are using a `LinkedHashSet` - if you do not know what this is, read up on [hash tables](https://en.wikipedia.org/wiki/Hash_table) first. – tucuxi Aug 27 '20 at 07:18
  • @Thomas pointed already to the source code of `AbstractCollection.contains()`. Have a look there (http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/c5d02f908fb2/src/share/classes/java/util/AbstractCollection.java#l98) but simply speaking the `contains` method will iterate over the content of the collection and perform a `equals` check for all elements. If one check will result in `true`, the method will return also `true`, immediat. If not, that means all elements in the collection are different to the element in question, it will return false. Note that the check will also work with `null`. – jmizv Aug 27 '20 at 07:21
  • @jmizv The code shows it will run the method `equals()`. If I override that method, the output will still be false. I also need to override the `hashCode()` method. But where is the reference for that when `contains()` is executed the 'hashCode' is compared? – MadHatter Aug 27 '20 at 07:32
  • @MadHatter in many collections just overriding `equals()` would be sufficient but especially the hash based ones also need you to override `hashCode()`. That's why the [JavaDoc on `equals()`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object)) states: "Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes." – Thomas Aug 27 '20 at 08:45