Комментарии 33
Эту ситуацию спрашивают на собеседованиях на Junior позицию, так как все описано в любой базовой книге по Java.
Как сломать HashSet в Java?
Ничего не сломанно, просто неправильно используется структура данных.
Зато теперь на собесах бедете уверенно отвечать про устройство хэшмепы.
Структура не используется неправильно. Посыл скорее в том, что при использовании mutable объектов могут возникать подобные ситуации, и к ним нужно быть внимательнее.
Неправильно. Нарушается контракт HashSet. Элемент после изменения хеша будет лежать на неправильной позиции. И это полностью ломает хеш-таблицу как структуру. Например, вы изменили элемент. А потом вставили элемент равный новому значению. Теперь у вас в хештаблице два одинаковых элемента, но один из них просто не на своём месте (и поэтому метод вставки его не заметил). Затем вы вставляете в хештаблицу ещё элементы, в ней кончается место и случается перехеширование. Что произойдёт в этот момент? Может быть сработает какой-нибудь assert и у вас случится исключение в месте вообще не связанном с исходной ошибкой. Может быть один элемент тихо потеряется. Может быть у вас теперь поиск начнет выдавать другой элемент, не тот что прежде. И держу пари это никак не регламентировано стандартом. Самое настоящее undefined behavior.
У вас после модификации ключа хештаблица превращается в бомбу, которая может взорваться (бросить недокументированное исключение, произвольно изменить видимое количество элементов) при вызове теоретически любого метода после изменения значения (даже если вы не пытаетесь обратиться к самому бракованному элементу). Их поведение больше не соответствует документации.
Я бы сказал, что поведение будет соответствовать документации. Но не будет соответствовать ожиданиям. Вроде не с чего там кидать исключения. При изменении размера будет пере-хеширование и объект встанет на своё место, если есть дубликаты, то они уберутся.
При вставке одинакового объекта создастся копия если нет коллизии хэшей.
При итерации после такой вставки вернётся два одинаковых объекта. Собственно это у автора и произошло.
При поиске скорее не найдётся если нет коллизии хэшей.
Кажется, хаб "Изучение языков" не про языки программирования ?
Ждем цикл статей где будет на примерах раскрыта тема того, что некоторые переменные метода передаются по ссылке, а некоторые по значению. :)
Вы удивитесь, но в данном коде даже не надо ничего менять в уже добавленных в мапу объектах, достаточно сделать вот так:
public class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
"}";
}
}
Dog jimmyOne = new Dog("Jimmy", 4);
Dog jimmyTwo = new Dog("Jimmy", 4);
Set<Dog> dogs = new HashSet<>();
dogs.add(jimmyOne);
dogs.add(jimmyTwo);
Я понимаю, о чем вы. Но в данном случае вариант с переопределением equals и hashCode не рассматривался, поскольку разбирается случай именно с модификацией объекта, который уже существует в коллекции.
Так этот код норм. Эти объекты не равны ни по hash ни по equals.
Изменяемые объекты можно использовать в хэшмапах, если hash и equals продолжают возвращать те же значения. Не спрашивайте меня когда это может быть полезно.
Тоже мне новости 197затертого года "А вы знаете товарищи , что если в фортране передать константу в процедуру и там её переписать , то она потом поменяет значение ?? "
вот вам идея для следующей статьи начального уровня - какое будет поведения у HashSet'a объектов класса classA, если в классе classА:
1. Не переопределять ни equals(), ни hashCode().
2. Переопределить только equals().
3. Переопределить только hashCode().
4. Переопределить и equals(), и hashCode().
Следующую статью посветите вопросу почему
new Boolean(true) == Boolean.TRUE
это false
Смотрим документацию на Set:
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
Ну это очень просто, прям очевидно. Это даже до простого уровня не дотягивает. Лучше б написал статью "Как я вошёл в IT после курсов, не зная элементарных вещей".
Что самое забавное, это то что на интервью многие кандидаты (любой уровень) говорят, что генерировать хешкод по всем полям обьекта, например через автогенерацию ИДЕ или ломбок, это прям хорошая идея.
И большая часть из них, даже намеки про мутабельные поля не понимает.
Так что, к сожалению, автор не одинок. Надо запретить ломбок и автогенерацию! (Сарказм)
эх, если бы только в языках придумали защиту... const...
В Джаву Record завезли. В Питоне tuple и dataclass.
А как рекорд нас спасёт? По прежнему можно значения поля объекта поменять же
Поля у рекордов финальные.
Что, правда что-ли?))
А у вас весь проект на рекордах сразу стал и все остальные типы иммутабельные стали?))
Что, правда что-ли?))
Правда.
А у вас весь проект на рекордах сразу стал и все остальные типы иммутабельные стали?))
Не понял о каком проекте вы говорите.
Видимо о ваших пет проектах, где с появлением рекордов всё поменялось)
С Рекордами ничего принципиально не поменялось для меня, просто стало удобнее обявлять неизменяемые типы. Меньше возможности для ошибки. Легче коммуницировать свои намеренья сдалать этот объект неизменяемым.
Для сложных объектов всегда можно билдер сделать.
Я не люблю мутабл объекты. Слишком много времения я провёл перскакивая с одной части проекта в другую чтобы понять цепочки мутаций.
Как не стоит работать с HashSet в Java?