Переопределение hashCode() в LinkedHashSet в Java

191
28 февраля 2017, 15:25

Всем привет, подскажите пожалуйста как правильно переопределить hashCode что б корректно работала такая запись:

    LinkedHashSet<Integer> uniqueValues = new LinkedHashSet<Integer>();
    Random rnd = new Random();
    while(uniqueValues.size() < 5){
         number = 1 + rnd.nextInt(5 - 1 + 1);
         uniqueValues.add(number);
    }

Необходимо что бы коллекции значения не сортировались по возрастанию, а находились в порядке добавления, я так понял для этого необходимо переопределить HashCode(), только чуть не понял где и как

Answer 1

Вам не надо переопределять hashCode. Элементы будут и так храниться в том порядке добавления. Это свойство LinkedHashSet.

Answer 2

Методы hashCode() и equals() необходимо переопределить для кастомного (Вами созданного) объекта, чтобы определить поведение, при сравнении двух этих объектов. Т.е. надо явно указать, что считать одинаковыми объектами.

Суть Set'a (множества) как раз в том, что в него нельзя добавить объекты, которые уже в нем есть. Т.е. если вы будете добавлять каждый раз один и тот же объект (исходя как раз из equals и hashCode) он не будет добавляться. Использование реализации LinkedHashSet уже подразумевает хранение в порядке добавления.

Допустим у меня есть класс:

class MyClass{
    private int id;
    private String name;
    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}

Теперь я создаю Set и помещаю в него два объекта:

Set<MyClass> set = new LinkedHashSet<>();
set.add(new MyClass(1,"1"));
set.add(new MyClass(1,"1"));
set.forEach(myClass -> System.out.println("Element -> id: "+myClass.getId()+"; name: "+myClass.getName()));

Результат:

Element -> id: 1; name: 1

Element -> id: 1; name: 1

Т.е. в текущем множестве находится два объекта. Это происходит потому что все объекты в Java наследуются от класса Object, в котором сравнение объектов происходит по ссылке (на участок в памяти). Т.е. технически это два разных объекта (располагаются в разных участках памяти), и они не равны.

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

Добавляем эти методы в класс MyClass:

@Override
public boolean equals(Object o) {
    //Здесь явное сравнение по ссылке
    if (this == o) return true;
    //Здесь, если объект не является таким же классом то вернет false
    if (!(o instanceof MyClass)) return false;
    MyClass myClass = (MyClass) o;
    //Здесь, если id'шники не равны вернет false
    if (getId() != myClass.getId()) return false;
    //Здесь, если name не равен null (у того и другого объекта) и они идентичны возвращаем true 
    return getName() != null ? getName().equals(myClass.getName()) : myClass.getName() == null;
}
@Override
public int hashCode() {
    //Здесь складываем значения id и hashCode() строки name и получаем относительно уникальный хэш
    int result = getId();
    result = 31 * result + (getName() != null ? getName().hashCode() : 0);
    return result;
}

Заново запущу код создания Set'a только добавлю еще пару элементов:

Set<MyClass> set = new LinkedHashSet<>();
set.add(new MyClass(1,"1"));
set.add(new MyClass(2,"2"));
set.add(new MyClass(2,"not 2"));//Id совпадают, но name разные
set.add(new MyClass(1,"1"));//Пытаюсь добавить уже существующий элемент
set.add(new MyClass(3,"3"));
set.add(new MyClass(2,"2"));//Пытаюсь добавить уже существующий элемент
set.forEach(myClass -> System.out.println("Element -> id: "+myClass.getId()+"; name: "+myClass.getName()));

Результат:

Element -> id: 1; name: 1

Element -> id: 2; name: 2

Element -> id: 2; name: not 2

Element -> id: 3; name: 3

Как видно из примера идентичные значения не стали добавляться, хотя при этом это разные объекты в памяти. По этой логике я могу отказаться от сравнения поля name, тогда оно будет происходить только по id.

P.S. Для большинства встроенных в Java классов данные методы уже определены, и Вам не нужно это делать явно.

READ ALSO
Background image

Background image

Как сделать background image на столько большим, что бы можно сделать кнопку наверх в виде ракеты который взлетает с космодрома (это наш background image в самом...

215
Работа с событиями JavaScript (jQuery)

Работа с событиями JavaScript (jQuery)

Доброго времени суток

251
Слои, z-index, перекрытие слоев

Слои, z-index, перекрытие слоев

Выпадающее меню кнопки попадает по слой другого элемента, который находится нижеХотя у выпадающего меню явно указано абсолютное позиционирование...

248