C # / Java: правильная реализация CompareTo, когда Equals проверяет ссылочную идентичность - PullRequest
0 голосов
/ 13 марта 2010

Я считаю, что этот вопрос в равной степени относится и к C #, и к Java, поскольку оба требуют, чтобы {c, C} ompareTo согласовывалось с {e, E} quals:

Предположим, я хочу, чтобы метод equals () был таким же, как проверка ссылок, т. Е .:

public bool equals(Object o) {
    return this == o;
}

В таком случае, как я могу реализовать compareTo (Object o) (или его общий эквивалент)? Отчасти это легко, но я не уверен насчет другой части:

public int compareTo(Object o) {
    MyClass other = (MyClass)o;
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

Я не могу просто слепо вернуть 1 или -1, потому что решение должно соответствовать обычным требованиям сравнения. Я могу проверить все поля экземпляра, но если они все равны, я все же хотел бы, чтобы CompareTo возвращало значение, отличное от 0. Должно быть верно, что a.compareTo (b) == - (b.compareTo (a) ), и порядок должен оставаться согласованным до тех пор, пока состояние объектов не изменится.

Однако меня не волнует порядок действий между вызовами виртуальной машины. Это заставляет меня думать, что я мог бы использовать что-то вроде адреса памяти, если бы я мог получить это. С другой стороны, возможно, это не сработает, потому что сборщик мусора может решить переместить мои объекты.

hashCode - еще одна идея, но я бы хотел, чтобы что-то было всегда уникальным, а не просто уникальным.

Есть идеи?

Ответы [ 4 ]

3 голосов
/ 13 марта 2010

Прежде всего, если вы используете Java 5 или выше, вы должны реализовать Comparable<MyClass> вместо простого старого Comparable, поэтому ваш метод compareTo должен принимать параметры типа MyClass, а не Object :

public int compareTo(MyClass other) {
    if (this == other) {
        return 0;
    } else {
        int c = foo.CompareTo(other.foo)
        if (c == 0) {
            // what here?
        } else {
            return c;
        }
    }
}

На ваш вопрос Джош Блох в «Эффективной Java» (глава 3, пункт 12) говорит:

Разработчик должен обеспечить sgn (x.compareTo (y)) == -sgn (y.compare- К (х)) для всех х и у. (Это подразумевает, что x.compareTo (y) должен выдать исключение тогда и только тогда, когда y.compareTo (x) выдает исключение.)

Это означает, что если c == 0 в приведенном выше коде, вы должны вернуть 0.

Это, в свою очередь, означает, что у вас могут быть объекты A и B, которые не равны, но их сравнение возвращает 0. Что мистер Блох должен сказать по этому поводу?

Настоятельно рекомендуется, но не обязательно, чтобы (x.compareTo (y) == 0) == (x.equals (y)). Вообще говоря, любой класс, который реализует Сопоставимый интерфейс и нарушающий это условие должен четко указывать этот факт. Рекомендуемый язык: «Примечание. порядок, несовместимый с равными. ”

И

Класс, метод CompareTo которого накладывает порядок что не согласуется с равенством будет по-прежнему работать, но отсортированные коллекции, содержащие элементы класса могут не подчиняться генеральному контракту соответствующей коллекции интерфейсы (Коллекция, Набор или Карта). Это потому что генеральные контракты для этих интерфейсов определены с точки зрения метода equals, но отсортированные коллекции используйте тест на равенство, наложенный CompareTo вместо равных. Это не катастрофа, если это произойдет, но об этом нужно знать.

Обновление: Так что ИМХО с вашим текущим классом вы не можете сделать compareTo совместимым с equals. Если вам действительно нужно это иметь, единственный способ, которым я вижу, - это ввести нового члена, который бы дал строгий естественный порядок вашему классу. Тогда в случае, если все значащие поля двух объектов сравниваются с 0, вы все равно можете определить порядок двух на основе их значений специального порядка.

Этот дополнительный член может быть счетчиком экземпляров или отметкой времени создания. Или вы можете попробовать использовать UUID .

2 голосов
/ 14 марта 2010

В Java или C #, вообще говоря, нет фиксированного упорядочения объектов. Экземпляры могут перемещаться сборщиком мусора при выполнении вашего CompareTo или операции сортировки, использующей ваш CompareTo.

Как вы заявили, хеш-коды, как правило, не уникальны, поэтому их нельзя использовать (два разных экземпляра с одинаковым хеш-кодом возвращают вас к исходному вопросу). А реализация Java Object.toString, которая, как полагают многие, обнаруживают идентификатор объекта (MyObject @ 33c0d9d), является не чем иным, как именем класса объекта, за которым следует хеш-код. Насколько я знаю, ни JVM, ни CLR не имеют понятия идентификатора экземпляра.

Если вы действительно хотите, чтобы ваши классы имели последовательное упорядочение, вы можете попробовать использовать возрастающее число для каждого создаваемого вами нового экземпляра. Напоминаем, что увеличение этого счетчика должно быть поточно-ориентированным, поэтому оно будет относительно дорогим (в C # вы можете использовать Interlocked.Increment).

1 голос
/ 13 марта 2010

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

На самом деле я был бы более склонен изменить поведение равных, чтобы отразить их сравнение, а не наоборот. Для большинства целей, которые я могу придумать, это будет более естественным.

0 голосов
/ 13 марта 2010

На мой взгляд, с общим эквивалентом легче работать, это зависит от ваших внешних требований, это пример IComparable<MyClass>:

public int CompareTo(MyClass other) {
    if (other == null) return 1;
    if (this == other) {
        return 0;
    } else {
        return foo.CompareTo(other.foo);
    }
}

Если классы равны или если foo равен, это конец сравнения, если только нет чего-то вторичного для сортировки, в этом случае добавьте его в качестве возврата, если foo.CompareTo(other.foo) == 0

Если у ваших классов есть ID или что-то еще, сравните их с вторичными, иначе не беспокойтесь об этом ... коллекция, в которой они хранятся, и порядок поступления в эти классы для сравнения - вот что определить окончательный порядок в случае равных объектов или равных object.foo значений.

...