== (또는! =)를 사용하여 Java Enum 비교

대부분의 새로운 Java 개발자는 일반적으로 .NET Framework를 사용하는 대신 String.equals (Object)를 사용하여 Java 문자열을 비교해야한다는 사실을 빠르게 알게됩니다 ==. 이것은 거의 항상 String의 ID (메모리의 주소)가 아닌 String 내용 (String을 구성하는 실제 문자)을 비교하는 것을 의미하기 때문에 새로운 개발자에게 반복적으로 강조되고 강화됩니다 . ==Enum.equals (Object) 대신 사용할 수있는 개념을 강화해야한다고 생각합니다 . 이 게시물의 나머지 부분에서이 주장에 대한 이유를 제공합니다.

==Java 열거 형을 비교 하는 데 사용하는 것이 "equals"메서드를 사용하는 것보다 거의 항상 낫다고 생각하는 네 가지 이유가 있습니다 .

  1. ==열거에이 같은 예상 비교 (내용) 등을 제공합니다equals
  2. ==열거에은 (덜 장황)보다 틀림없이 더 읽을 수equals
  3. ==열거에 더 널 안전보다equals
  4. ==열거 형에 컴파일 시간 (정적) 런타임 검사보다는 확인을 제공합니다

위에 나열된 두 번째 이유 ( "논란의 여지가 더 읽기 쉬운")는 분명히 의견의 문제이지만 "덜 자세한 정보"에 대한 부분은 동의 할 수 있습니다. ==열거 형을 비교할 때 일반적으로 선호하는 첫 번째 이유 는 Java 언어 사양이 열거 형을 설명하는 방식의 결과입니다. 섹션 8.9 ( "열거")는 다음과 같이 설명합니다.

열거 형 유형을 명시 적으로 인스턴스화하려고하는 것은 컴파일 타임 오류입니다. Enum의 최종 복제 방법은 enum 상수가 절대 복제 될 수 없도록하고 직렬화 메커니즘에 의한 특수 처리는 역 직렬화의 결과로 중복 인스턴스가 생성되지 않도록합니다. 열거 형 유형의 반사 인스턴스화는 금지됩니다. 이 네 가지 요소를 함께 사용하면 열거 형 상수에 의해 정의 된 것 외에 열거 형 유형의 인스턴스가 존재하지 않습니다.

각 열거 형 상수의 인스턴스가 하나만 있기 때문에 둘 중 하나 이상이 열거 형 상수를 참조하는 것으로 알려진 경우 두 개체 참조를 비교할 때 equals 메서드 대신 == 연산자를 사용할 수 있습니다. (Enum의 equals 메소드는 단순히 인수에 대해 super.equals를 호출하고 결과를 리턴하여 ID 비교를 수행하는 최종 메소드입니다.)

위에 표시된 사양에서 발췌 한 내용 ==은 동일한 열거 형 상수의 인스턴스가 두 개 이상있을 수있는 방법이 없기 때문에 연산자를 사용하여 두 열거 형을 비교 하는 것이 안전하다는 것을 암시하고 명시 적으로 명시 합니다.

에 네 번째 장점 ==이상 .equals열거 형을 비교하는 경우는 컴파일시 안전과 관련이있다. Object.equals (Object)는 계약에 따라 임의의 .NET Framework를 가져와야 ==하기 .equals때문에를 사용 하면 컴파일 시간이 더 엄격 해 Object집니다. Java와 같이 정적으로 입력 된 언어를 사용할 때이 정적 입력의 장점을 최대한 활용한다고 생각합니다. 그렇지 않으면 동적으로 입력 된 언어를 사용합니다. 효과적인 Java의 반복되는 주제 중 하나는 가능할 때마다 정적 유형 검사를 선호한다는 것입니다.

예를 들어, 사용자 지정 열거 형을 호출 Fruit했고이를 java.awt.Color 클래스와 비교하려고 한다고 가정 해 보겠습니다 . 은 Using ==운영자 나를 문제의 (내가 제일 좋아하는 자바 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이 비교를 위해 메서드를 사용했다면 코드는 제대로 컴파일되었지만 열거 형이 클래스와 같을 수 false없기 때문에 메서드는 항상 false를 반환 합니다. 권장하지 않지만 다음을 사용하는 비교 방법은 다음과 같습니다.dustin.examples.Fruitjava.awt.Color.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을 피하는 것입니다. 효과적인 Java NullPointerException 처리에서 언급했듯이 일반적으로 예기치 않은 NullPointerExceptions 를 피하고 싶습니다 . null의 존재가 예외적 인 경우로 취급되기를 진정으로 원하는 제한된 상황이 있지만 종종 문제에 대한보다 우아한보고를 선호합니다. 열거 형을 비교할 때의 장점은 (NPE)를 ==만나지 않고도 null이 null이 아닌 열거 형과 비교할 수 있다는 것 NullPointerException입니다. 이 비교의 결과는 분명히입니다 false.

사용할 때 NPE를 피하는 한 가지 방법 은 열거 형 상수 또는 알려진 null이 아닌 열거 형에 대해 메서드 .equals(Object)를 호출 equals한 다음 의심스러운 문자 (null 일 수 있음)의 잠재적 열거 형을 매개 변수로 equals메서드에 전달하는 것입니다. 이것은 NPE를 피하기 위해 Java with Strings에서 수년 동안 종종 수행되었습니다. 그러나 ==연산자의 경우 비교 순서는 중요하지 않습니다. 나는 그것을 좋아한다.

나는 내 주장을했고 이제 몇 가지 코드 예제로 넘어 간다. 다음 목록은 앞서 언급 한 가상 과일 열거 형의 실현입니다.

Fruit.java

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

다음 코드 목록은 특정 열거 형 또는 개체가 특정 과일인지 감지하는 메서드를 제공하는 간단한 Java 클래스입니다. 나는 일반적으로 열거 형 자체에 이와 같은 수표를 넣었지만 설명 및 데모 목적으로 여기에 별도의 클래스에서 더 잘 작동합니다. 이 클래스에는 및 둘 다 와 비교 Fruit하기 위해 앞에서 설명한 두 가지 메서드가 포함되어 있습니다. 물론 열거 형을 클래스와 비교하는 데 사용하는 메서드 는 제대로 컴파일하기 위해 해당 부분을 주석 처리해야했습니다.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