Производительность равенства в Java (instanceOf против isAssignableFrom) - PullRequest
4 голосов
/ 21 января 2012

Этот вопрос конкретно касается производительности и в некоторой степени краткости различных вариантов реализации.

Я обновил себя этой статьей о реализации права на равенство. Мой вопрос особенно соответствует canEqual (чтобы обеспечить отношение эквивалентности).

вместо перегрузки метода canEquals для использования instanceOf в каждом классе в иерархии (экземпляр paramenter является классом времени компиляции). Почему бы не использовать isAssignableFrom (который разрешается динамически) только в классе верхнего уровня. Делает для очень краткого кода, и вам не нужно перегружать третий метод.

Пока эта альтернатива работает. Есть ли какие-то соображения по поводу производительности, о которых мне нужно знать?

enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET;
}
class Point {

    int x;
    int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }


    @Override public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof Point) {
            Point that = (Point) other;
            //Option 1
            //result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY());
            //Option 2
            //result = (that.getClass().isAssignableFrom(this.getClass()) && this.getX() == that.getX() && this.getY() == that.getY());
            //Option 3
            //result = (getClass() == that.getClass() && this.getX() == that.getX() && this.getY() == that.getY());
        }
        return result;
    }

    @Override public int hashCode() {
        return (41 * (41 + x) + y);
    }

    public boolean canEqual(Object other) {  return (other instanceof Point);   }
}

public class ColoredPoint extends Point{
      Color color;

        public ColoredPoint(int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }

        @Override public boolean equals(Object other) {
            boolean result = false;
            if (other instanceof ColoredPoint) {
                ColoredPoint that = (ColoredPoint) other;
                result = (this.color.equals(that.color) && super.equals(that));
            }
            return result;
        }

        @Override public int hashCode() {
            return (41 * super.hashCode() + color.hashCode());
        }

        @Override public boolean canEqual(Object other) {    return (other instanceof ColoredPoint);   }

    public static void main(String[] args) {
        Object p = new Point(1, 2);
        Object cp = new ColoredPoint(1, 2, Color.INDIGO);

        Point pAnon = new Point(1, 1) {
            @Override public int getY() {
                return 2;
            }
        };

        Set<Point> coll = new java.util.HashSet<Point>();
        coll.add((Point)p);

        System.out.println(coll.contains(p)); // prints true
        System.out.println(coll.contains(cp)); // prints false
        System.out.println(coll.contains(pAnon)); // prints true
    }
}

Ответы [ 7 ]

4 голосов
/ 21 января 2012

Обновление : На самом деле, ваш метод технически недопустим, как я сначала думал, потому что он нарушает контракт симметрии equals для подклассов, которые не переопределяют equals:

Point p = new Point(1, 2);
Point pAnon = new Point(1, 1) {
    @Override public int getY() {
        return 2;
    }
};

System.out.println(p.equals(pAnon)); // prints false
System.out.println(pAnon.equals(p)); // prints true

Причина в том, что p.getClass().isAssignableFrom(pAnon.getClass()) - это true, в то время как обратное, pAnon.getClass().isAssignableFrom(p.getClass()) - false.

Если вы не уверены в этом, попробуйте запустить свой код и сравнить его сверсия в статье: вы заметите, что она печатает true, false, false вместо true, false, true, как в примере в статье.

3 голосов
/ 21 января 2012

, если вы не хотите сравнивать классы разных типов, самый простой, безопасный, краткий и, вероятно, самый эффективный вывод:

(getClass() == that.getClass())
1 голос
/ 21 января 2012

Вот мой второй ответ на уточненный вопрос

Рассмотрим, когда мы вызываем Point.equals(ColoredPoint cp);

Point.equals () сначала проверяет

if (other instanceof Point)...

, что проходит.Из трех представленных вариантов все три проверяют, что другой объект, в данном случае ColoredPoint, удовлетворяет еще одному критерию.Возможны следующие варианты:

  1. будет истинным, только если Point является экземпляром ColoredPoint, который никогда не будет
  2. будет истинным, только если ColoredPoint можно назначить из Point, который никогда не будет
  3. никогда не будет истинным.

