HashSet позволяет дублировать

Кажется, я не могу заставить экземпляр HashSet работать должным образом. Код, который я использовал, выглядит следующим образом:

import testing.Subclass;
import java.util.HashSet;

public class tester {
  public static void main(String[] args) throws Exception {
    HashSet<Subclass> set = new HashSet<Subclass>();
    set.add(new Subclass("007812"));
    set.add(new Subclass("007813"));
    System.out.println("Set size " + set.size());
    set.add(new Subclass("007812"));
    System.out.println("Set size " + set.size());

    for(Subclass sub : set) {
      System.out.println(" sub acctNbr " + sub.getAcctNbr());
    }
  }
}

Подкласс

public class Subclass implements Comparable<Subclass> {

  public Subclass(String acctNbr) {
    this.acctNbr = acctNbr;
  }
  private String acctNbr;
  public String getAcctNbr() {
    return this.acctNbr;
  }
  public int compareTo(Subclass other) {
    return this.getAcctNbr().compareTo(other.getAcctNbr());
  }

  public boolean equals(Subclass other) {
    if(other.getAcctNbr().equals(this.getAcctNbr()))
      return true;
    else
      return false;
  }
  public int hashCode() {
    return acctNbr.hashCode();
  }
}

Этот код выводит

sross@sross-workstation:~/Documents$ javac testing/Subclass.java
sross@sross-workstation:~/Documents$ javac tester.java
sross@sross-workstation:~/Documents$ java tester
Set size 2
Set size 3
 sub acctNbr 007812
 sub acctNbr 007812
 sub acctNbr 007813
sross@sross-workstation:~/Documents$

person Sheldon Ross    schedule 02.11.2009    source источник
comment
Какого поведения вы ожидаете и чем оно отличается от поведения, которое вы видите?   -  person Bill the Lizard    schedule 02.11.2009


Ответы (6)


Вам нужно переопределить equals(Object). Вместо этого вы реализовали метод equals с сигнатурой equals(Subclass). Следовательно, ваш HashSet использует метод equals(Object) по умолчанию, определенный в Object, для проверки на равенство.

Реализация equals(Object) по умолчанию основана на идентификаторе объекта, и, следовательно, набор «позволяет» добавить два String, которые, хотя и равны семантически, не являются одним и тем же объектом.

person Adamski    schedule 02.11.2009
comment
Не забудьте также переопределить hashCode(). - person mR_fr0g; 03.11.2009
comment
OP уже правильно переопределил hashCode(), но это все еще важный момент. - person Adamski; 03.11.2009

Вы неправильно переопределили Object.equals().

@Override
public boolean equals(Object other) {
    if ((other == null) || !(other instanceof Subclass)) {
        return false;
    }
    return ((Sublcass) other).getAcctNbr().equals(this.getAcctNbr());
}

Метод boolean equals(Subclass other) создает второй метод, который вы не собирались делать.

person Bombe    schedule 02.11.2009
comment
Boo только для необязательного тега переопределения. - person ; 02.11.2009
comment
Вам не нужно проверять значение null, так как instanceof возвращает false для нулевой ссылки. - person Jonathan Feinberg; 02.11.2009
comment
Согласен, строгая проверка @Override сразу бы указала на проблему. - person andersoj; 02.11.2009
comment
Вы можете оптимизировать это, сначала проверив, является ли это == другим. Кроме того, вам нужно привести «другое», прежде чем вы сможете вызвать для него getAcctNbr(). public boolean equals(Object o) { return this == o || (o instanceof Subclass && acctNbr.equals(((Subclass)o).getAcctNbr)); } - person Adamski; 03.11.2009

Две мета-точки:

Во-первых, выработайте привычку использовать @Override каждый раз, когда считаете, что переопределяете метод. Это привело бы к тому, что код вашего примера не смог бы скомпилироваться, что привело бы к обнаружению проблемы.

Во-вторых, если вы используете IDE, и она не выделила для вас хорошего жирного предупреждения, значит, она неправильно настроена! Вы должны это исправить!

И если вы не используете IDE — вы действительно должны это сделать. Как только вы введете public boolean equals(Subclass other), текст изменит цвет и появится предупреждение, говорящее о вашей вероятной проблеме.

Между прочим, стандартная идиома для equals(), к которой я пришел, такова:

@Override public boolean equals(Object object) {
  if (object instanceof Subclass) {
    Subclass that = (Subclass) object;
    return this.anInt == that.anInt
        && this.aString.equals(that.aString); // for example
  }
  return false;
}

В некоторых случаях стоит добавить if (object == this) { return true; }, но делать это регулярно не стоит.

person Kevin Bourrillion    schedule 04.11.2009

У меня была почти такая же проблема, так как все говорили, что нужно переопределить правильный метод public boolean equals(Object o). Но этого недостаточно!

Также необходимо переопределить public int hashCode() (как вы это сделали), иначе java вообще не будет вызывать метод equals.

person Abbas    schedule 06.09.2012

Во-первых, похоже, что ваш equals(Subclass other) должен быть equals(Object other), чтобы переопределить метод java.lang.Object.equals(), как вы хотите. Вероятно, набор вызывает базовую реализацию equals().

person andersoj    schedule 02.11.2009
comment
Ага, точнее, именно то, что сказал Бомбе. - person andersoj; 02.11.2009

Ваш метод equals никогда не вызывается. Сигнатура equals требует, чтобы он принимал Object, а не какой-либо другой класс (включая любой класс, реализующий equals).

public boolean equals(Object other) {
    ...
}
person Jonathan Feinberg    schedule 02.11.2009