переопределение метода равных при работе с наследованием

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

Однако в отношении «Эффективной Java» мое понимание таково (и я новичок в этом, поэтому я вполне могу ошибаться!) Блох утверждает, что в конечном итоге оба могут быть проблематичными: «Невозможно расширить инстанцируемый класс и добавить компонент value, сохраняя при этом контракт равных, если вы не хотите отказаться от преимуществ объектно-ориентированной абстракции »., Затем рекомендует «отдать предпочтение композиции, а не наследованию ».

Итак, я имею дело с этой иерархией классов: AbstractClass, ConcreteClass1 и ConcreteClass2. ConcreteClass1 расширяет AbstractClass, а ConcreteClass2 расширяет ConcreteClass1. На данный момент только AbstractClass переопределяет метод equals.

Итак, в AbstractClass:

public abstract class AbstractClass {
        private String id;


        public boolean equals(Object other) {
            return other != null && other.getClass().equals(getClass())
                    && id.equals(((AbstractClass) other).id);
        }

    }

И в ConcreteClass1 у меня есть:

public class ConcreteClassOne extends AbstractClass
{
  private final AbstractClass parent;

  public ConcreteClassOne( String anId, AbstractClass aParent )
  {
    super( anId );

    parent = aParent;
  }

}

Наконец в ConcreteClassTwo у меня есть:

public class ConcreteClassTwo extends ConcreteClassOne
{
  private static int nextTrackingNo = 0;

  private final int trackingNo;

  public ConcreteClassTwo ( String anId )
  {
    super( anId, null );

    trackingNo= getNextTrackingNo();
  }
}

Поэтому я считаю, что мне нужно переопределить метод equals как в ConcreteClassOne, так и в ConcreteClassTwo, чтобы включить значимые поля parent и trackingNo. Я'Мне не разрешено менять дизайн, поэтому использование композиции не вариант. Какие-либо предложения?

 user239760721 мая 2013 г., 15:28
@MarkoTopolnik Вы поняли мою точку зрения!
 Supericy21 мая 2013 г., 15:17
И что'вопрос / проблема? Нет ничего плохого в переопределении метода equals.
 McDowell21 мая 2013 г., 15:33
 Marko Topolnik21 мая 2013 г., 15:18
Вопрос в том, чтобы перевесить равных при соблюдении своего контракта.
 user239760721 мая 2013 г., 16:47
@ McDowell спасибо, яЯ встречал это, но, честно говоря, до сих порУ меня была возможность изучить его полностью.

Ответы на вопрос(3)

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

Примером последней ситуации может быть базовый класс ImmutableSquareFloatMatrix с методамиint GetSize() а такжеfloat GetCell(int row, int column), Обычная реализация будет иметь массив значений (размер * размер) с плавающей точкой, но можно также иметь, например,ZeroMatrix а такжеIdentityMatrix классы, чье единственное поле указало размер,ConstantMatrix класс с полем, определяющим размер, и полем, указывающим значение, которое должно быть возвращено для каждой ячейки,DiagonalMatrix класс с одномерным массивом, просто содержащий элементы для диагонали (GetCell метод будет возвращать ноль для всего остального) и т. д.

Учитывая два экземпляра классов, полученных изImmutableSquareFloatMatrixМожно было бы сравнить их, сравнивая их размеры и затем сравнивая все значения в них, но во многих случаях это было бы неэффективно. Если один из объектов сравниваетсязнает» о другом объектеС типом, может быть возможно значительно повысить эффективность. Если ни один объект не знает о другом, возврат к методу сравнения по умолчанию может быть медленным, но в любом случае даст правильные результаты.

Работоспособный подход к решению этой ситуации может заключаться в том, чтобы базовый тип реализовывалequals2 метод, который возвращает 1, если его специальное знание другого объекта означает, что он может сказать, что он равен, -1, если он может сказать, что он не равен, или 0, если он не могРассказать Если любой типequals2 метод знает, что онинеравны, онинеравны. В противном случае, если кто-то знает, что ониравны ониравны. В противном случае проверьте равенство с помощью сравнения по ячейкам.

