Java Enum 정의


151

Java generics를 꽤 잘 이해했다고 생각했지만 java.lang.Enum에서 다음을 발견했습니다.

class Enum<E extends Enum<E>>

누군가이 유형 매개 변수를 해석하는 방법을 설명 할 수 있습니까? 유사한 유형 매개 변수가 사용될 수있는 다른 예를 제공하기위한 보너스 포인트.


9
내가 가장 좋아하는 설명은 다음과 같습니다. Groking Enum (일명 Enum & lt; E는 Enum & lt; E >>)
— Alan Moore

이 질문은 더 나은 해답이 있습니다 stackoverflow.com/a/3068001/2413303을
— EpicPandaForce

답변:


105

열거 형의 형식 인수는 동일한 형식 인수를 가진 열거 형에서 파생되어야 함을 의미합니다. 어떻게 이런 일이 일어날 수 있습니까? 형식 인수를 새 형식 자체로 만듭니다. 따라서 StatusCode라는 열거 형이 있으면 다음과 같습니다.

public class StatusCode extends Enum<StatusCode>

이제 제약 조건을 확인하면 Enum<StatusCode>그렇게 E=StatusCode됩니다. 확인하자 : E확장 Enum<StatusCode>합니까? 예! 우린 괜찮아

이것의 요점이 무엇인지 스스로에게 물어 보는 것이 좋을 것이다. 글쎄, 그것은 Enum 용 API가 예를 들어 Enum<E>구현 하는 것을 말할 수있는 자체를 참조 할 수 있다는 것을 의미한다 Comparable<E>. 기본 클래스는 비교 (enum의 경우)를 수행 할 수 있지만 올바른 종류의 열거 만 서로 비교할 수 있습니다. (편집 : 글쎄, 거의-하단의 편집을 참조하십시오.)

ProtocolBuffers의 C # 포트에서 비슷한 것을 사용했습니다. "메시지"(불변) 및 "빌더"(변경 가능, 메시지 작성에 사용됨)가 있으며 유형 쌍으로 제공됩니다. 관련된 인터페이스는 다음과 같습니다.

public interface IBuilder<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

public interface IMessage<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

즉, 메시지에서 적절한 빌더를 얻을 수 있으며 (예 : 메시지 사본을 가져와 비트를 변경) 빌더에서 메시지 작성을 마치면 적절한 메시지를 얻을 수 있습니다. API를 사용하는 사용자는 실제로 이것을 신경 쓸 필요가 없습니다. 끔찍하게 복잡하고 어디에 있는지 알기 위해 여러 번 반복했습니다.

편집 : 이것으로도 자체가 괜찮지 만 같은 유형이 아닌 유형 인수를 사용하는 홀수 유형을 만드는 것을 막을 수는 없습니다. 목적은 잘못된 경우 로부터 보호하기보다는 올바른 경우에 혜택을 제공 하는 것 입니다.

따라서 EnumJava에서 "특별히"처리되지 않은 경우 (주석에서 언급 한대로) 다음 유형을 작성할 수 있습니다.

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

SecondComparable<First>오히려 구현할 Comparable<Second>것이지만 First그 자체로는 괜찮을 것입니다.


1
@artsrc : 왜 빌더와 메시지 모두에서 일반화되어야 하는지를 기억 나지 않습니다. 나는 비록 그것이 필요하지 않았다면 그 길을 따라 가지 않았을 것이라고 확신한다. :)
— Jon Skeet

1
@SayemAhmed : 네, 방해하지 않는 그 유형을 혼합 측면. 이에 대한 메모를 추가하겠습니다.
— Jon Skeet

1
"프로토콜 버퍼의 C # 포트에서 비슷한 것을 사용했습니다." 그러나 빌더에는 유형 매개 변수 유형을 리턴하는 인스턴스 메소드가 있으므로 다릅니다. Enum유형 매개 변수 유형을 반환하는 인스턴스 메소드가 없습니다.
— newacct

1
@ JonSkeet : 열거 형 클래스는 항상 자동 생성되므로 class Enum<E>모든 경우에 충분 하다고 주장합니다 . 그리고 Generics에서는 실제로 유형 안전을 보장해야하는 경우 더 제한적인 범위 만 사용해야합니다.
— newacct

1
@JonSkeet : 경우 또한, Enum서브 클래스는 항상 자동 생성되지 않은, 당신이 필요로 할 유일한 이유 class Enum<E extends Enum<?>>이상이 class Enum<E>액세스 할 수있는 기능입니다 ordinal에 대한이 compareTo(). 그러나 그것에 대해 생각하면 언어 관점에서 서수를 통해 두 가지 유형의 열거 형을 비교할 수 있다는 것은 의미가 없습니다. 따라서 Enum.compareTo()그 사용 의 구현은 자동 생성되는 서브 클래스 ordinal의 맥락에서만 의미가 Enum있습니다. 수동으로 하위 클래스 수 있다면 Enum, compareTo아마 할 것이다 abstract.
— newacct

