Java 열거 리터럴이 일반 유형 매개 변수를 가질 수없는 이유는 무엇입니까?


148

Java 열거 형은 훌륭합니다. 제네릭도 마찬가지입니다. 물론 우리는 유형 삭제로 인해 후자의 한계를 알고 있습니다. 그러나 이해할 수없는 한 가지가 있습니다. 왜 이런 식으로 열거 형을 만들 수 없습니까?

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

이 제네릭 형식 매개 변수 <T>는 여러 곳에서 유용 할 수 있습니다. 메소드에 일반 유형 매개 변수를 상상해보십시오.

public <T> T getValue(MyEnum<T> param);

또는 열거 형 클래스 자체에서도 :

public T convert(Object o);

보다 구체적인 예 # 1

위의 예는 일부에서는 너무 추상적으로 보일 수 있으므로 여기에 내가 원하는 이유에 대한 더 실제적인 예가 있습니다. 이 예에서는 사용하고 싶습니다

  • 열거 형, 유한 속성 키 집합을 열거 할 수 있기 때문에
  • 제네릭 (Generics) : 속성 저장을위한 메서드 수준 형식 안전성을 가질 수 있기 때문에
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

보다 구체적인 예 # 2

데이터 유형 열거가 있습니다.

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

각 열거 형 리터럴에는 분명히 제네릭 형식을 기반으로 추가 속성이 있습니다. <T> 을 갖는 동시에 열거 형 (불변, 단일 톤, 열거 가능 등)입니다.

질문:

아무도 이것을 생각하지 않았습니까? 이것이 컴파일러 관련 제한입니까? 사실 키워드 " enum "이 JVM에 생성 된 코드를 나타내는 구문 설탕으로 구현 된다는 점을 고려하면 이 제한을 이해하지 못합니다.

누가 나에게 설명 할 수 있습니까? 대답하기 전에 다음을 고려하십시오.

  • 일반 유형이 지워지는 것을 알고 있습니다 :-)
  • Class 객체를 사용하는 해결 방법이 있다는 것을 알고 있습니다. 그들은 해결 방법입니다.
  • 제네릭 형식은 해당되는 경우 (예 : convert () 메서드 호출시) 컴파일러 생성 형식 캐스트를 생성합니다.
  • 제네릭 형식 <T>는 열거 형에 있습니다. 따라서 열거 형의 각 리터럴에 바인딩됩니다. 따라서 컴파일러는 다음과 같은 내용을 작성할 때 적용 할 유형을 알고 있습니다.String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • T getvalue()메소드 의 일반 유형 매개 변수에도 동일하게 적용됩니다 . 컴파일러는 호출 할 때 타입 캐스팅을 적용 할 수 있습니다String string = someClass.getValue(LITERAL1)

3
이 제한 사항도 이해하지 못합니다. 나는 최근에 열거 형에 다른 "비교 가능한"유형이 포함되어 있고 제네릭을 사용하면 동일한 유형의 비교 가능한 유형 만 경고하지 않고 비교할 수 있습니다 (런타임에 적절한 유형을 비교하더라도). 열거 형에 바인딩 된 유형을 사용하여 지원되는 비교 가능한 유형을 지정하여 이러한 경고를 제거 할 수 있었지만 대신 SuppressWarnings 주석을 추가해야했습니다. compareTo와는 어쨌든 클래스 캐스트 예외를 발생하지 않습니다 때문에 ... 아직 확인 I 추측하지만
MetroidFan2002

5
(+1) 나는 프로젝트에서 유형 안전 격차를 막으려 고 노력하는 중입니다.이 임의의 제한으로 인해 죽었습니다. enumJava 1.5 이전에 사용했던 "typesafe enum"관용구로 바꾸십시오 . 갑자기 열거 형 멤버를 매개 변수화 할 수 있습니다. 아마도 내가 지금 할 것입니다.
Marko Topolnik

1
@EdwinDalorzo는 :에서 구체적인 예와 함께 질문 업데이트 jOOQ 이 과거에 대단히 유용했을 것이다.
Lukas Eder

2
@ LukasEder 지금 당신의 요점을 참조하십시오. 멋진 새 기능인 것 같습니다. 어쩌면 프로젝트 코인 메일 링리스트 에서 제안해야 할 수도 있습니다 . 열거 형에는 다른 흥미로운 제안이 있지만 당신과 같은 제안은 없습니다.
Edwin Dalorzo

1
전적으로 동의합니다. 제네릭이없는 열거 형은 무너집니다. 귀하의 사례 # 1도 내 것입니다. 일반 열거 형이 필요한 경우 JDK5를 포기하고 일반 오래된 Java 1.4 스타일로 구현합니다. 이 접근법에는 추가 이점이 있습니다 . 나는 한 클래스 또는 패키지에 모든 상수를 강요하지는 않습니다. 따라서 패키지 별 기능 스타일이 훨씬 우수합니다. 이것은 구성과 같은 "열"에 대해서만 완벽합니다. 상수는 논리적 의미에 따라 패키지로 분산됩니다 (모두를 보려면 유형 계층 구조가 표시됨).
Tomáš Záluský

답변:


50

이것은 현재 JEP-301 Enhanced Enums 에서 논의되고 있습니다. JEP에 주어진 예는 바로 내가 찾던 것입니다.

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

불행히도 JEP는 여전히 다음과 같은 중요한 문제로 어려움을 겪고 있습니다. http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
2018 년 12 월 현재, JEP 301에 대한 삶의 징후가 다시 있었지만, 그 논의를 생략하면 문제가 여전히 해결되지 않았다는 것이 분명해집니다.
Pont

11

대답은 질문에 있습니다.

유형 삭제 때문에

인수 유형이 지워 지므로이 두 가지 방법 중 어느 것도 사용할 수 없습니다.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

이러한 방법을 실현하려면 다음과 같이 열거 형을 구성 할 수 있습니다.

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

2
글쎄, 삭제는 컴파일 타임에 발생합니다. 그러나 컴파일러는 형식 검사에 일반 형식 정보를 사용할 수 있습니다. 그리고 나서 제네릭 타입을 타입 캐스트로 "변환"합니다. 나는 질문을 다시 말할 것이다
Lukas Eder

2
내가 완전히 따르는 지 확실하지 않습니다. 가져 가라 public T convert(Object);. 이 방법은 예를 들어 다양한 유형을 <T>로 좁힐 수 있다고 추측합니다. String 객체의 생성은 런타임입니다-컴파일러는 수행하지 않습니다. 따라서 String.class와 같은 런타임 유형을 알아야합니다. 아니면 뭔가 빠졌습니까?
Martin Algesten

6
제네릭 형식 <T>이 열거 형 (또는 생성 된 클래스)에 있다는 사실을 놓친 것 같습니다. 열거 형의 유일한 인스턴스는 리터럴이며 모두 상수 제네릭 형식 바인딩을 제공합니다. 따라서 public T convert (Object)에서 T에 대한 모호함이 없습니다.
Lukas Eder

2
아하! 알았다. 당신은 나보다 영리합니다 :)
Martin Algesten

1
나는 그것이 다른 모든 것과 비슷한 방식으로 평가되는 열거 형에 대해 클래스에 온 것 같아요. 자바에서는 일 일정 해 보이지만 그렇지 않습니다. public final static String FOO;정적 블록을 고려하십시오 static { FOO = "bar"; }-컴파일 타임에 알려진 경우에도 상수가 평가됩니다.
Martin Algesten

5

당신이 할 수 없기 때문에. 진심으로. 언어 사양에 추가 할 수 있습니다. 그렇지 않았습니다. 약간의 복잡성이 추가됩니다. 비용에 대한 이점은 우선 순위가 높지 않다는 것을 의미합니다.

업데이트 : 현재 JEP 301 : Enhanced Enums 의 언어에 추가되고 있습니다 .


합병증에 대해 자세히 설명해 주시겠습니까?
Mr_and_Mrs_D

4
나는 며칠 동안 이것에 대해 생각하고 있었고 여전히 합병증이 없습니다.
Marko Topolnik

4

ENUM에는 작동하지 않는 다른 방법이 있습니다. 어떤 것MyEnum.values() 돌아 올까요?

이건 어떤가요 MyEnum.valueOf(String name) ?

컴파일러가 다음과 같은 일반적인 방법을 만들 수 있다고 생각하면 valueOf

공개 정적 MyEnum valueOf (문자열 이름);

처럼 호출하면 MyEnum<String> myStringEnum = MyEnum.value("some string property")작동하지 않습니다. 예를 들어 전화하면 MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")어떻게됩니까? 예를 들어 MyEnum.<Int>value("some double property")삭제 유형으로 인해 호출 할 때 예외를 발생 시키거나 null을 반환하는 것과 같이 해당 메소드를 올바르게 작동하도록 구현할 수 없습니다 .


2
왜 작동하지 않습니까? 그들은 단순히 와일드 카드를 ... 사용하십시오 : MyEnum<?>[] values()MyEnum<?> valueOf(...)
루카스 에델

1
그러나 MyEnum<Int> blabla = valueOf("some double property");유형이 호환되지 않기 때문에 이와 같은 할당을 수행 할 수 없습니다 . 또한 이중 속성 이름에 존재하지 않는 MyEnum <Int>을 반환하고 삭제로 인해 해당 메소드가 제대로 작동하지 않기 때문에이 경우 null을 얻습니다.
user1944408

또한 values ​​()를 반복하는 경우 일반적으로 원하는 것이 아닌 MyEnum <?>을 사용해야합니다. 예를 들어 Int 속성 만 반복 할 수 없기 때문입니다. 또한 피하고 싶은 캐스팅을 많이해야합니다. 모든 유형에 대해 다른 열거 형을 만들거나 인스턴스를 사용하여 클래스를 만들 것을 제안합니다.
user1944408

글쎄, 둘 다 가질 수는 없을 것 같아. 보통, 나는 내 자신의 "열"구현에 의지한다. 그것은 단지 Enum다른 유용한 기능들이 많이 있으며, 이것은 선택적이고 매우 유용 할 것입니다.
Lukas Eder

0

솔직히 이것은 무엇보다 문제를 찾아내는 더 많은 해결책처럼 보입니다.

Java 열거 형의 전체 목적은 비슷한 String 또는 Integer 표현보다 일관성과 풍부 성을 제공하는 방식으로 비슷한 특성을 공유하는 유형 인스턴스의 열거를 모델링하는 것입니다.

교과서 열거 형의 예를 들어보십시오. 이것은 매우 유용하거나 일관성이 없습니다.

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

다른 행성에서 다른 일반 유형 변환을 원하는 이유는 무엇입니까? 어떤 문제가 해결됩니까? 언어 의미를 복잡하게하는 것이 정당합니까? 이 동작이 필요한 경우이를 달성하기위한 최고의 도구입니다.

또한 복잡한 전환을 어떻게 관리 하시겠습니까?

인스턴스

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

String Integer이름이나 서수를 제공 하기에 충분 합니다. 그러나 제네릭을 사용하면 모든 유형을 제공 할 수 있습니다. 로의 전환을 MyComplexClass어떻게 관리 하시겠습니까? 이제 컴파일러가 제네릭 열거 형에 제공 할 수있는 제한된 유형의 하위 집합이 있음을 알고 이미 많은 프로그래머를 피할 수없는 개념 (Generics)에 대한 추가 혼란을 도입함으로써 두 가지 구문을 모색했습니다.


17
유용하지 않은 몇 가지 예를 생각하면 결코 유용하지 않을 것이라는 끔찍한 주장입니다.
Elias Vasylenko

1
예제는 요점을 뒷받침합니다. 열거 형 인스턴스는 유형의 하위 클래스이며 훌륭하고 단순합니다. 제네릭을 포함하는 것은 매우 모호한 이익을 위해 복잡성 측면에서 웜의 캔입니다. 당신이 저를 downvote 할 경우에 당신은 또한 너무 많은 말로 같은 것을 말한 Tom Hawtin을 downvote해야합니다
nsfyn55