С точки зрения производительности (и дизайна) не было никакого смысла в проверке other instanceof Point, потому что фактическое поведение ОП хочет (чего он не смогвыражается в том, что для его конкретного случая использования равенство между этими объектами означает, что они должны быть одного и того же класса.

Поэтому для производительности и дизайна просто используйте

 this.getClass() == that.getClass()

, как было предложеноby @ jthalborn

Когда более поздний кодер увидит instanceof или isAssignableFrom в вашем коде, он будет думать, что подклассам разрешено равняться базовому классу, что полностью вводит в заблуждение.

1 голос
/ 21 января 2012

Все ответы, данные до сих пор, не отвечают на вопрос - но указывают на equals() контракт.Равенство должно быть отношением эквивалентности (транзитивным, симметричным, рефлексивным), и равные объекты должны иметь одинаковый хэш-код.Это прекрасно распространяется на подклассы - при условии, что подклассы сами не переопределяют equals() или hashCode().Таким образом, у вас есть два варианта - либо вы наследуете equals() от Point (так что ColoredPoint экземпляры равны, если они имеют одинаковые координаты, даже если они имеют другой цвет), либо вы переопределяете equals() (и теперь должныубедитесь, что Point и ColoredPoint никогда не равны).

Если вам нужно выполнить поточечное сравнение, не используйте equals() - вместо этого напишите метод pointwiseEquals().

Что бы вы ни выбрали, вам все равно придется выполнить проверку класса в equals().

getClass() == that.getClass()

, безусловно, лучший исполнитель, но он сломается, если вы рассчитываете на равенствотестовые подклассы, которые сами по себе не переопределяют equals() (и на практике единственный способ, которым вы можете гарантировать, - это сделать класс или методы равенства окончательными и вообще не допустить переопределения каких-либо подклассов).Если это выбор между instanceOf и isAssignableFrom, практической разницы нет, оба они фактически выполняют один и тот же тест во время выполнения (единственное отличие состоит в том, что instanceOf может выполнить время компиляциипроверка работоспособности, но в этом случае он ничего не может знать, когда ввод просто Object).В обоих случаях проверка во время выполнения идентична - проверьте целевой класс в перечисленных интерфейсах объекта (что здесь не применимо, поскольку мы не проверяем интерфейс), или поднимитесь по иерархии классов, пока мы не найдемперечислите класс или доберитесь до корня.

1 голос
/ 21 января 2012

См. мой ответ для В чем разница между равенством и эквивалентностью? .

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

Редактировать

Это сводится к тому, x в следующем:

if (other instanceof Point) {
   Point that = (Point) other;

   boolean x = that.getClass().isAssignableFrom(this.getClass());
}

имеет ту же силу, что и getClass() == that.getClass(). Согласно ответу @ waxwing это не так.

Даже если бы это было правильно, я не вижу здесь никакого выигрыша в производительности, позвонив по номеру that.getClass().isAssignableFrom.

0 голосов
/ 01 февраля 2013

Хорошо, у нас здесь есть пример из Эффективной Java (у меня есть 2-е издание 2008 г.).Пример приведен в ITEM 8: OBEY THE GENERAL CONTRACT WHEN OVERRIDING EQUALS, начиная со страницы 37 (я пишу это на случай, если вы захотите проверить).

class ColoredPoint extends Point{}, и есть 2 попытки показать, почему instanceof BAD.Первая попытка была

// Broken - violates symmetry!
@Override public boolean equals(Object o) {
          if (!(o instanceof ColorPoint))
          return false;
          return super.equals(o) && ((ColorPoint) o).color == color;

}

, а вторая была

 // Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
    return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
    return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint)o).color == color;

}

Прежде всего, второй IF никогда не будет достигнут,Если 'o' не является точкой, которая является суперклассом для ColorPoint, как может случиться, что точка не будет ColorPoint ??????

Так что вторая попытка с начала неверна!Где единственный шанс для ИСТИННОГО сравнения - super.equals(o) && ((ColorPoint)o).color == color;, которого недостаточно !!решение здесь будет следующим:

if (super.equals(o)) return true;

  if (!(o instanceof ColorPoint))  
      if ((o instanceof Point)) return this.equals(o); 
      else return false;

  return (color ==((ColorPoint)o).color && this.equals(o));

obj.getClass () используется для очень специфического equals (), но ваша реализация зависит от вашей области видимости.Как вы определяете, что два объекта равны или нет?Реализуйте его, и он будет работать соответственно.

0 голосов
/ 21 января 2012

Я думаю, что ваше решение потерпит неудачу, потому что оно не транзитивное OOPS, симметричное. См. Главу «Эффективная Java»

Point p = new Point(2,3);
ColoredPoint cp = new ColoredPoint(2,3, Color.WHITE);

Я считаю (не запускал ваш код), что

p.equals (cp) верно

но

cp.equals (p) ложно

Хотя я не до конца понимаю ваш код - это относится к canEquals (), который был закомментирован. Короткий ответ: вы должны либо игнорировать цвет для равенства, либо делать то, что предложил @jthalborn.

...