Java Hashset.contains() создает таинственный результат

Я обычно не кодирую на Java, но в последнее время у меня не было выбора. Возможно, у меня возникло серьезное непонимание того, как правильно использовать HashSet. Таким образом, возможно, что-то, что я сделал, просто неправильно. Однако я благодарен за любую помощь, которую вы могли бы предложить. Итак, актуальная проблема:

В небольшой программе, которую я писал, я генерировал очень похожие объекты, которые при создании имели бы очень специфический id (a string или в моей последней итерации a long). Поскольку каждый объект создавал новые объекты, я хотел отфильтровать все те, что я уже создал. Поэтому я начал бросать id каждого нового объекта в свой Hash (Set) и тестировать с помощью HashSet.contains(), если ранее был создан объект. Вот полный код:

// hashtest.java
import java.util.HashSet;

class L {
    public long l;
    public L(long l) {
        this.l = l;
    }
    public int hashCode() {
        return (int)this.l;
    }
    public boolean equals(L other) {
        return (int)this.l == (int)other.l;
    }
}

class hashtest {
    public static void main(String args[]) {
        HashSet<L> hash = new HashSet<L>();
        L a = new L(2);
        L b = new L(2);
        hash.add(a);
        System.out.println(hash.contains(a));
        System.out.println(hash.contains(b));
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() == b.hashCode());
    }
}

выводит следующий результат:

true
false
true
true    

поэтому, по-видимому, contains не использует функцию equals, предоставленную L, или у меня есть некоторое существенное непонимание понятия...

Я тестировал его с openjdk (текущая версия, включенная в ubuntu), и официальная текущая Java из Oracle на Win7

для полноты официальной документации java-api для HashSet.contains():

public boolean contains(Object o)

Возвращает true, если этот набор содержит указанный элемент. Более формально, возвращает true тогда и только тогда, когда этот набор содержит элемент e такой, что (o==null ? e==null : o.equals(e)).

http://download.oracle.com/javase/6/docs/api/java/util/HashSet.html#contains(java.lang.Object)

Любые идеи или предложения?

Ответы

Ответ 1

Ваш метод equals должен принять Object.
Поскольку вы объявили его как принимающий L, он становится дополнительной перегрузкой вместо переопределения метода.
Поэтому, когда класс hashSet вызывает equals, он разрешает базовый метод Object.equals. Когда вы вызываете equals, вы вызываете свою перегрузку, потому что a и b объявляются как L вместо Object.

Чтобы предотвратить эту проблему в будущем, вы должны добавить @Override всякий раз, когда вы переопределяете метод.
Таким образом, компилятор предупредит вас, если это не переопределение.

Ответ 2

На самом деле вы не переопределяете Object.equals; вместо этого вы определяете новый метод с тем же именем, но с разными параметрами. Обратите внимание, что Object.equals принимает аргумент Object, тогда как ваш метод equals принимает аргумент L. Если вы переписываете метод equals для принятия Object и выполняете необходимую проверку/литье типов на L во время выполнения, то ваш код работает так, как вы ожидаете.

Кроме того, именно поэтому вы действительно должны использовать аннотации @Override всякий раз, когда ваш JRE поддерживает их. Таким образом, компилятор будет жаловаться, если вы случайно реализуете новый метод, когда вы собираетесь переопределить существующий.

В качестве примера этот метод equals должен работать правильно. (И, в случае несвязанной заметки, он не будет терпеть неудачу, если сравниваемый объект равен null.)

@Override
public boolean equals(Object other) {
    return other != null && other instanceof L && this.l == ((L)other).l;
}

Ответ 3

Когда вы добавляете объекты в набор, он внутренне вызывает методы equals и hashCode. Вы должны переопределить эти два метода. Например, я взял один класс bean с name, id, designation, затем создал и добавил объект employee.

HashSet<Employee> set = new HashSet<Employee>();
Employee employee = new Employee();
employee.setId(1);
employee.setName("jagadeesh");
employee.setDesignation("programmer");
set.add(employee);
Employee employee2 = new Employee();
employee2.setId(1);
employee2.setName("jagadeesh");
employee2.setDesignation("programmer");
set.add(employee2);

set.add() вызывает внутренние методы equals и hashCode. Поэтому вы должны переопределить эти два метода в своем классе bean.

@Override
public int hashCode(){
    StringBuffer buffer = new StringBuffer();
    buffer.append(this.name);
    buffer.append(this.id);
    buffer.append(this.designation);
    return buffer.toString().hashCode();
}
@Override
public boolean equals(Object object){
    if (object == null) return false;
    if (object == this) return true;
    if (this.getClass() != object.getClass())return false;
    Employee employee = (Employee)object;
    if(this.hashCode()== employee.hashCode())return true;
   return false;
}   

Здесь мы переопределяем equals() и hashCode(). Когда вы добавляете объект к методу HashSet, он выполняет внутреннюю итерацию всех объектов и вызывает метод equals. Поэтому мы переопределяем hashCode, он сравнивает все объекты hashCode с текущим hashCode и возвращает true, если оба они равны, иначе он возвращает false.