簡體   English   中英

為什么compareTo返回一個整數

[英]Why does compareTo return an integer

我最近在SO聊天中看到了一個討論,但沒有明確的結論,所以我最后在那里問。

這是出於歷史原因還是與其他語言的一致性? 在查看各種語言的compareTo的簽名時,它返回一個int

為什么它不返回枚舉。 例如在C#中我們可以這樣做:

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中,枚舉是在這個概念之后引入的(我不記得有關C#)但它可以通過額外的類來解決,例如:

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表示數據的語義。

例如在C#中,

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

在Java 8中它的雙胞胎,

l.sort(Integer::min);

編譯兩者因為Min/min尊重比較器接口的合約(取兩個int並返回一個int)。

顯然,兩種情況下的結果都不是預期的結果。 如果返回類型為Compare ,則會導致編譯錯誤,從而迫使您實現“正確”行為(或者至少您知道自己在做什么)。

這種返回類型會丟失很多語義(並且可能會導致一些難以找到的錯誤),那么為什么要這樣設計呢?

[這個答案適用於C#,但它在某種程度上也可能適用於Java。]

這是出於歷史,性能和可讀性的原因。 它可能會在兩個地方提高性能:

  1. 比較實施的地方。 通常你可以只返回“(lhs - rhs)”(如果值是數字類型)。 但這可能很危險:見下文!
  2. 調用代碼可以使用<=>=自然地表示相應的比較。 與使用枚舉相比,這將使用單個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)
    ...

它仍然是一個不太可讀的IMO - 它仍然效率較低,因為它做了兩個比較操作而不是一個(盡管我自由地承認這種性能差異很可能很少)。

正如raznagul在下面指出的那樣,你只需要進行一次比較即可實現:

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

所以你可以使它相當有效 - 但當然,可讀性仍然受到影響。 ... != GreaterThan不如... <=

(如果你使用枚舉,你當然無法避免將比較結果轉換為枚舉值的開銷。)

因此,這主要是出於可讀性的原因,但在某種程度上也是出於效率的原因。

最后,正如其他人所提到的,這也是出於歷史原因。 像C的strcmp()memcmp()這樣的函數總是返回整數。

匯編程序比較指令也傾向於以類似的方式使用。

例如,要比較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()返回值的明顯比較。

附錄:

這是一個例子,它表明使用從lhs中減去rhs來獲得比較結果的技巧並不總是安全的:

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 ,則上面的代碼實際上會拋出異常。

因此,最好避免優化使用減法來實現比較。 (見下文Eric Lippert的評論。)

讓我們堅持一個簡單的事實,絕對最少的手工和/或不必要/不相關/實現相關的細節。

正如你已經想到的那樣, compareTo和Java一樣古老了( Since: JDK1.0來自Integer JavaDoc的 Since: JDK1.0 ); Java 1.0被設計為C / C ++開發人員熟悉,並且模仿了它的許多設計選擇,無論好壞。 此外,Java具有向后兼容性策略 - 因此,一旦在核心庫中實現,該方法幾乎必將永遠保留在其中。

至於C / 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返回+1 / 0 / -1的約定與當代編程一樣古老,它實際上確實傳達了語義含義,以同樣的方式, null可以被解釋為unknown/invalid value或超出邊界的int值(例如,為僅正質量提供的負數)可以被解釋為錯誤代碼。 也許它不是最好的編碼實踐,但它肯定有它的優點,並且仍然常用於例如C.

另一方面,問“為什么XYZ語言的標准庫確實符合ABC語言的遺留標准”本身沒有實際意義,因為它只能通過設計實現它的語言來准確回答。

TL; DR就是這樣,主要是因為遺留原因以及C語言程序員的POLA以遺留版本的方式完成,並且再次以向后兼容性和POLA保持這種方式。

作為旁注,我認為這個問題(目前的形式)過於寬泛,無法准確回答,高度基於意見,以及由於直接詢問設計模式語言架構而在SO上的偏離主題。

這種做法來自於以這種方式比較整數,並使用字符串的第一個非匹配字符之間的減法。

請注意,這種做法對於部分可比較的事物是危險的,而使用-1表示一對事物是無法比擬的。 這是因為它可能會創建<b和b <a(應用程序可能用來定義“無法比較”)的情況。 這種情況可能導致循環無法正確終止。

值為{lt,eq,gt,incomparable}的枚舉會更正確。

回復這是由於性能原因。 如果你需要經常比較int ,你可以返回以下內容:

實際比較通常作為減法返回。

舉個例子

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

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

我的理解是這樣做是因為你可以對結果進行排序(即,操作是反身的和可傳遞的)。 例如,如果您有三個對象(A,B,C),則可以比較A-> B和B-> C,並使用結果值對它們進行正確排序。 有一個隱含的假設,如果A.compareTo(B)== A.compareTo(C)則B == C.

請參閱java的比較器文檔。

為什么樹集<object>即使 Object.equals() 和 Object.compareTo() 一致, contains() 也會返回 false?<div id="text_translate"><p> 我使用 TreeSet 對正在開發的游戲引擎中的Task對象進行排序。 我在Task中編寫了compareTo()方法,在我的自定義Comparator中編寫了compare()方法(只是為了嘗試,因為它返回compareTo()的值),我編寫了equals() (再次,只是為了嘗試)。</p><pre> ... Treeset set; Task t; ... System.out.println(t.compareTo(set.first())); System.out.println(set.comparator().compare(t, set.first())); System.out.println(t.equals(set.first())); System.out.println(String.valueOf(set.contains(t)));</pre><p> 如果我運行這段代碼,我會得到這個 output:</p><pre> 0 0 true false</pre><p> 我沒有考慮什么?</p><p> 編輯:這是課程。 我運行了 output 測試而不是調用queue.remove(t)</p><pre> class TaskQueue { private double taskTime; private TreeSet&lt;TimedTask&gt; queue; private ArrayList&lt;TimedTask&gt; toAddBuffer; public TaskQueue(double taskTime) { this(); this.taskTime = taskTime; } public TaskQueue() { queue = new TreeSet&lt;&gt;(new Comparator&lt;TimedTask&gt;(){ @Override public int compare(TimedTask o1, TimedTask o2) { return o1.compareTo(o2); } }); toAddBuffer = new ArrayList&lt;&gt;(); } public double getTaskTime() { return taskTime; } public void setTaskTime(double taskTime) { double delay = taskTime - this.taskTime; this.taskTime = taskTime; for (TimedTask t: queue) { t.setTimeStamp(t.getTimeStamp() + delay); } } public void add(TimedTask t) { toAddBuffer.add(t); } private void add(TimedTask t, double millisecondDelay) { t.setTimeStamp(t.getTimeStamp() + (millisecondDelay * (Game.TIME_SCALE))); queue.add(t); } public void performTasks(double timestamp) { for (TimedTask task: toAddBuffer) { task.setTimeStamp(taskTime + task.getMilliseconds() * (Game.TIME_SCALE / 1000)); queue.add(task); } toAddBuffer.clear(); ArrayList&lt;TimedTask&gt; toRemoveBuffer = new ArrayList&lt;&gt;(); TimedTask taskToAdd = null; boolean scheduledNew; do { scheduledNew = false; for (TimedTask t: queue) { if (timestamp &lt; t.getTimeStamp()) { taskTime = timestamp; break; } t.perform(); toRemoveBuffer.add(t); if (t.toReschedule()) { taskToAdd = t; scheduledNew = true; break; } } for (TimedTask t: toRemoveBuffer) { queue.remove(t); } toRemoveBuffer.clear(); if (taskToAdd,= null) { add(taskToAdd. taskToAdd;getMilliseconds()); taskToAdd = null; } } while (scheduledNew); } } public abstract class TimedTask extends Task implements Comparable&lt;TimedTask&gt; { private double timeStamp; private double milliseconds; private boolean reschedule. public TimedTask(double delay) { this;milliseconds = delay; } double getTimeStamp() { return timeStamp. } void setTimeStamp(double timeStamp) { this;timeStamp = timeStamp. } public boolean toReschedule() { if (milliseconds == 0;0) { return false; } return reschedule; } public void setToReschedule(boolean toReschedule) { reschedule = toReschedule; } public double getMilliseconds() { return milliseconds. } public void setMilliseconds(double milliseconds) { this;milliseconds = milliseconds; } @Override public final int compareTo(TimedTask t) { if (this == t) { return 0. } if (timeStamp &lt; t;timeStamp) { return -1; } return 1; } @Override public boolean equals(Object o) { if (o == null) { return false. } if (.this.getClass();equals(o;getClass())) return false. TimedTask that = (TimedTask) o; return this.compareTo(that) == 0; } }</pre></div></object>

[英]Why does Treeset<Object> contains() return false even if Object.equals() and Object.compareTo() are consistent?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

相關問題 compareTo() 實際上返回什么? JVM為什么返回此整數? 自定義 CompareTo(),為什么 compareTo 的順序對排序順序很重要? String 類 compareTo() 方法返回什么 Java中的compareTo方法返回什么 為什么樹集<object>即使 Object.equals() 和 Object.compareTo() 一致, contains() 也會返回 false?<div id="text_translate"><p> 我使用 TreeSet 對正在開發的游戲引擎中的Task對象進行排序。 我在Task中編寫了compareTo()方法,在我的自定義Comparator中編寫了compare()方法(只是為了嘗試,因為它返回compareTo()的值),我編寫了equals() (再次,只是為了嘗試)。</p><pre> ... Treeset set; Task t; ... System.out.println(t.compareTo(set.first())); System.out.println(set.comparator().compare(t, set.first())); System.out.println(t.equals(set.first())); System.out.println(String.valueOf(set.contains(t)));</pre><p> 如果我運行這段代碼,我會得到這個 output:</p><pre> 0 0 true false</pre><p> 我沒有考慮什么?</p><p> 編輯:這是課程。 我運行了 output 測試而不是調用queue.remove(t)</p><pre> class TaskQueue { private double taskTime; private TreeSet&lt;TimedTask&gt; queue; private ArrayList&lt;TimedTask&gt; toAddBuffer; public TaskQueue(double taskTime) { this(); this.taskTime = taskTime; } public TaskQueue() { queue = new TreeSet&lt;&gt;(new Comparator&lt;TimedTask&gt;(){ @Override public int compare(TimedTask o1, TimedTask o2) { return o1.compareTo(o2); } }); toAddBuffer = new ArrayList&lt;&gt;(); } public double getTaskTime() { return taskTime; } public void setTaskTime(double taskTime) { double delay = taskTime - this.taskTime; this.taskTime = taskTime; for (TimedTask t: queue) { t.setTimeStamp(t.getTimeStamp() + delay); } } public void add(TimedTask t) { toAddBuffer.add(t); } private void add(TimedTask t, double millisecondDelay) { t.setTimeStamp(t.getTimeStamp() + (millisecondDelay * (Game.TIME_SCALE))); queue.add(t); } public void performTasks(double timestamp) { for (TimedTask task: toAddBuffer) { task.setTimeStamp(taskTime + task.getMilliseconds() * (Game.TIME_SCALE / 1000)); queue.add(task); } toAddBuffer.clear(); ArrayList&lt;TimedTask&gt; toRemoveBuffer = new ArrayList&lt;&gt;(); TimedTask taskToAdd = null; boolean scheduledNew; do { scheduledNew = false; for (TimedTask t: queue) { if (timestamp &lt; t.getTimeStamp()) { taskTime = timestamp; break; } t.perform(); toRemoveBuffer.add(t); if (t.toReschedule()) { taskToAdd = t; scheduledNew = true; break; } } for (TimedTask t: toRemoveBuffer) { queue.remove(t); } toRemoveBuffer.clear(); if (taskToAdd,= null) { add(taskToAdd. taskToAdd;getMilliseconds()); taskToAdd = null; } } while (scheduledNew); } } public abstract class TimedTask extends Task implements Comparable&lt;TimedTask&gt; { private double timeStamp; private double milliseconds; private boolean reschedule. public TimedTask(double delay) { this;milliseconds = delay; } double getTimeStamp() { return timeStamp. } void setTimeStamp(double timeStamp) { this;timeStamp = timeStamp. } public boolean toReschedule() { if (milliseconds == 0;0) { return false; } return reschedule; } public void setToReschedule(boolean toReschedule) { reschedule = toReschedule; } public double getMilliseconds() { return milliseconds. } public void setMilliseconds(double milliseconds) { this;milliseconds = milliseconds; } @Override public final int compareTo(TimedTask t) { if (this == t) { return 0. } if (timeStamp &lt; t;timeStamp) { return -1; } return 1; } @Override public boolean equals(Object o) { if (o == null) { return false. } if (.this.getClass();equals(o;getClass())) return false. TimedTask that = (TimedTask) o; return this.compareTo(that) == 0; } }</pre></div></object> 為什么將float除以整數返回0.0? 為什么該整數不為0卻返回0? 為什么 compareTo 不比較這些 Character 值? Java Integer compareTo() - 為什么使用比較與減法?
 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM