2

I was messing around trying to understand how HashSets behave and I've run into this problem that I can't get my head around. The 2nd and 3rd dog objects have the same name, and equals() and hashcode() have been overriden to make names mean equality. Despite this, the hashSet still has duplicates, and I can't figure out why.

I reread the data structures chapter of Head First Java but it still suggests that my code should work in theory.

public class DataStructsTests<E> {

HashSet<Dogs> tree = new HashSet<Dogs>();
HashSet<Dogs> treeOwner = new HashSet<Dogs>();

public static void main(String[] args) {
    DataStructsTests<String> d = new DataStructsTests<String>();
    d.go();
}

public void go() {
    Dogs dog = new Dogs("Scout", "a");
    tree.add(dog);
    treeOwner.add(dog);

    Dogs dog2 = new Dogs("Brodie", "b");
    tree.add(dog2);
    treeOwner.add(dog2);

    Dogs dog3 = new Dogs("Brodie", "c");
    tree.add(dog3);
    treeOwner.add(dog3);

    System.out.println(tree);
    System.out.println(treeOwner);

    System.out.println(dog2.equals(dog3));
    System.out.println(dog2.hashCode() + " " + dog3.hashCode());
}

class Dogs {
    private String name;
    private String ownerName;

    public Dogs(String n, String o) {
        name = n;
        ownerName = o;
    }

    public boolean equals(Dogs d) {
        return name.equals(d.getName());
    }

    public int hashCode() {
        return name.hashCode();
    }

         public String getName() {
        return name;
    }

             public String toString() {
        return name;
    }

Running the program returns this:

[Brodie, Brodie, Scout]
[Brodie, Brodie, Scout]
true
1998211617 1998211617

Even though equals() returns true and the hashcodes are the same, the duplicates still remain.

Edit: Turns out the problem was that I hadn't overridden the equals() method properly as I used Dog rather than Object.

L292092
  • 128
  • 1
  • 6
  • 2
    You haven't overriden the `equals` method. It *must* have a parameter of type `Object` to be overridden. This is merely an overload. – RealSkeptic Jun 03 '19 at 14:38
  • 1
    That is why you should **always** add `@Override` to methods when you actually try to override. – luk2302 Jun 03 '19 at 14:40

1 Answers1

5

equals takes an object of type Object, which is the one being called by HashSet. You need something like:

@Override
public boolean equals(Object d) {
    if (! d instanceof Dogs){
         return false;
    }
    return name.equals(((Dogs) d).getName());
}

Here are the components of this answer:

  1. public boolean equals(Object d) - equals, at least the version inherited from Object, is defined to take Objects, so to override it, you must also take an Object.
  2. @Override - tells the compiler to warn you if you make a mistake like you made in your question.
  3. d instanceof Dogs - checks if the Object fed in is even a Dogs in the first place.
  4. ((Dogs) d).getName() - The reason for the cast to Dogs is because d is now being passed in as an Object, so you won't automatically gain access to Dogs's methods unless you explicitly say that you want to view the Object as a Dogs.

And one final note: the general convention in Java is to name classes in the singular, unless there is some reason to believe that each instance will be multiple somethings. This is to avoid ambiguity. Dog d makes clear what d is; it is clearly a Dog. What, exactly, is a Dogs d? Is d many dogs, and they just don't have their own object type? It becomes a little ambiguous.

Ben I.
  • 1,065
  • 1
  • 13
  • 29