簡體   English   中英

比較 Java 枚舉成員:== 或 equals()?

[英]Comparing Java enum members: == or equals()?

我知道 Java 枚舉被編譯為帶有私有構造函數和一堆公共靜態成員的類。 在比較給定枚舉的兩個成員時,我一直使用.equals() ,例如

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

但是,我剛剛遇到了一些使用 equals 運算符==而不是 .equals() 的代碼:

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

我應該使用哪個運算符?

兩者在技術上都是正確的。 如果您查看.equals()的源代碼,它只是.equals() ==

但是,我使用== ,因為這將是空安全的。

==可以用在enum嗎?

是的:枚舉具有嚴格的實例控制,允許您使用==來比較實例。 這是語言規范提供的保證(我強調):

JLS 8.9 枚舉

除了由其枚舉常量定義的實例之外,枚舉類型沒有其他實例。

嘗試顯式實例化枚舉類型是編譯時錯誤。 Enumfinal clone方法確保永遠無法克隆enum常量,並且序列化機制的特殊處理確保永遠不會因反序列化而創建重復實例。 禁止枚舉類型的反射實例化。 這四件事一起確保enum類型的實例不存在於enum常量定義的實例之外。

因為只有一個每個實例enum常數,允許使用==操作者在適當位置的equals比較兩個對象的引用時,如果已知它們中的至少一個是指方法enum常數 Enumequals方法是一個final方法,它僅調用super.equals的參數並返回結果,從而執行身份比較。)

Josh Bloch 推薦的這種保證足夠強大,如果您堅持使用單例模式,實現它的最佳方法是使用單元素enum (請參閱: Effective Java 2nd Edition,Item 3:Enforce the singleton property with私有構造函數或枚舉類型;還有單例中的線程安全


==equals什么區別?

提醒一下,需要說明的是,通常==不是equals的可行替代方案。 然而,當它是(例如enum )時,需要考慮兩個重要的區別:

==從不拋出NullPointerException

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

==在編譯時進行類型兼容性檢查

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

應該在適用時使用==嗎?

Bloch 特別提到,對其實例進行適當控制的不可變類可以向其客戶保證==可用。 enum被特別提到作為例證。

第 1 條:考慮靜態工廠方法而不是構造函數

[...] 它允許不可變類保證不存在兩個相等的實例: a.equals(b)當且僅當a==b 如果一個類做出了這個保證,那么它的客戶端可以使用==運算符而不是equals(Object)方法,這可能會提高性能。 枚舉類型提供了這種保證。

總而言之,在enum上使用==的參數是:

  • 有用。
  • 它更快。
  • 它在運行時更安全。
  • 在編譯時更安全。

使用==比較兩個枚舉值是有效的,因為每個枚舉常量只有一個對象。

附帶說明一下,如果您像這樣編寫equals() ,則實際上不需要使用==來編寫空安全代碼:

public useEnums(final SomeEnum a) {
    if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
        …
    }
    …
}

這是一種稱為從左側比較常量的最佳實踐,您絕對應該遵循。

正如其他人所說, ==.equals()在大多數情況下都有效。 您沒有比較其他人指出的完全不同類型的對象的編譯時確定性是有效且有益的,但是 FindBugs 也會發現比較兩種不同編譯時類型的對象的特定類型的錯誤(並且可能通過Eclipse/IntelliJ 編譯時檢查),因此 Java 編譯器發現它並沒有增加太多額外的安全性。

然而:

  1. 這樣的事實==不會拋出NPE在我心目中是一個缺點== 幾乎不需要enum類型為null ,因為您可能想要通過null表達的任何額外狀態都可以作為附加實例添加到enum中。 如果它意外地為null ,我寧願有一個 NPE 而不是==默默地評估為假。 因此,我不同意運行時更安全的觀點; 最好養成永遠不要讓enum值為@Nullable的習慣。
  2. ==更快的論點也是虛假的。 在大多數情況下,您將在編譯時類型為 enum 類的變量上調用.equals() ,在這些情況下,編譯器可以知道這與==相同(因為enumequals()方法可以不被覆蓋)並且可以優化函數調用。 我不確定編譯器當前是否這樣做,但如果沒有,並且結果證明是 Java 整體的性能問題,那么我寧願修復編譯器也不願讓 100,000 個 Java 程序員改變他們的編程風格以適應特定編譯器版本的性能特征。
  3. enums是對象。 對於所有其他 Object 類型,標准比較是.equals() ,而不是== 我認為為enums例外是危險的,因為您最終可能會不小心將 Objects 與==而不是equals()進行比較,特別是如果您將enum重構為非枚舉類。 在這種重構的情況下,上面的It 工作點是錯誤的。 為了讓自己相信==的使用是正確的,您需要檢查所討論的值是enum還是原始值; 如果它是一個非enum類,它會是錯誤的但很容易錯過,因為代碼仍然可以編譯。 使用.equals()錯誤的唯一情況是所討論的值是原始值; 在這種情況下,代碼將無法編譯,因此更難錯過。 因此, .equals()更容易被識別為正確的,並且對未來的重構更安全。

我實際上認為 Java 語言應該在對象上定義 == 以在左側值上調用 .equals(),並為對象標識引入一個單獨的運算符,但這不是 Java 的定義方式。

總之,我仍然認為參數支持使用.equals()作為enum類型。

我更喜歡使用==而不是equals

