Java Hashset.contains () дает загадочный результат - PullRequest
15 голосов
/ 07 ноября 2010

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

В небольшой программе, которую я писал, я генерировал очень похожие объекты, которые при создании имели бы очень специфический идентификатор (a string или в моей последней итерации a long). Поскольку каждый объект порождал новые объекты, я хотел отфильтровать все, что я уже создал. Поэтому я начал добавлять идентификатор каждого нового объекта в мой 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)

Есть идеи или предложения?

Ответы [ 3 ]

28 голосов
/ 07 ноября 2010

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

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

3 голосов
/ 04 июля 2011

Когда вы добавляете объекты в набор, он внутренне вызывает методы 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 методы.Поэтому вы должны переопределить эти два метода в своем классе бинов.

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

3 голосов
/ 07 ноября 2010

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

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

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