web-dev-qa-db-ja.com

Java列挙型メンバーの比較:==またはequals()?

私は、Javaのenumが、プライベートコンストラクタと多数のパブリック静的メンバーを持つクラスにコンパイルされていることを知っています。与えられたenumの2つのメンバを比較するとき、私はいつも.equals()を使いました。

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

しかし、.equals()の代わりに等号演算子==を使用するコードをいくつか見つけました。

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

どの演算子を使うべきですか?

1549
Matt Ball

どちらも技術的に正しいです。 .equals()のソースコードを見ると、それは単に==に委ねられています。

私は==を使います。

1398
Reverend Gonzo

==enumで使用できますか?

はい:enumはインスタンスを比較するために==を使うことを可能にする厳密なインスタンスコントロールを持ちます。これは言語仕様によって提供される保証です(私が強調します):

JLS 8.9列挙型

列挙型には、その列挙定数によって定義されたもの以外のインスタンスはありません。

列挙型を明示的にインスタンス化しようとするのはコンパイル時エラーです。 Enum内のfinal cloneメソッドはenum定数が複製されないことを保証し、シリアライゼーションメカニズムによる特別な処理は重複したインスタンスがデシリアライゼーションの結果として決して作成されないことを保証します。列挙型の反射的インスタンス化は禁止されています。まとめると、これら4つのことは、enum型のインスタンスが、enum定数で定義されたものを超えて存在しないことを保証します。

enum定数のインスタンスは1つだけなので、少なくとも1つがequals定数を参照していることがわかっている場合は、2つのオブジェクト参照を比較するときにenumメソッドの代わりに==演算子を使用できます。 (equalsEnumメソッドは、その引数に対してsuper.equalsを呼び出すだけで結果を返すfinalメソッドであるため、同一性比較を実行します。)

この保証はJosh Blochが推奨するほど強力です。シングルトンパターンを使用することを主張する場合、それを実装する最良の方法は単一要素enumを使用することです(参照: Effective Java 2nd Edition、Item 3:シングルトンの強制)プライベートコンストラクタまたは列挙型を持つプロパティ ; Singleton のスレッドセーフ


==equalsの違いは何ですか?

注意として、一般的に、==equalsに代わる実行可能な方法ではありません。しかし、そうである場合(enumの場合など)、考慮すべき2つの重要な違いがあります。

==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:コンストラクタの代わりに静的ファクトリメソッドを検討する

[...]これは、不変クラスが2つの等しいインスタンスが存在しないことを保証することを可能にします:a==bの場合に限りa.equals(b)。クラスがこの保証を行う場合、そのクライアントはequals(Object)メソッドの代わりに==演算子を使用できます。これにより、パフォーマンスが向上する可能性があります。列挙型はこの保証を提供します。

まとめると、enum==を使用するための引数は次のとおりです。

  • できます。
  • それは速いです。
  • 実行時により安全です。
  • コンパイル時に安全です。
1018

2つのenum値を比較するために==を使用すると、各enum定数に対して1つのオブジェクトしかないため、うまくいきます。

ちなみに、equals()を次のように書くのであれば、実際には==を使ってnullセーフなコードを書く必要はありません。

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

これは、 左から定数を比較 として知られているベストプラクティスです。

77
Pascal Thivent

他の人が言っているように、==.equals()の両方ともほとんどの場合に動作します。完全に異なる種類のオブジェクトを比較していないというコンパイル時の確実性は有効であり有益ですが、2つの異なるコンパイル時型のオブジェクトを比較するという特定の種類のバグもFindBugsによって発見されます。 Eclipse/IntelliJはコンパイル時の検査を行うので、それを見つけたJavaコンパイラはそれほど安全性を高めることはありません。

しかしながら:

  1. 私の心に==がNPEを投げないことは、== 不利益 です。 enumを使って表現する必要がある追加の状態は、追加のインスタンスとしてnullに追加できるため、null型をenumにする必要はほとんどありません。それが予想外にnullであれば、私は==よりも暗黙のうちにfalseと評価するよりもNPEを持っているほうがいいです。したがって、私は実行時により安全であるの意見に同意しません。 enumの値を@Nullableにしないようにする習慣を身に付けることをお勧めします。
  2. ==速いであるという議論もまた偽物です。ほとんどの場合、コンパイル時の型がenumクラスである変数に対して.equals()を呼び出すことになります。その場合、(enumequals()メソッドはオーバーライドできないので)これは==と同じであることがわかります。関数呼び出しコンパイラが現在これを行っているかどうかはわかりませんが、そうではなく、Java全体のパフォーマンス上の問題であることが判明した場合は、10万人のJavaプログラマが自分のプログラミングスタイルを変更するよりもコンパイラを修正します。特定のコンパイラバージョンのパフォーマンス特性.
  3. enumsはオブジェクトです。他のすべてのオブジェクト型の標準比較は==ではなく.equals()です。特にenumsをenum以外のクラスにリファクタリングした場合、誤ってObjectsをequals()ではなく==と比較してしまう可能性があるため、enumを例外とするのは危険です。そのようなリファクタリングの場合、上からのIt worksポイントは間違っています。 ==の使用が正しいことをあなた自身に納得させるためには、問題の値がenumまたはプリミティブのどちらであるかをチェックする必要があります。もしそれがenum以外のクラスであった場合、それは間違っていますが、コードはまだコンパイルされるので見逃しやすいです。 .equals()の使い方が間違っている唯一のケースは、問題の値がプリミティブな場合です。その場合、コードはコンパイルされないので、見逃しがちです。したがって、.equals()は正しいと識別するのがはるかに簡単で、将来のリファクタリングに対してより安全です。

