Подтвердить что ты не робот

Почему compareTo возвращает целое число

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

Это по историческим причинам или согласованности с другими языками? При просмотре подписи compareTo различных языков он возвращает int.

Почему он не возвращает перечисление. Например, в С# мы могли бы сделать:

enum CompareResult {LessThan, Equals, GreaterThan};

и:

public CompareResult CompareTo(Employee other) {
    if (this.Salary < other.Salary) {
         return CompareResult.LessThan;
    }
    if (this.Salary == other.Salary){
        return CompareResult.Equals;
    }
    return CompareResult.GreaterThan;
}

В Java после этой концепции были введены перечисления (я не помню о С#), но это могло быть разрешено дополнительным классом, таким как:

public final class CompareResult {
    public static final CompareResult LESS_THAN = new Compare();
    public static final CompareResult EQUALS = new Compare();
    public static final CompareResult GREATER_THAN = new Compare();

    private CompareResult() {}
}  

и

interface Comparable<T> {
    Compare compareTo(T obj);
}

Я спрашиваю об этом, потому что я не думаю, что int хорошо отражает семантику данных.

Например, в С#,

l.Sort(delegate(int x, int y)
        {
            return Math.Min(x, y);
        });

и его близнец в Java 8,

l.sort(Integer::min);

компилируется как потому, что Min/min соблюдает контракты интерфейса компаратора (берут два ints и возвращают int).

Очевидно, что результаты в обоих случаях не ожидаются. Если тип возврата был Compare, это вызвало бы ошибку компиляции, заставив вас реализовать "правильное" поведение (или, по крайней мере, вы знаете, что вы делаете).

Много семантики теряется с этим типом возвращаемого значения (и потенциально может вызвать некоторые трудные ошибки), поэтому зачем его создавать так?

4b9b3361

Ответ 1

[Этот ответ для С#, но, вероятно, это также яблоки к Java в некоторой степени.]

Это для целей истории, производительности и удобства чтения. Это потенциально увеличивает производительность в двух местах:

  • Где выполняется сравнение. Часто вы можете просто вернуть "(lhs - rhs)" (если значения представляют собой числовые типы). Но это может быть опасно: см. Ниже!
  • Вызывающий код может использовать <= и >= для естественного представления соответствующего сравнения. Это будет использовать одну инструкцию IL (и, следовательно, процессор) по сравнению с использованием перечисления (хотя есть способ избежать накладных расходов перечисления, как описано ниже).

Например, мы можем проверить, является ли значение lhs меньше или равно значению rhs следующим образом:

if (lhs.CompareTo(rhs) <= 0)
    ...

Используя перечисление, это будет выглядеть так:

if (lhs.CompareTo(rhs) == CompareResult.LessThan ||
    lhs.CompareTo(rhs) == CompareResult.Equals)
    ...

Это явно менее читаемо и также неэффективно, так как он делает сравнение дважды. Вы можете исправить неэффективность, используя временный результат:

var compareResult = lhs.CompareTo(rhs);

if (compareResult == CompareResult.LessThan || compareResult == CompareResult.Equals)
    ...

Он все еще намного менее читаемый ИМО - и он еще менее эффективен, поскольку он выполняет две операции сравнения вместо одной (хотя я свободно признаю, что, скорее всего, такая разница в производительности редко будет иметь значение).

Как показывает разнагул ниже, вы можете сделать это только с помощью одного сравнения:

if (lhs.CompareTo(rhs) != CompareResult.GreaterThan)
    ...

Таким образом, вы можете сделать его достаточно эффективным - но, конечно, читаемость все еще страдает. ... != GreaterThan не так ясно, как ... <=

(И если вы используете перечисление, вы не можете избежать накладных расходов, чтобы, конечно, преобразовать результат сравнения в значение перечисления.)

Таким образом, это в первую очередь делается по соображениям удобочитаемости, но также в некоторой степени по причинам эффективности.

Наконец, как отмечали другие, это также делается по историческим причинам. Функции, такие как C strcmp() и memcmp(), всегда возвращали ints.

Инструкции по сопоставлению ассемблера также обычно используются аналогичным образом.

Например, чтобы сравнить два целых числа в ассемблере x86, вы можете сделать что-то вроде этого:

CMP AX, BX ; 
JLE lessThanOrEqual ; jump to lessThanOrEqual if AX <= BX

или

CMP AX, BX
JG greaterThan ; jump to greaterThan if AX > BX

или

CMP AX, BX
JE equal      ; jump to equal if AX == BX

Вы можете увидеть очевидные сравнения с возвращаемым значением из CompareTo().

Добавление:

Вот пример, который показывает, что не всегда безопасно использовать трюк вычитания rhs из lhs, чтобы получить результат сравнения:

int lhs = int.MaxValue - 10;
int rhs = int.MinValue + 10;

// Since lhs > rhs, we expect (lhs-rhs) to be +ve, but:

Console.WriteLine(lhs - rhs); // Prints -21: WRONG!

Очевидно, это связано с тем, что арифметика переполнена. Если вы включили checked для сборки, то код, который был выше, фактически вывел бы исключение.

По этой причине лучше избегать оптимизации вычитания для реализации сравнения. (См. Комментарии Эрика Липперта ниже.)

Ответ 2

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

Как вы уже разобрались, compareTo так же стара, как Java (Since: JDK1.0from Integer JavaDoc); Java 1.0 была разработана для того, чтобы быть знакомой разработчикам C/С++, и имитировала многие варианты дизайна, к лучшему или к худшему. Кроме того, у Java есть обратная политика совместимости - таким образом, после реализации в основной библиотеке метод почти всегда остается в нем навсегда.

Что касается C/С++ - strcmp/memcmp, который существовал до тех пор, пока string.h, так по существу до тех пор, пока стандартная библиотека C вернет точно те же значения (вернее, compareTo возвращает те же значения, что и strcmp/memcmp) - см., например, C ref - strcmp. На момент создания Java это было логичным делом. В то время на Java не было перечислений, никаких дженериков и т.д. (Все, что приходилось в >= 1,5)

Само решение о возвращаемых значениях strcmp вполне очевидно - в первую очередь вы можете получить 3 основных результата в сравнении, поэтому выберите +1 для "большего", -1 для "меньше" и 0 для "равных" было логичным делом. Кроме того, как указано, вы можете легко получить значение путем вычитания, а возврат int позволяет легко использовать его в дальнейших вычислениях (традиционным способом типа C), а также обеспечивает эффективную реализацию с одним оператором.

Если вам нужен/нужен использовать интерфейс сравнения типов на основе enum, вы можете это сделать, но поскольку соглашение strcmp return +1/0/-1 является таким же старым как современное программирование, оно действительно передает семантическое значение, таким же образом null может быть интерпретировано как unknown/invalid value или значение out of bounds (например, отрицательное число, предоставленное для положительного качества), можно интерпретировать как код ошибки. Возможно, это не лучшая практика кодирования, но она, безусловно, имеет свои плюсы и все еще широко используется, например. в C.

С другой стороны, вопрос о том, почему стандартная библиотека языка XYZ соответствует устаревшим стандартам языка ABC, сам по себе является спорным, так как он может быть точно отреагирован самим языком, разработанным, кто его реализовал.

TL; DR, в основном потому, что это было сделано в старых версиях по причинам, связанным с наследием, и POLA для программистов на C, и снова поддерживается для обратной совместимости и POLA.

В качестве побочного примечания я рассматриваю этот вопрос (в его нынешнем виде) слишком широк, чтобы на него можно было ответить точно, на основе мнения и границы вне темы на SO из-за прямого запроса о шаблонах проектирования и языковой архитектуре.суб >

Ответ 3

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

Обратите внимание, что эта практика опасна для вещей, которые частично сопоставимы при использовании -1, чтобы означать, что пара вещей несравнимо. Это связано с тем, что оно может создать ситуацию, b и b < a (которое приложение может использовать для определения "несравнимого" ). Такая ситуация может привести к неправильному завершению циклов.

Перечисление со значениями {lt, eq, gt, несравнимое} было бы более правильным.

Ответ 4

Ответ, это связано с соображениями производительности. Если вам нужно сравнить int, как это часто бывает, вы можете вернуть следующее:

Инфарктное сравнение часто возвращается в качестве подстроки.

В качестве примера

public class MyComparable implements Comparable<MyComparable> {
    public int num;

    public int compareTo(MyComparable x) {
        return num - x.num;
    }
}

Ответ 5

Я понимаю, что это делается потому, что вы можете упорядочить результаты (т.е. операция рефлексивная и транзитивная). Например, если у вас есть три объекта (A, B, C), вы можете сравнить A- > B и B- > C и использовать полученные значения, чтобы упорядочить их правильно. Существует подразумеваемое предположение, что если A.compareTo(B) == A.compareTo(C), то B == C.

См. java comparator документация.