27

다음은이 책에서 설명의 수정 된 버전 인 자바 제네릭과 컬렉션 : 우리는 가지고 Enum선언

enum Season { WINTER, SPRING, SUMMER, FALL }

수업으로 확장됩니다

final class Season extends ...

어디서나 ...열거 형의 매개 변수가있는 기본 클래스가되어야합니다. 무엇이 필요한지 알아 봅시다. 요구 사항 중 하나 Season는 구현해야한다는 것 Comparable<Season>입니다. 그래서 우리는 필요합니다

Season extends ... implements Comparable<Season>

이 기능을 사용하기 위해 무엇을 사용할 수 ...있습니까? 이 매개 변수화 여야하므로 Enum다음 중 하나를 선택할 Enum<Season>수 있습니다.

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

따라서 Enum같은 유형에 매개 변수화됩니다 Season. 의 초록 Season과 매개 변수는 Enum만족하는 모든 유형입니다.

 E extends Enum<E>

Maurice Naftalin (공동 저자, Java Generics and Collections)


1
@newacct 좋아, 이제 알겠다 : 모든 열거 형이 Enum <E>의 인스턴스가 되길 원하십니까? (Enum 하위 유형의 인스턴스 인 경우 위의 인수가 적용됩니다.) 그러나 더 이상 유형 안전 열거 형이 없으므로 열거 형을 갖는 요점을 전혀 잃지 않습니다.
— Maurice Naftalin

1
@newacct Season구현 을 고집하고 싶지 Comparable<Season>않습니까?
— Maurice Naftalin

2
@newacct Enum의 정의를 보라. 한 인스턴스를 다른 인스턴스와 비교하려면 서수를 비교해야합니다. 따라서 compareTo메서드 의 인수 는 하위 유형 으로 선언Enum 되었거나 컴파일러에 서 수가 없다고 (올바르게) 말할 것입니다.
— Maurice Naftalin

2
@MauriceNaftalin : Java가 서브 클래 싱을 수동으로 금지하지 않았다면 지금 어떻게 선언 되어 있더라도을 Enum가질 수 있습니다 . 그것은 그럼, 서로 열거 한 유형을 비교할 수 있도록 많은 이해가되지 것 '들 어쨌든 선언으로 이해가되지 것입니다. 경계는 이것에 어떤 도움도 제공하지 않습니다. class OneEnum extends Enum<AnotherEnum>{}EnumEnumcompareTo
— newacct February

2
@MauriceNaftalin : 서 수가 그 이유 public class Enum<E extends Enum<?>>라면 충분할 것입니다.
— newacct February

6

이것은 간단한 예제와 서브 클래스에 대한 체인 메소드 호출을 구현하는 데 사용할 수있는 기술로 설명 할 수 있습니다. 아래 예제에서는 체인이 작동하지 않도록 setName반환 Node합니다 City.

class Node {
    String name;

    Node setName(String name) {
        this.name = name;
        return this;
    }
}

class City extends Node {
    int square;

    City setSquare(int square) {
        this.square = square;
        return this;
    }
}

public static void main(String[] args) {
    City city = new City()
        .setName("LA")
        .setSquare(100);    // won't compile, setName() returns Node
}

따라서 일반 선언에서 하위 클래스를 참조 할 수 있으므로 City이제 올바른 유형을 반환합니다.

abstract class Node<SELF extends Node<SELF>>{
    String name;

    SELF setName(String name) {
        this.name = name;
        return self();
    }

    protected abstract SELF self();
}

class City extends Node<City> {
    int square;

    City setSquare(int square) {
        this.square = square;
        return self();
    }

    @Override
    protected City self() {
        return this;
    }

    public static void main(String[] args) {
       City city = new City()
            .setName("LA")
            .setSquare(100);                 // ok!
    }
}

귀하의 솔루션은 확인되지 않은 캐스트를 가지고 있습니다 : return (CHILD) this;getThis () 메소드 추가 고려 : protected CHILD getThis() { return this; } 참조 : angelikalanger.com/GenericsFAQ/FAQSections/…
— Roland