1
@ nsfyn55 열거 형은 Java에서 가장 복잡하고 가장 마술적인 언어 기능 중 하나입니다. 예를 들어 정적 메서드를 자동 생성하는 다른 단일 언어 기능은 없습니다. 또한, 각 열거 형은 이미 입니다 제네릭 형식의 인스턴스입니다.
Marko Topolnik

1
@ nsfyn55 디자인 목표는 열거 형 멤버를 강력하고 유연하게 만드는 것이 었습니다. 따라서 사용자 지정 인스턴스 메서드 및 가변 인스턴스 변수와 같은 고급 기능을 지원합니다. 이들은 각 구성원에 대해 개별적으로 특화된 행동 을 갖도록 설계 되어 복잡한 사용 시나리오에서 적극적인 협력자로 참여할 수 있습니다. 무엇보다도 Java 열거 형은 일반 열거 형 관용구 유형을 안전하게 만들도록 설계 되었지만 형식 매개 변수가 없으면 가장 소중한 목표에 미치지 못합니다. 나는 그 이유 가 매우 분명하다고 확신 합니다.
Marko Topolnik

1
반동에 대한 언급을 눈치 채지 못했습니다 ... Java 그것을 지원하지만 단지 사용 사이트이므로 조금 다루기 어려워집니다. interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }우리는 소비되는 유형과 생산할 때마다 수동으로 해결해야하며 그에 따라 범위를 지정해야합니다.
Marko Topolnik

-2

"enum"은 열거 형의 약자입니다. 코드를 읽기 쉽도록 서수 대신에 명명 된 상수 세트 일뿐입니다.

형식 매개 변수 상수의 의도 된 의미가 무엇인지 알 수 없습니다.


1
에서 java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. 이제 보입니까? :-)
Lukas Eder

4
유형 매개 변수 상수의 의미가 무엇인지 알 수 없다고 말했습니다. 그래서 함수 매개 변수가 아닌 형식 매개 변수 상수를 보여주었습니다.
Lukas Eder

물론 Java 언어는 함수 포인터와 같은 것을 알지 못합니다. 여전히 상수는 매개 변수화 된 형식이 아니라 형식입니다. 그리고 유형 매개 변수 자체는 유형 변수가 아니라 일정합니다.
Ingo

그러나 열거 형 구문은 단지 구문 설탕입니다. 아래에서, 그것들은 정확히 같습니다 CASE_INSENSITIVE_ORDER... Comparator<T>열거 형 이라면 , 관련 바인딩이있는 리터럴이없는 이유는 <T>무엇입니까?
Lukas Eder

Comparator <T>가 열거 형이라면 실제로 모든 종류의 이상한 것들을 가질 수 있습니다. 아마도 Integer <T>와 같은 것입니다. 그러나 그렇지 않습니다.
Ingo

-3

기본적으로 열거 형을 인스턴스화 할 수 없기 때문에

JVM에서 허용 한 경우 T 클래스를 어디에 설정 하시겠습니까?

열거는 항상 동일하거나 최소한 데이터가 크게 바뀌지 않아야하는 데이터입니다.

새로운 MyEnum <> ()?

여전히 다음 접근법이 유용 할 수 있습니다.

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

8
setO()방법이 마음에 들지 않습니다 enum. 나는 enums 상수를 고려하고 불변 을 암시 합니다 . 그래서 가능하더라도 할 수 없습니다.
Martin Algesten

2
컴파일러가 생성 한 코드에 열거 형이 표시됩니다. 각 리터럴은 실제로 개인 생성자를 호출하여 생성됩니다. 따라서 컴파일 타임에 제네릭 형식을 생성자에 전달할 수 있습니다.
Lukas Eder

2
열거 형의 setter는 완전히 정상입니다. 특히 열거 형을 사용하여 싱글 톤을 만들 경우 (Joshua Bloch의 항목 3 유효 Java)
Preston
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.