Как HashSet не позволяет дублировать?
Я прошел через add
метод HashSet
. Отмечено, что
Если этот набор уже содержит элемент, вызов оставляет неизменным и возвращает false.
Но метод add
внутренне сохраняет значения в HashMap
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
В методе put
HashMap
указано, что
Связывает указанное значение с указанным ключом на этой карте. Если ранее карта содержала отображение для ключа, старое значение заменяется.
Итак, если метод put
HashMap
заменяет старое значение, как метод HashSet
add
оставляет набор неизменным в случае повторяющихся элементов?
Ответы
Ответ 1
PRESENT
- это просто фиктивное значение - на самом деле набор не имеет значения. Что касается набора, это ключи карты. Итак, логика выглядит так:
Set.add(a):
map.put(a, PRESENT) // so far, this is just what you said
the key "a" is in the map, so...
keep the "a" key, but map its value to the PRESENT we just passed in
also, return the old value (which we'll call OLD)
look at the return value: it OLD, != null. So return false.
Теперь тот факт, что OLD == PRESENT
не имеет значения - и обратите внимание, что Map.put
не изменяет ключ, просто значение, сопоставленное этому ключу. Так как клавиши карты - это то, что действительно волнует Set
, Set
не изменяется.
Фактически, произошли некоторые изменения в базовых структурах Set
- он заменил отображение (a, OLD)
на (a, PRESENT)
. Но это не видно извне реализации Set
. (И как это бывает, это изменение даже не является реальным изменением, так как OLD == PRESENT
).
Ответ 2
Ответ, который вы, возможно, смотрите, сводится к тому, что хэш-карта поддержки отображает элементы набора в значение PRESENT
, которое определено в HashSet.java следующим образом:
private static final Object PRESENT = new Object();
В исходном коде для HashMap.put
мы имеем:
386 public V put(K key, V value) {
387 if (key == null)
388 return putForNullKey(value);
389 int hash = hash(key.hashCode());
390 int i = indexFor(hash, table.length);
391 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
392 Object k;
393 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
394 V oldValue = e.value;
395 e.value = value;
396 e.recordAccess(this);
397 return oldValue;
398 }
399 }
400
401 modCount++;
402 addEntry(hash, key, value, i);
403 return null;
404 }
Поскольку этот ключ уже существует, мы берем раннее возвращение в строке 397. Но вы можете подумать, что происходит изменение карты на строке 395, в которой, как представляется, мы меняем значение карты запись. Однако значение value
равно PRESENT
. Но поскольку PRESET
является статическим и окончательным, есть только один такой экземпляр; и поэтому присваивание e.value = value
фактически не изменяет карту и, следовательно, множество вообще!
Ответ 3
Как вы видите, метод HashSet.add
добавляет элемент в HashMap.put
в качестве ключа не как значение. Значение заменяется на HashMap
не на ключ.
Ответ 4
См. HashMap#put
:
Связывает указанное значение с указанным ключом на этой карте. Если на карте ранее содержалось отображение для ключа, старое значение заменить.
Он заменяет ключ новым значением, таким образом, никакие дубликаты не будут в HashSet
.
Ответ 5
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
e - это ключ, поэтому, если e уже присутствует put
не вернется null
. Следовательно, add
вернет false.
JavaDoc для put
:
предыдущее значение, связанное с ключом, или null, если не было сопоставления для ключа. (Нулевой возврат также может указывать на то, что ранее связанная карта null с ключом.)
Ответ 6
Из javadocs для HashMap.put(),
"Связывает указанное значение с указанным ключом на этой карте. Если ранее карта содержала отображение для ключа, старое значение заменяется."
Таким образом, значение карты будет заменено (это постоянное статическое поле в классе HashSet и, следовательно, тот же экземпляр заменяется), а ключ карты остается нетронутым (что фактически является элементом Set collection)