Если у вас естьequals оба вConcreteClassOne а такжеConcreteClassTwo тогда симметрияequals сломано:

Object c1 = new ConcreteClassOne(),
       c2 = new ConcreteClassTwo();
System.out.println("c1=c2? " + c1.equals(c2)");
System.out.println("c2=c1? " + c2.equals(c1)");

Теперь, если вы реализуетеequals обычный способ, это собирается напечатать

true
false

потому что вc2.equals у тебя естьinstanceof ConcreteClassTwo, что не удается дляc1, но в противоположном случае аналогичная проверка проходит.

 user239760721 мая 2013 г., 15:30
Как также отметил Блох!
 Marko Topolnik21 мая 2013 г., 15:32
Да, я думал, что вам нужно дополнительное объяснение / подтверждение этого пункта. Блох прав и тамВы ничего не можете сделать, кроме как позволять равенство только при условии.a.getClass()==b.getClass()
 user239760721 мая 2013 г., 15:35
Да, как я уже говорил, я новичок в этой концепции, поэтому подтверждение того, что я понял из БлохаТочка зрения приветствуется.

Самый простой подход состоит в расширении equals () как в конкретном, так и в абстрактном классах.

public class ConcreteClassTwo extends ConcreteClassOne {
    public boolean equals(Object other) {
        boolean rv = super.equals( other );
        if ( other instanceof ConcreteClassTwo ) {
           rv = rv && (this.trackingNo == ((ConcreteClassTwo) other).trackingNo);
        }
        return rv;
    }
}
 Marko Topolnik21 мая 2013 г., 16:19
Но вы нарушаете транзитивность, поэтому ничего особенного здесь не получается.
 Andy Thomas22 мая 2013 г., 16:03
Вы, кажется, ошибаетесь. Увидетьideone.com/3yMrU8, Тем не менее, мои предпочтения будут (для того, чтобы) избежать этой проблемы, сделав не листовые классы абстрактными; иначе избегайте добавления компонента значения в подкласс конкретного класса; иначе используйте getClass () вместо instanceof во всей иерархии классов.
 Marko Topolnik22 мая 2013 г., 09:50
Если у вас есть ,c2ac1, а такжеc2b (экземпляры ConcreteClassTwo, One и Two соответственно), вы можете иметьc2a.equals(c1) а такжеc1.equals(c2b) без гарантии.c2a.equals(c2b)
 Andy Thomas21 мая 2013 г., 16:46
@MarkoTopolnik - я исправлял ваш предыдущий комментарий к этому ответу. Можете ли вы привести пример проблемы транзитивности, которую вывидишь? [Редактировать: Добавлено пропущенное выражение возврата.]
 user239760721 мая 2013 г., 16:41
@MarkoTopolnik так же, как Блох "композиция над наследством " окончательное последнее решение? то есть в данном случае это вопрос проектирования, а не реализации.
 user239760721 мая 2013 г., 15:26
Спасибо! И что касается instanceof и getClass (), могу ли я сказать это правильно?безопаснее придерживаться getClass ()? т.е. объекты двух разных подклассов никогда не должны быть одинаковыми. скажем, если я расширяю ConcreteClass1, чтобы иметь ConcreteClass3, объекты, созданные из CC2 и CC3, не равны, даже если они имеют одинаковое содержимое.
 Andy Thomas21 мая 2013 г., 15:53
@ user2397607 - В твоем случае да. В общем, Bloch предпочитает instanceof getClass (), чтобы поддерживать тривиальные подклассы, которые нет добавить любое состояние. Но здесь рефлексивность удовлетворяет вашей реализацией AbstractClass.equals () и моей реализацией ConcreteClassTwo.equals (). При этом c1.equals (c2) == c2.equals (c1).
 user239760721 мая 2013 г., 16:48
@ AndyThomas-Cramer заметил это :)

Ваш ответ на вопрос