четверг, 15 декабря 2011 г.

Переопределение equals и hashCode

Есть у класса Object такой метод, как equals, который используется для сравнения двух объектов. По умолчанию он выглядит так:
public boolean equals(Object obj){
 return (this == obj);
}
Т.е. просто сравниваются две ссылки(прямо как оператор "=="). Понятно, что обычно, это не то, что нам надо. Обычно два объекта равны, если основные данные их равны. Чтобы это понимали не только мы, но и компилятор, нам и нужно переопределять метод equals. Существует несколько свойств, которым должен удовлетворять наш переопределенный метод, а именно:
  1. Симметричность. Т.е. если для каких-либо объектов x и y x.equals(y) возвращает true, то и y.equals(x) должен возвращать true.
  2. Рефлексивность. Для любого объекта x x.equals(x) должен возвращать true.
  3. Постоянство. Для любых объектов x и y x.equals(y) возвращает одно и тоже, если информация, используемая в сравнениях, не меняется.
  4. Транзитивность. Для любых объектов x, y и z, если x.equals(y) вернет true и y.equals(z) вернет true, то и x.equals(z) должен вернуть true.
В этих свойствах подразумевается, что объекты не null. А для null всё просто
  • Для любого не null объекта x x.equals(null) должен возвращать false.
Итак, предположим у нас есть такой вот класс:

public class MyClass{
    private int x;
    public MyClass(int x){ this.x = x; }
    public int getX(){ return x; }
}
Очевидно, нам надо, чтобы equals возвращал true тогда и только тогда, когда поля x у объектов равны. Что ж, переопределенный метод будет выглядеть примерно так:
    @Override
    public boolean equals(Object obj){
        if (this == obj)
            return true;
        if (!(obj instanceof MyClass))
            return false;
        MyClass myClass = (MyClass)obj;
        return (x == myClass.getX());
    }
Кстати, не забывайте, что тип аргумента должен быть именно Object, а не MyClass. В этом случае это будет уже не переопределение(overriding), а перегрузка(overloading). Именно поэтому, при переопределении следует использовать конструкцию @Override, она проверит, действительно ли ваш метод переопределяет метод суперкласса, и, если это не так, выдаст ошибку.
Пройдемся по коду. Сначала идет проверка на свойство №2(рефлексивность). Затем мы должны сравнить наши поля x, но т.к. в метод передается Object, то нам надо делать приведение типа. А вдруг нам передали не MyClass? Тут нам пригодится instanceof. x instanceof y проверяет: является ли x экземпляром y. Однако конструкция instanceof не только позволяет нам отбросить неверные объекты, но и проверяет наше свойство о null-объектах, т.к. x instanceof y возвращает false, если x - null. Ну а дальше мы непосредственно проверяем наши поля на равенства.
И вроде все бы ничего, переопределили equals, всё работает. Однако, переопределяя equals вы должны позаботиться еще об одном методе класса Object:
public native int hashCode()
Существуют следующие соглашения, относительно этих двух методов:
  • Всякий раз, когда метод вызывается у одного и того же объекта во время выполнения приложения, он должен возвращать одно и то же число, если используемая информация не изменяется. hashCode может возвращать разные значения для идентичных объектов в различных экземплярах приложения.
  • Если два объекта равны, согласно equals, то их hashCode должны возвращать одинаковые значения.
  • Обратное требование необязательно. Два неравных объекта могут возвращать одинаковый hashCode. Однако для повышения производительности, лучше, чтобы разные объекты возвращали разные коды.
Итак, равные объекты должны иметь одинаковые хэш-коды. Лучший способ гарантировать это - использовать для вычисления хэш-кода те же данные, что участвуют в сравнении.
Метод hashCode используется в hash-коллекциях(например HashSet), и чем меньше будет коллизий(одинаковый код при разных объектах) тем эффективнее эти коллекции будут работать с объектами вашего класса.
В приведенном выше классе MyClass хэш-кодом может выступать само значение x. Это совсем тривиальный случай. А что, если у нас, наряду с числом x, будет например строка s.
Часто используют примерно такой подход:
    @Override
    public int hashCode(){
        int code = 11;
        int k = 7;
        code = k*code + x;
        code = k*code + s.hashCode();
        return code;
    }
Думаю принцип понятен. Всем удачи!

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

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.