Как использовать Наборы в качестве ключей в Картах Java

У меня есть Карта, которая использует Набор для ключевого типа, как это:

Map<Set<Thing>, Val> map;

Когда я запрашиваю map.containsKey (myBunchOfThings), он возвращает false, и я не понимаю почему. Я могу выполнить итерации через каждый ключ в наборе ключей и проверить, что существует ключ, который (1) имеет тот же хэш-код и (2) равняется () myBunchOfThings.

System.out.println(map.containsKey(myBunchOfThings)); // false.
for (Set<Thing> k : map.keySet()) {
  if (k.hashCode() == myBunchOfThings.hashCode() && k.equals(myBunchOfThings) {
     System.out.println("Fail at life."); // it prints this.
  }
}

Я просто существенно неправильно понимаю контракт для containsKey? Существует ли секрет к использованию наборов (или в более общем плане, наборы) как ключи к картам?

14
задан Gabe Johnson 6 March 2010 в 17:00
поделиться

4 ответа

Ключ не должен изменяться при использовании на карте. В java-документе Map говорится:

Примечание: следует проявлять большую осторожность, если изменяемые объекты используются в качестве ключей карты. Поведение карты не указано { {1}}, если значение объекта изменяется способом, который влияет на сравнения равно , в то время как объект является ключом на карте. Особым случаем этого запрета является то, что для карты не разрешено содержать в качестве ключа.Хотя для карты допустимо содержать в качестве значения , рекомендуется проявлять особую осторожность: методы equals и hashCode больше не определены в такая карта.

Я знал об этой проблеме, но до сих пор не проверял ее. Затем я уточню еще немного:

   Map<Set<String>, Object> map  = new HashMap<Set<String>, Object>();

   Set<String> key1 = new HashSet<String>();
   key1.add( "hello");

   Set<String> key2 = new HashSet<String>();
   key2.add( "hello2");

   Set<String> key2clone = new HashSet<String>();
   key2clone.add( "hello2");

   map.put( key1, new Object() );
   map.put( key2, new Object() );

   System.out.println( map.containsKey(key1)); // true
   System.out.println( map.containsKey(key2)); // true
   System.out.println( map.containsKey(key2clone)); // true

   key2.add( "mutate" );

   System.out.println( map.containsKey(key1)); // true
   System.out.println( map.containsKey(key2)); // false
   System.out.println( map.containsKey(key2clone)); // false (*)

   key2.remove( "mutate" );

   System.out.println( map.containsKey(key1)); // true
   System.out.println( map.containsKey(key2)); // true
   System.out.println( map.containsKey(key2clone)); // true

После изменения key2 карта его больше не содержит. Мы могли бы подумать, что карта "индексирует" данные при добавлении, и тогда мы могли бы ожидать, что она все еще содержит клон key2 (строка, отмеченная * ). Но, как ни странно, это не так.

Итак, как сказано в java-документе, ключи не должны изменяться, иначе поведение не определено . Период.

Думаю, именно это и происходит в вашем случае.

21
ответ дан 1 December 2019 в 09:01
поделиться

Вы изменили набор после вставки? Если да, то возможно, что набор был отсортирован в другой ведро, а не в тот, в котором он ищет. При итерации он действительно находит ваш набор, поскольку просматривает всю карту.

Я полагаю, что в контракте для HashMap говорится, что вам не разрешается изменять хэш-код для объектов, используемых в качестве ключа,

2
ответ дан 1 December 2019 в 09:01
поделиться

Вы должны стремиться использовать неизменяемые типы в качестве ключей для Map s. Коллекции и наборы, как правило, очень легко изменяются, поэтому обычно использовать этот способ - плохая идея.

Если вы хотите использовать много значений ключа в качестве ключа Map , вам следует использовать реализацию класса, разработанную для этой цели, например Apache Commons Collections MultiKey .

Если вам действительно необходимо использовать набор или коллекцию в качестве ключа, сначала сделайте его неизменяемым ( Collections.unmodifiableSet (...) ), а затем не сохраняйте ссылку на изменяемый объект поддержки.

Еще одна трудность с использованием Коллекций в качестве ключей заключается в том, что они могут быть построены в другом порядке. Только отсортированная коллекция будет иметь высокую вероятность совпадения. Например, если вы используете последовательно упорядоченный ArrayList , но во второй раз создаете список другим способом, он не будет соответствовать ключу - хэш-код и порядок значений будут другими.

РЕДАКТИРОВАТЬ : Я исправлюсь в этом утверждении ниже, так как мне никогда не приходилось использовать Set для кет-игры. Я только что прочитал часть реализации hashCode в AbstractHashSet. При этом используется простая сумма всех значений, поэтому она не зависит от порядка. Equals также проверяет, что один набор содержит все значения из другого набора. Однако это все еще верно для других видов коллекций в Java (порядок ArrayList имеет значение).

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

7
ответ дан 1 December 2019 в 09:01
поделиться

Вы передаете точный набор (набор, который хотите найти) при сравнении ключа?

0
ответ дан 1 December 2019 в 09:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: