Почему equals и hashCode были определены в Object?

Какова причина принятия решения о включении этих методов в java.lang.Object? Равенство и хеширование не имеют смысла для многих классов.

Логичнее было бы сделать два интерфейса:

interface Equalable {
    boolean equals(Equalable other);
}

interface Hashable extends Equalable {
    int hashCode();
}

Например, определение HashSet может выглядеть как

class HashSet<T extends Hashable> ...

Это предотвратило бы одну из общих ошибок начинающих - использование набора элементов без реализации equals/hashCode.

Ответы

Ответ 1

Когда мы реализуем Interface we inject (or accept) контракт, определенный интерфейсом.

Equalable и Hashable - два разных контракта. Но если мы посмотрим внимательно, то мы увидим, что оба они зависят друг от друга, а это означает, что они являются частью single interface, что-то вроде EqualableAndHashable.

Теперь очевидный вопрос: должны ли они быть частью этого нового интерфейса EqualableAndHashable или Object?

Позвольте узнать. Нам нужно == (equal operator) проверить равенство двух объектов. Оператор == подтверждает, равны ли значения/ссылки для двух разных примитивов/объектов. Но на это не всегда можно ответить, просто проверив с помощью оператора ==.

Теперь вопрос заключается в том, следует ли вводить это равенство which is also a contract через интерфейсы или часть класса Object?

Если мы посмотрим, мы не можем просто сказать что-то вроде:

TypeX не гарантирует договор равенства.

Это станет хаосом, если некоторые типы объектов предлагают равенство, а некоторые - нет. Это означает, что объект TypeX должен соблюдать договор равенства, который справедлив и для всех других типов объектов. Таким образом, он не должен вводить равенства из интерфейса, потому что равенство должно быть частью контракта для любого объекта по умолчанию, иначе оно создаст хаос.

Итак, нам нужны объекты, чтобы реализовать реализацию equals. Но он не может реализовать только метод equals, он также должен реализовать метод hashcode.

Ответ 2

Реализация по умолчанию в java.lang.Object имеет смысл. Часто, это достаточно хорошо. В JPA/веб-приложениях я нахожусь очень редко, если когда-либо переопределяют равные и hashCode.

Лучше может быть вопрос: для объектов неизменяемых значений, таких как String, Long и т.д., почему вы не можете переопределить оператор == для вызова equals(), как вы можете в С#? Я видел гораздо больше ошибок из-за этого, чем у меня по умолчанию equals/hashCode не делает правильные вещи. Например,

Long x = obj.getId(); 
Long y = obj2.getId();  
if (x == y) { // oops, probably meant x.equals(y)! }
Тем не менее, справедливый вопрос заключается в том, почему методы по умолчанию не блокируются за интерфейсом тегирования, например, по умолчанию Object.clone(). Там реализация по умолчанию, но вы должны явно признать, что хотите ее использовать, реализовав Cloneable. Так же легко мог быть аналогичный интерфейс тегов, например Collectible или Equatable, а затем подписи для методов коллекций могли бы быть Equatable вместо Object.

Ответ 3

(Лично, если бы они были в интерфейсе, я бы поставил их там, чтобы избежать по крайней мере одного класса ошибок equals/hashCode.)

Я думаю, вам нужна реализация в Object, как механизм возврата, означающий, что все будет иметь реализацию в любом случае, интерфейс или нет.

Я подозреваю, что многие из них историчны; программирование Java сегодня выглядит немного иначе, чем программирование Java тогда.

Ответ 4

Mhh не уверен, но когда Java 1.0 была выпущена, дженерики еще не существовали. Они добавлены в Java 5.0 в 2004 году.. поэтому ваше предложение не может быть реализовано для Java 1.0

Ответ 5

Первоначально на Java не было никаких дженериков. Это было сработано, позволяя любому Object быть членом любой коллекции, и, таким образом, любой Object нужен hashCode и equals. К этому времени он слишком укоренился, чтобы измениться.

Ответ 6

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

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

Ответ 7

Если у вас есть список объектов, и вы вызываете метод contains, что должен делать Java? Я думаю, что реализация по умолчанию (сравнение ссылок) - это достойное решение. Таким образом вам не придется реализовывать себя equals и hashcode для каждого класса, который вы используете в коллекции.

Ответ 8

Действительно, это просто для удобства, и так лучше. Ну, подумайте о том, что потребуется, чтобы сделать равенство объектов, если у вас не было метода .equals:

AreEqual(Object obj1,Object obj2) {
  if(!obj1 instanceof Equalable) return false;
  if(!obj2 instanceof Equalable) return false;
  return ((Equalable)(obj1).equals((Equalable)obj2);
}

Это верно даже для hashCode. Иногда ссылочного равенства достаточно. Если вы сделали HashSet только принимающими объекты, которые реализуют Hashable, тогда вам придется явно создавать классы Hashable, даже если вам требуется только ссылочное равенство. И вы уменьшили универсальность в противном случае отличной структуры данных.

Лучше иметь объект по умолчанию (а иногда и достаточный).equals и .hashCode функцию и вызвать несколько проблем для новых пользователей, чем заставить частых пользователей перетаскивать язык через более бюрократическую ленту.

Ответ 9

Любой объект, независимо от его типа, может разумно ответить, эквивалентен ли он любому другому объекту, даже если другой тип объекта тот, о котором он никогда не слышал. Если он никогда не слышал о другом типе объекта, этого факта достаточно, чтобы сообщить, что он не эквивалентен последнему объекту.