@Roland 링크에 감사드립니다. 아이디어를 빌 렸습니다. 이 특별한 경우에 왜 이것이 나쁜 습관인지 설명하는 기사로 나를 설명하거나 지시 할 수 있습니까? 링크의 메소드에는 더 많은 타이핑이 필요하며 이것이 이것을 피하는 주요 이유입니다. 이 경우에는 캐스트 오류를 ​​본 적이 없습니다. + 불가피한 캐스트 오류가 있음을 알고 있습니다. 즉, 여러 유형의 객체를 동일한 컬렉션에 저장하는 경우입니다. 따라서 확인되지 않은 캐스트가 중요하지 않고 디자인이 다소 복잡 Node<T>하면 (사례가 아님) 시간을 절약하기 위해 무시합니다.
— Andrey Chaschev

구문 설탕을 추가하는 것 외에는 편집이 이전과 다르지 않다면 다음 코드가 실제로 컴파일되지만 런타임 오류가 발생한다는 것을 고려하십시오.`Node <City> node = new Node <City> () .setName ( "node"). setSquare (1);`자바 바이트 코드를 보면 유형 삭제로 인해 명령문 return (SELF) this;이로 컴파일되어 있음을 return this;알 수 있습니다.
— Roland

@Roland 덕분에, 이것이 내가 필요한 것입니다-자유 때 예제를 업데이트 할 것입니다.
— Andrey Chaschev

다음 링크도 좋습니다 : angelikalanger.com/GenericsFAQ/FAQSections/…
— Roland

3

그게 무슨 뜻인지 궁금한 사람은 아닙니다. 혼란스러운 Java 블로그를 참조하십시오 .

"클래스가이 클래스를 확장하면 매개 변수 E를 전달해야합니다. 매개 변수 E의 경계는이 매개 변수를 동일한 매개 변수 E로 확장하는 클래스에 대한 것입니다."


1

이 게시물은 '재귀 적 제네릭 형식'이라는 문제를 완전히 명확하게 설명했습니다. 이 특정 구조가 필요한 다른 사례를 추가하고 싶었습니다.

일반 그래프에 일반 노드가 있다고 가정하십시오.

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

그런 다음 특수한 유형의 그래프를 가질 수 있습니다.

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}

여전히 class Foo extends Node<City>Foo가 City와 관련이없는 곳 을 만들 수 있습니다 .
— newacct

1
그래, 그게 틀 렸니? 나는 그렇게 생각하지 않습니다. Node <City>에서 제공하는 기본 계약은 여전히 ​​유효하며 Foos와 작업을 시작하지만 ADT에서 도시를 가져 오기 때문에 Foo 하위 클래스 만 유용하지 않습니다. 이것에 대한 사용 사례가있을 수 있지만 일반 매개 변수를 서브 클래스와 동일하게 만드는 것이 더 간단하고 유용 할 가능성이 큽니다. 그러나 어느 쪽이든, 디자이너는 그 선택을합니다.
— mdma

@mdma : 동의합니다. 그렇다면 바운드는 무엇을 제공 class Node<T>합니까?
— newacct

1
@nozebacle :이 예제는 "이 특정 구조가 필요하다"는 것을 보여주지 않습니다. class Node<T>귀하의 예와 완전히 일치합니다.
— newacct

1

Enum소스 코드 를 보면 다음과 같습니다.

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    } 
}

먼저 무엇을 E extends Enum<E>의미합니까? 이것은 type 매개 변수가 Enum에서 확장되어 원시 형식으로 매개 변수화되지 않는다는 것을 의미합니다 (자체적으로 매개 변수화 됨).

열거 형이있는 경우 관련이 있습니다.

public enum MyEnum {
    THING1,
    THING2;
}

내가 올바르게 알면

public final class MyEnum extends Enum<MyEnum> {
    public static final MyEnum THING1 = new MyEnum();
    public static final MyEnum THING2 = new MyEnum();
}

따라서 MyEnum은 다음과 같은 메소드를받습니다.

public final int compareTo(MyEnum o) {
    Enum<?> other = (Enum<?>)o;
    Enum<MyEnum> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

그리고 더 중요한 것은

    @SuppressWarnings("unchecked")
    public final Class<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

이 만드는 getDeclaringClass()적절한에 캐스팅 Class<T>개체를.

더 명확한 예는 제네릭 바운드를 지정하려는 경우이 구성을 피할 수없는 이 질문에 대답 한 것입니다 .


당신이 보여 compareTo주거나 경계를 getDeclaringClass요구하는 것은 없습니다 extends Enum<E>.
— newacct

0

Wikipedia에 따르면이 패턴을 Curiously recurring template pattern 이라고 합니다. 기본적으로 CRTP 패턴을 사용하면 유형 캐스팅없이 하위 클래스 유형을 쉽게 참조 할 수 있습니다. 즉, 패턴을 사용하여 가상 기능을 모방 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.