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.