Java: HashSet, что такое концепция сравнения?
Исходя из мира C++, я нахожу чтение документации HashSet несколько трудным:
В C++ вы должны иметь:
что в свою очередь указывает на:
Что делает очевидным требование к типу элемента, обрабатываемого std::set
, Мой вопрос: каковы требования к типу (E) элементов, поддерживаемых Set
на яве?
Вот короткий пример, который я не понимаю:
import gdcm.Tag;
import java.util.Set;
import java.util.HashSet;
public class TestTag
{
public static void main(String[] args) throws Exception
{
Tag t1 = new Tag(0x8,0x8);
Tag t2 = new Tag(0x8,0x8);
if( t1 == t2 )
throw new Exception("Instances are identical" );
if( !t1.equals(t2) )
throw new Exception("Instances are different" );
if( t1.hashCode() != t2.hashCode() )
throw new Exception("hashCodes are different" );
Set<Tag> s = new HashSet<Tag>();
s.add(t1);
s.add(t2);
if( s.size() != 1 )
throw new Exception("Invalid size: " + s.size() );
}
}
Приведенный выше простой код завершается ошибкой с:
Exception in thread "main" java.lang.Exception: Invalid size: 2 at TestTag.main(TestTag.java:42)
Из моего прочтения документации для Set необходимо реализовать только оператор equals:
Чего мне не хватает в документации?
4 ответа
Я просто попытался воспроизвести вашу проблему, и, возможно, вы просто неправильно переопределили equals и / или hashSet.
Посмотрите на мою неправильную реализацию тега:
public class Tag {
private int x, y;
public Tag(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Tag tag) {
if (x != tag.x) return false;
return y == tag.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
Выглядит вполне нормально, не так ли? Но проблема в том, что я на самом деле не переопределяю правильный метод equals, я перегрузил его своей собственной реализацией.
Для корректной работы equals должен выглядеть так:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
if (x != tag.x) return false;
return y == tag.y;
}
Чего мне не хватает в документации?
Вы смотрите не на ту часть документации.
C++ set
является "отсортированным набором уникальных объектов" и "обычно реализуется как красно-черные деревья".
В Java Set
это более абстрактная концепция (это интерфейс, а не класс) с несколькими реализациями, в частности, HashSet
и TreeSet
(игнорирование параллельных реализаций).
Как вы можете догадаться по названию, Java TreeSet
является эквивалентом C++ set
,
Что касается требований, HashSet
использует hashCode()
а также equals()
методы. Они определены на Object
класс, и должен быть переопределен на классах, которые должны быть в HashSet
или как ключи в HashMap
,
За TreeSet
и ключи TreeMap
, у вас есть два варианта: предоставить Comparator
при создании TreeSet
(аналогично C++) или объекты должны реализовывать Comparable
интерфейс.
Я предполагаю, что это была просто комбинация неудачи и неправильного понимания требования HashSet. Спасибо @christophe за помощь, я понял проблему, когда попытался добавить в свой сгенерированный Swig класс Tag.java:
@Override
public boolean equals(Object o) {
}
Я получил следующее сообщение об ошибке:
gdcm/Tag.java:78: error: method does not override or implement a method from a supertype
@Override
^
1 error
1 warning
Что означало, что моя ошибка была просто:
- Во-первых, у меня была неправильная подпись:
boolean equals(Object o)
знак равноboolean equals(Tag t)
Намек был просто использовать @Override
ключевое слово.
Для тех, кто запрашивает исходный код, код Java генерируется с помощью swig. Оригинальный код C++ находится здесь:
Размер равен 2 (не равен 1), поэтому он генерирует исключение (ваша последняя строка кода). Изменить: Возможно, я не понял тебя раньше. Как правило, тип E - это то, что вы выбираете. Например: положить объект или "?" Заставит вас поставить любой тип. В вашем случае вы выбираете Tag как тип E, поэтому он должен быть содержимым набора.