Как стать автором
Обновить

Комментарии 33

Эту ситуацию спрашивают на собеседованиях на Junior позицию, так как все описано в любой базовой книге по Java.

Естественно. Поэтому у статьи уровень сложности простой, и она не нацелена на "прожженных" джавистов.

Как сломать HashSet в Java?

Ничего не сломанно, просто неправильно используется структура данных.

Зато теперь на собесах бедете уверенно отвечать про устройство хэшмепы.

Структура не используется неправильно. Посыл скорее в том, что при использовании mutable объектов могут возникать подобные ситуации, и к ним нужно быть внимательнее.

Неправильно. Нарушается контракт HashSet. Элемент после изменения хеша будет лежать на неправильной позиции. И это полностью ломает хеш-таблицу как структуру. Например, вы изменили элемент. А потом вставили элемент равный новому значению. Теперь у вас в хештаблице два одинаковых элемента, но один из них просто не на своём месте (и поэтому метод вставки его не заметил). Затем вы вставляете в хештаблицу ещё элементы, в ней кончается место и случается перехеширование. Что произойдёт в этот момент? Может быть сработает какой-нибудь assert и у вас случится исключение в месте вообще не связанном с исходной ошибкой. Может быть один элемент тихо потеряется. Может быть у вас теперь поиск начнет выдавать другой элемент, не тот что прежде. И держу пари это никак не регламентировано стандартом. Самое настоящее undefined behavior.

У вас после модификации ключа хештаблица превращается в бомбу, которая может взорваться (бросить недокументированное исключение, произвольно изменить видимое количество элементов) при вызове теоретически любого метода после изменения значения (даже если вы не пытаетесь обратиться к самому бракованному элементу). Их поведение больше не соответствует документации.

Я бы сказал, что поведение будет соответствовать документации. Но не будет соответствовать ожиданиям. Вроде не с чего там кидать исключения. При изменении размера будет пере-хеширование и объект встанет на своё место, если есть дубликаты, то они уберутся.

При вставке одинакового объекта создастся копия если нет коллизии хэшей.
При итерации после такой вставки вернётся два одинаковых объекта. Собственно это у автора и произошло.
При поиске скорее не найдётся если нет коллизии хэшей.

Кажется, хаб "Изучение языков" не про языки программирования ?

Ждем цикл статей где будет на примерах раскрыта тема того, что некоторые переменные метода передаются по ссылке, а некоторые по значению. :)

В java все аргументы метода передаются по значению.

Hidden text

Надо только не забывать, что для объектных типов значением является указатель.

Вы удивитесь, но в данном коде даже не надо ничего менять в уже добавленных в мапу объектах, достаточно сделать вот так:

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 продолжают возвращать те же значения. Не спрашивайте меня когда это может быть полезно.

Да все верно )

Но автора волновало это "два одинаковых по своему наполнению объекта", а тут без переопределения этих методов никуда.

Как в том анектоде: "В крайнем случае 10 лет и два тонеля"

Тоже мне новости 197затертого года "А вы знаете товарищи , что если в фортране передать константу в процедуру и там её переписать , то она потом поменяет значение ?? "

Данная тема может быть очевидна для вас, как для человека с опытом, но новичкам может быть полезно.

О, в Фортране это было ещё фееричнее! В отладчике (MS Fortran) наводишь мышку на число 3 – а он показывает тултип со значением 5.

вот вам идея для следующей статьи начального уровня - какое будет поведения у HashSet'a объектов класса classA, если в классе classА:

1. Не переопределять ни equals(), ни hashCode().
2. Переопределить только equals().
3. Переопределить только hashCode().
4. Переопределить и equals(), и hashCode().

Следующую статью посветите вопросу почему

new Boolean(true) == Boolean.TRUE

это false

Вы ещё классику вспомните:

#define true false // счастливой отладки!

В классике, емнип, в define ещё рандом был добавлен.

Не, это чуть позже, чтобы не было слишком просто)

Смотрим документацию на 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.

А как рекорд нас спасёт? По прежнему можно значения поля объекта поменять же

Поля у рекордов финальные.

Что, правда что-ли?))
А у вас весь проект на рекордах сразу стал и все остальные типы иммутабельные стали?))

Что, правда что-ли?))

Правда.

А у вас весь проект на рекордах сразу стал и все остальные типы иммутабельные стали?))

Не понял о каком проекте вы говорите.

Видимо о ваших пет проектах, где с появлением рекордов всё поменялось)

С Рекордами ничего принципиально не поменялось для меня, просто стало удобнее обявлять неизменяемые типы. Меньше возможности для ошибки. Легче коммуницировать свои намеренья сдалать этот объект неизменяемым.

Для сложных объектов всегда можно билдер сделать.

Я не люблю мутабл объекты. Слишком много времения я провёл перскакивая с одной части проекта в другую чтобы понять цепочки мутаций.


Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации