Дублирующиеся элементы в java.util.Set

#java #set

#java #установить

Вопрос:

java.util.Set реализации удаляют дублирующиеся элементы.

Как дублирующиеся элементы удаляются внутренне в java.util.Set ?

Ответ №1:

На самом деле, AFAIK из исходных текстов, большинство Set реализаций в java даже не проверяют, содержится ли элемент уже.

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

например, HashSet вызывает put(K,V) внутренний HashMap , который просто вставляет новый объект, перезаписывая старую запись, если она дублируется.

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

1. <E> java.util.Set.of(E... elements) выбрасывает IllegalArgumentException , если найден какой-либо дубликат.

Ответ №2:

Немного углубившись в ваш вопрос, я предполагаю, что вы наблюдаете странное поведение с java.util.HashSet (обычно то, что все используют по умолчанию).

В соответствии с контрактом java.util.Set можно получить один и тот же объект в java.util.HashSet два раза подобным образом:

 import java.util.HashSet;
import java.util.Set;

public class SetTest 
{
  public static void main(String[] args) 
  {
    MyClass myObject = new MyClass(1, "testing 1 2 3");

    Set<MyClass> set = new HashSet<MyClass>();
    set.add(myObject);

    myObject.setHashCode(2);
    set.add(myObject);

    System.out.println(set.size());  // this will print 2.
  }

  private static class MyClass 
  {
    private int hashCode;
    private String otherField;

    public MyClass(int hashCode, String otherField) 
    {    
      this.hashCode = hashCode;
      this.otherField = otherField;
    }

    public void setHashCode(int hashCode) 
    {
      this.hashCode = hashCode;
    }

    public boolean equals(Object obj) 
    {    
      return obj != null amp;amp; obj.getClass().equals(getClass()) amp;amp; ((MyClass)obj).otherField.equals(otherField);
    }

    public int hashCode() 
    {
      return hashCode;
    }
  }
}
  

После указателя из @jitter и взгляда на исходный код вы можете понять, почему это могло произойти.

Как говорит @jitter, java.util.HashSet использует java.util.HashMap внутренне. Когда хэш меняется между первым и вторым добавлением, в java.util.HashMap используется другой сегмент, и объект находится в наборе дважды.

Пример кода может выглядеть немного искаженным, но я видел, как это происходит в естественных условиях с классами домена, где хэш создается из изменяемых полей, а метод equals не синхронизирован с этими полями.

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

1. Изменение объектов в HashSet таким образом, чтобы изменять их результаты hashCode() /equals(), приводит к неопределенному поведению.

2. @Joachim — точно, но это не значит, что этого не происходит. Фактически, методы equals / hashCode, генерируемые популярными IDE, часто приводят к изменению хэш-кода при изменении объекта.

3.Возможно, хэш-код должен измениться, если объект мутировал — в конце концов, он должен быть согласован с equals() , поэтому его необходимо изменить, если объект больше не будет считаться равным своему состоянию до мутации. Реальная проблема здесь заключается в использовании изменяемых объектов в качестве ключей HashMap; настоятельно рекомендуется использовать только неизменяемые объекты, иначе вы подвергнетесь такого рода случайности, которая, вообще говоря, hashCode() должна изменяться по мере изменения изменяемого объекта.

4. @dtsazza — хэш-код необязательно менять при изменении объекта, поскольку равные хэш-коды не подразумевают равенства (и, как показывает приведенный выше пример, изменение хэша после создания экземпляра может быть опасным).

Ответ №3:

Простой способ выяснить это — посмотреть в исходном коде интересующий вас код.

Каждый JDK имеет src.zip включен, который содержит исходный код для общедоступных классов, так что вы можете просто найти исходный код для HashSet и посмотреть 🙂 Я часто использую Eclipse для этого. Запустите его, создайте новый Java-проект, установите JVM в качестве установленного JDK (если нет, вы используете JRE по умолчанию в системе, который не имеет src.zip ) и Ctrl-Shift-T для перехода к HashSet.

Ответ №4:

Прочитайте ваш вопрос более подробно:

Вы не можете добавлять дубликаты из java doc для Set.add() или вы имеете в виду addAll?:

Добавляет указанный элемент в этот набор, если он еще не присутствует (необязательная операция). Более формально, добавляет указанный элемент e к этому набору, если набор не содержит элемента e2, такого что (e==null ? e2==null : e.равно(e2)). Если этот набор уже содержит элемент, вызов оставляет набор неизменным и возвращает false. В сочетании с ограничением на конструкторы это гарантирует, что наборы никогда не будут содержать повторяющихся элементов.

Ответ №5:

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

Ответ №6:

Во-первых, set не «Удаляет» дубликаты, он вообще не позволяет вводить дубликаты.

Позвольте мне познакомить вас с реализацией метода set.add(e).

set.add(e) возвращает логическое значение, указывающее, было ли добавлено e в набор или нет.

Давайте возьмем этот простой код для примера: введите описание изображения здесь

Мы получим x как true и y как false .

Давайте посмотрим, что на самом деле делает add(): введите описание изображения здесь

Итак, HashSet в основном использует HashMap внутренне и отправляет элемент в качестве ключа (и пустой инициализированный объект с именем PRESENT в качестве значения.). Это map.put(k,v) либо возвращает значение null, если ключ никогда не существовал, либо возвращает старое значение, которое имел ключ.

Поэтому при выполнении set.add(1) в первый раз мы получаем null в ответ на map.put(1,PRESENT) , и именно поэтому мы получаем true .

И когда мы вызываем его во второй раз, мы не получаем null в ответ на map.put(1,PRESENT) и, следовательно, set.add(1) возвращает false .

(Вы можете углубиться в метод put, который внутренне вызывает putVal и использует хэш, чтобы определить, существует ли уже ключ, в зависимости от того, какой из них возвращает значение null или old.)

И поскольку мы используем HashMap внутренне, который использует hash для определения уникальности ключа, у нас никогда не будет одного и того же элемента дважды в HashSet.