実際には、Java言語は左側の値で.equals()を呼び出すためにObjectsに==を定義し、オブジェクト識別のために別の演算子を導入すべきだと思いますが、それはJavaが定義された方法ではありません。

まとめると、引数はenum型に.equals()を使用することを支持しています。

59
Tobias

これを比較するためのおおまかなタイミングテストは次のとおりです。

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を1つずつコメントアウトします。これが逆アセンブルされたバイトコードの上からの2つの比較です。

 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

最初の(等しい)仮想呼び出しを実行し、スタックからの戻りブール値をテストします。 2番目の(==)は、スタックから直接オブジェクトアドレスを比較します。最初のケースではもっと活動があります。

私は一度に両方のIFでこのテストを数回実行しました。 "=="はこれまでより少し速いです。

13
ChrisCantrell

equalsの代わりに==を使うのが好きです。

他の理由は、ここで既に説明した他の理由に加えて、それを認識せずにバグを導入する可能性があることです。この列挙型がまったく同じだが別々のパッケージに入っているとします(一般的ではありませんが、発生する可能性があります)。

最初のenum

package first.pckg

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

第2の列挙:

package second.pckg

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

次に、item.categoryでnextのようなequalsを使用し、first.pckg.Categoryであるが、それに気付かずに最初のenum(second.pckg.Category)をインポートするとします。

import second.pckg.Category;
...

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

item.getCategory()falseなので、あなたはtrueを期待していますが、常にJAZZ dueは別のenumです。そして見るのは少し難しいかもしれません。

したがって、代わりに演算子==を使用すると、コンパイルエラーが発生します。

operator ==を "second.pckg.Category"、 "first.pckg.Category"に適用することはできません

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
12
Pau

列挙型の場合は両方とも正しいです。

9
Suraj Chandran

Enum定数を比較するために==以外のものを使用することは意味がありません。まるで classオブジェクトとequalsを比較する - しないでください!

しかし、Sun JDK 6u10以前には厄介なバグ(BugId 6277781 )があり、歴史的な理由で興味深い場合があります。このバグは、逆シリアル化されたenumで==を適切に使用することを妨げましたが、これは間違いなく多少角っぽいケースです。

6
Tomas

Enumはpublic static final field(immutable)で宣言された列挙型定数ごとに1つのインスタンス(シングルトンのような)を返すクラスなので、==演算子を使用してequals()メソッドを使わずに等価性をチェックできます

Enumが==で簡単に機能するのは、定義された各インスタンスもシングルトンであるためです。そのため、==を使った恒等比較は常にうまくいきます。

しかし、==を使用するのは列挙型で機能するため、すべてのコードがその列挙型の使用と密接に関連していることを意味します。

例えば、Enumはインターフェースを実装することができます。現在、Interface1を実装しているenumを使用しているとします。後で、誰かがそれを変更するか、同じインターフェースの実装として新しいクラスImpl1を導入します。その後、Impl1のインスタンスを使い始めると、==が以前に使用されていたために変更してテストするためのコードがたくさんあります。

したがって、正当な利益がない限り、グッドプラクティスと見なされるものに従うことが最善です。

3
Dev Amitabh

tl; dr

別のオプションは Objects.equals ユーティリティメソッドです。

Objects.equals( thisEnum , thatEnum )

Objects.equals

.equals()の代わりに等しい演算子==

どの演算子を使うべきですか?

3番目のオプションは、 equals ユーティリティクラス Java 7に追加 以降にある静的な Objects メソッドです。

これは Month enumを使った例です。

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

利点

この方法にはいくつかの利点があります。

  • 安全性なし
  • コンパクトで読みやすい

使い方

Objects.equals で使用されているロジックは何ですか?

Java 10ソースコード of OpenJDK から、自分自身で確かめてください。

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

つまり、どちらにも長所と短所があります。

一方では、他の答えで説明されているように、==を使用することに利点があります。

一方、何らかの理由でenumを別のアプローチ(通常のクラスインスタンス)に置き換える場合は、==を使用すると問題が発生します。 (BTDT)

1
glglgl

私はpolygenelubricants答えを補足したいです:

私は個人的にequals()を好みます。しかし、それは型の互換性チェックを妨げます。これは重要な制限だと思います。

コンパイル時に型の互換性をチェックするには、列挙型でカスタム関数を宣言して使用します。

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

これにより、NPE保護、コードの読みやすさ、およびコンパイル時の型の互換性チェックという両方のソリューションのすべての利点が得られます。

列挙型に未定義の値を追加することもお勧めします。

0
Christ