除了這里已經討論的其他原因之外,其他原因是您可能會在沒有意識到的情況下引入錯誤。 假設您有這個完全相同的枚舉,但在單獨的包中(這並不常見,但可能會發生):

第一個枚舉

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

第二個枚舉:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

然后假設你在item.category使用了像 next 一樣的item.category ,它是first.pckg.Category但你導入了第二個枚舉( second.pckg.Category )而不是第一個沒有意識到它:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())

因此,盡管您期望 true 是因為item.getCategory()JAZZ但由於是不同的枚舉,您總是會得到false 而且可能有點難看。

因此,如果您改為使用運算符== ,則會出現編譯錯誤:

運算符 == 不能應用於“second.pckg.Category”、“first.pckg.Category”

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

tl;博士

另一種選擇是Objects.equals實用程序方法。

Objects.equals( thisEnum , thatEnum )

Objects.equals用於 null 安全

等於運算符 == 而不是 .equals()

我應該使用哪個運算符?

第三個選項是在Java 7及更高版本中添加Objects實用程序類中的靜態equals方法。

例子

這是使用Month枚舉的示例。

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns `false`.

好處

我發現這種方法有幾個好處:

  • 零安全
  • 緊湊、可讀

這個怎么運作

Objects.equals使用的邏輯是什么?

親自查看OpenJDKJava 10 源代碼

return 
    ( a == b ) 
    || 
    ( 
        a != null 
        && 
        a.equals( b )
    )
;

這是一個粗略的時間測試來比較兩者:

import java.util.Date;

public class EnumCompareSpeedTest {

    static enum TestEnum {ONE, TWO, THREE }

    public static void main(String [] args) {

        Date before = new Date();
        int c = 0;

        for(int y=0;y<5;++y) {
            for(int x=0;x<Integer.MAX_VALUE;++x) {
                if(TestEnum.ONE.equals(TestEnum.TWO)) {++c;}
                if(TestEnum.ONE == TestEnum.TWO){++c;}              
            }
        }

        System.out.println(new Date().getTime() - before.getTime());
    }   

}

一次注釋掉一個 IF。 以下是上面反匯編字節碼中的兩個比較:

 21  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 24  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 27  invokevirtual EnumCompareSpeedTest$TestEnum.equals(java.lang.Object) : boolean [28]
 30  ifeq 36

 36  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 39  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 42  if_acmpne 48

第一個 (equals) 執行虛擬調用並測試堆棧中的返回布爾值。 第二個 (==) 直接從堆棧中比較對象地址。 在第一種情況下有更多的活動。

我用兩個 IF 一次一個地運行了這個測試。 “==”總是稍微快一點。

在枚舉的情況下,兩者都是正確的!!

聲納規則之一是Enum values should be compared with "==" 原因如下:

使用equals()測試枚舉值的equals()是完全有效的,因為枚舉是一個對象,每個 Java 開發人員都知道==不應該用於比較對象的內容。 同時,在枚舉上使用==

  • 提供與equals()相同的預期比較(內容equals()

  • equals()更空安全

  • 提供編譯時(靜態)檢查而不是運行時檢查

由於這些原因,應該優先使用==不是equals()

最后但並非最不重要的一點是,枚舉上的==可以說比equals()更具可讀性(更簡潔equals()

使用==以外的任何東西來比較枚舉常量都是無稽之談。 這就像class對象與equals進行比較——不要這樣做!

但是,由於歷史原因,Sun JDK 6u10 及更早版本中存在一個令人討厭的錯誤 ( BugId 6277781 )。 這個錯誤阻止了在反序列化枚舉上正確使用== ,盡管這可以說是一種極端情況。

枚舉是為public static final field (不可變)聲明的每個枚舉常量返回一個實例(如單例)的類,以便==運算符可用於檢查它們的相等性,而不是使用equals()方法

枚舉很容易與 == 一起工作的原因是因為每個定義的實例也是一個單例。 因此,使用 == 進行身份比較將始終有效。

但是使用 == 因為它適用於枚舉意味着您的所有代碼都與該枚舉的使用緊密結合。

例如:枚舉可以實現一個接口。 假設您當前正在使用一個實現 Interface1 的枚舉。 如果稍后有人更改它或引入一個新類 Impl1 作為同一接口的實現。 然后,如果您開始使用 Impl1 的實例,由於以前使用 ==,您將需要更改和測試大量代碼。

因此,除非有任何合理的收益,否則最好遵循被認為是好的做法。

簡而言之,兩者各有利弊。

一方面,如其他答案中所述,使用==具有優勢。

另一方面,如果您出於任何原因用不同的方法(普通類實例)替換枚舉,使用==咬你。 (BTDT。)

我想補充 polygenelubricants 答案:

我個人更喜歡equals()。 但它需要類型兼容性檢查。 我認為這是一個重要的限制。

要在編譯時進行類型兼容性檢查,請在枚舉中聲明並使用自定義函數。

public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable

有了這個,您就獲得了兩種解決方案的所有優勢:NPE 保護、易於閱讀的代碼和編譯時的類型兼容性檢查。

我還建議為枚舉添加一個 UNDEFINED 值。

如果將原始類型與其類版本進行比較,則==可以引發NullPointerException 例如:

private static Integer getInteger() {
    return null;
}

private static void foo() {
    int a = 10;

    // Following comparison throws a NPE, it calls equals() on the 
    // non-primitive integer which is itself null. 
    if(a == getInteger()) { 
        // Some code
    }
}

暫無
暫無

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM