==(または!=)を使用してJava列挙型を比較します

ほとんどの新しいJava開発者は、一般に、を使用するのではなく、String.equals(Object)を使用してJava文字列を比較する必要があることをすぐに学び==ます。これは、ほとんどの場合、文字列のID(メモリ内のアドレス)ではなく、文字列のコンテンツ(文字列を形成する実際の文字)を比較することを意味するため、新しい開発者に対して繰り返し強調および強化されます。==Enum.equals(Object)の代わりに使用できる概念を強化する必要があると私は主張します。この投稿の残りの部分で、この主張の理由を説明します。

==Java列挙型を比較す​​るために使用する方が、「等しい」メソッドを使用するよりもほとんどの場合好ましいと私が信じる4つの理由があります。

  1. ==列挙に同じ予想比較(コンテンツ)を提供equals
  2. ==列挙型では(それほど冗長)よりも間違いなくより読みやすいですequals
  3. ==列挙型の複数のヌル・安全よりもequals
  4. ==列挙型の実行時検査をチェックするのではなく、コンパイル時(静的)を提供

上記の2番目の理由(「おそらくより読みやすい」)は明らかに意見の問題ですが、「冗長性が低い」という部分については合意できます。==列挙型を比較す​​るときに私が一般的に好む最初の理由は、Java言語仕様が列挙型をどのように記述しているかの結果です。セクション8.9(「列挙型」)は次のように述べています。

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

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

上記の仕様からの抜粋==は、同じ列挙型定数のインスタンスが複数存在する可能性がないため、演算子を使用して2つの列挙型を比較しても安全であることを暗示して明示的に示しています。

第4の利点==を超える.equals列挙型を比較するときには、コンパイル時の安全性に関係しています。Object.equals(Object)は契約により任意のをとらなければならないため==、を使用すると、コンパイル時のチェックがそれよりも厳密になります。 Javaのような静的に型付けされた言語を使用するとき、私はこの静的な型付けの利点を可能な限り利用すると信じています。それ以外の場合は、動的に型指定された言語を使用します。効果的なJavaの繰り返しのテーマの1つは、可能な限り静的な型チェックを好むということだと思います。.equalsObject

たとえば、というカスタム列挙型がFruitあり、それをクラスjava.awt.Colorと比較しようとしたとします。==演算子を使用すると、問題のコンパイル時エラー(お気に入りのJava IDEでの事前通知を含む)を取得できます。==演算子を使用してカスタム列挙型をJDKクラスと比較しようとするコードリストを次に示します。

/** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // This comparison of Fruit to Color will lead to compiler error: // error: incomparable types: Fruit and Color return Fruit.WATERMELON == candidateColor; } 

コンパイラエラーは、次に表示される画面スナップショットに表示されます。

私はエラーのファンではありませんが、ランタイムカバレッジに依存するのではなく、コンパイル時に静的にキャッチされることを好みます。equalsこの比較にメソッドを使用した場合、コードは正常にコンパイルされますがfalsedustin.examples.Fruit列挙型がjava.awt.Colorクラスと等しくなる方法がないため、メソッドは常にfalseを返します。私はそれをお勧めしませんが、これは以下を使用した比較方法.equalsです:

/** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } 

上記の「良い」点は、コンパイル時エラーがないことです。美しくコンパイルされます。残念ながら、これは潜在的に高い価格で支払われます。

列挙型を比較す​​るとき==ではなく使用することの最後の利点Enum.equalsは、恐ろしいNullPointerExceptionを回避することです。効果的なJavaNullPointerException処理で述べたように、私は通常、予期しないを避けたいと思っていNullPointerExceptionます。 nullの存在を例外的なケースとして扱いたいという状況は限られていますが、問題をより適切に報告することを好むことがよくあります。列挙型を==と比較する利点は、NullPointerException(NPE)に遭遇することなく、ヌルを非ヌルの列挙型と比較できることです。この比較の結果は、明らかにですfalse

使用時にNPEを回避する1つ.equals(Object)equals方法は、列挙型定数または既知の非null列挙型に対してメソッドを呼び出し、疑わしい文字(おそらくnull)の潜在的な列挙型をパラメーターとしてequalsメソッドに渡すことです。これは、NPEを回避するために、Javaで文字列を使用して何年にもわたって行われてきました。ただし、==演算子では、比較の順序は重要ではありません。私はすきです。

私は議論をしました、そして今私はいくつかのコード例に移ります。次のリストは、前述の架空のフルーツ列挙型の実現です。

Fruit.java

package dustin.examples; public enum Fruit { APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, GRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON } 

次のコードリストは、特定の列挙型またはオブジェクトが特定のフルーツであるかどうかを検出するためのメソッドを提供する単純なJavaクラスです。私は通常、これらのようなチェックを列挙型自体に入れますが、説明と説明の目的で、ここの別のクラスでより適切に機能します。このクラスには、との両方と比較するFruitために前に示した2つのメソッドが含まれています。もちろん、列挙型をクラスと比較するために使用するメソッドでは、正しくコンパイルするためにその部分をコメントアウトする必要がありました。Color==equals==

EnumComparisonMain.java

package dustin.examples; public class EnumComparisonMain { /** * Indicate whether provided fruit is a watermelon ({@code true} or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a watermelon; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is watermelon; {@code false} if * provided fruit is NOT a watermelon. */ public boolean isFruitWatermelon(Fruit candidateFruit) { return candidateFruit == Fruit.WATERMELON; } /** * Indicate whether provided object is a Fruit.WATERMELON ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a watermelon and may * not even be a Fruit! * @return {@code true} if provided object is a Fruit.WATERMELON; * {@code false} if provided object is not Fruit.WATERMELON. */ public boolean isObjectWatermelon(Object candidateObject) { return candidateObject == Fruit.WATERMELON; } /** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // Had to comment out comparison of Fruit to Color to avoid compiler error: // error: incomparable types: Fruit and Color return /*Fruit.WATERMELON == candidateColor*/ false; } /** * Indicate whether provided fruit is a strawberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a strawberry; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is strawberry; {@code false} if * provided fruit is NOT strawberry. */ public boolean isFruitStrawberry(Fruit candidateFruit) { return Fruit.STRAWBERRY == candidateFruit; } /** * Indicate whether provided fruit is a raspberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a raspberry; null is * completely and entirely unacceptable; please don't pass null, please, * please, please. * @return {@code true} if provided fruit is raspberry; {@code false} if * provided fruit is NOT raspberry. */ public boolean isFruitRaspberry(Fruit candidateFruit) { return candidateFruit.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Object is a Fruit.RASPBERRY ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a Raspberry and may * or may not even be a Fruit! * @return {@code true} if provided Object is a Fruit.RASPBERRY; {@code false} * if it is not a Fruit or not a raspberry. */ public boolean isObjectRaspberry(Object candidateObject) { return candidateObject.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } /** * Indicate whether provided fruit is a grape ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a grape; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is a grape; {@code false} if * provided fruit is NOT a grape. */ public boolean isFruitGrape(Fruit candidateFruit) { return Fruit.GRAPE.equals(candidateFruit); } } 

上記の方法で得られたアイデアのデモンストレーションに、単体テストでアプローチすることにしました。特に、GroovyのGroovyTestCaseを利用しています。Groovyを利用した単体テストを使用するためのそのクラスは、次のコードリストにあります。

EnumComparisonTest.groovy