Java generics를 꽤 잘 이해했다고 생각했지만 java.lang.Enum에서 다음을 발견했습니다.
class Enum<E extends Enum<E>>
누군가이 유형 매개 변수를 해석하는 방법을 설명 할 수 있습니까? 유사한 유형 매개 변수가 사용될 수있는 다른 예를 제공하기위한 보너스 포인트.
Java generics를 꽤 잘 이해했다고 생각했지만 java.lang.Enum에서 다음을 발견했습니다.
class Enum<E extends Enum<E>>
누군가이 유형 매개 변수를 해석하는 방법을 설명 할 수 있습니까? 유사한 유형 매개 변수가 사용될 수있는 다른 예를 제공하기위한 보너스 포인트.
답변:
열거 형의 형식 인수는 동일한 형식 인수를 가진 열거 형에서 파생되어야 함을 의미합니다. 어떻게 이런 일이 일어날 수 있습니까? 형식 인수를 새 형식 자체로 만듭니다. 따라서 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를 사용하는 사용자는 실제로 이것을 신경 쓸 필요가 없습니다. 끔찍하게 복잡하고 어디에 있는지 알기 위해 여러 번 반복했습니다.
편집 : 이것으로도 자체가 괜찮지 만 같은 유형이 아닌 유형 인수를 사용하는 홀수 유형을 만드는 것을 막을 수는 없습니다. 목적은 잘못된 경우 로부터 보호하기보다는 올바른 경우에 혜택을 제공 하는 것 입니다.
따라서 Enum
Java에서 "특별히"처리되지 않은 경우 (주석에서 언급 한대로) 다음 유형을 작성할 수 있습니다.
public class First extends Enum<First> {}
public class Second extends Enum<First> {}
Second
Comparable<First>
오히려 구현할 Comparable<Second>
것이지만 First
그 자체로는 괜찮을 것입니다.
Enum
유형 매개 변수 유형을 반환하는 인스턴스 메소드가 없습니다.
class Enum<E>
모든 경우에 충분 하다고 주장합니다 . 그리고 Generics에서는 실제로 유형 안전을 보장해야하는 경우 더 제한적인 범위 만 사용해야합니다.
Enum
서브 클래스는 항상 자동 생성되지 않은, 당신이 필요로 할 유일한 이유 class Enum<E extends Enum<?>>
이상이 class Enum<E>
액세스 할 수있는 기능입니다 ordinal
에 대한이 compareTo()
. 그러나 그것에 대해 생각하면 언어 관점에서 서수를 통해 두 가지 유형의 열거 형을 비교할 수 있다는 것은 의미가 없습니다. 따라서 Enum.compareTo()
그 사용 의 구현은 자동 생성되는 서브 클래스 ordinal
의 맥락에서만 의미가 Enum
있습니다. 수동으로 하위 클래스 수 있다면 Enum
, compareTo
아마 할 것이다 abstract
.
다음은이 책에서 설명의 수정 된 버전 인 자바 제네릭과 컬렉션 : 우리는 가지고 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)
Season
구현 을 고집하고 싶지 Comparable<Season>
않습니까?
compareTo
메서드 의 인수 는 하위 유형 으로 선언Enum
되었거나 컴파일러에 서 수가 없다고 (올바르게) 말할 것입니다.
Enum
가질 수 있습니다 . 그것은 그럼, 서로 열거 한 유형을 비교할 수 있도록 많은 이해가되지 것 '들 어쨌든 선언으로 이해가되지 것입니다. 경계는 이것에 어떤 도움도 제공하지 않습니다. class OneEnum extends Enum<AnotherEnum>{}
Enum
Enum
compareTo
public class Enum<E extends Enum<?>>
라면 충분할 것입니다.
이것은 간단한 예제와 서브 클래스에 대한 체인 메소드 호출을 구현하는 데 사용할 수있는 기술로 설명 할 수 있습니다. 아래 예제에서는 체인이 작동하지 않도록 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/…
Node<T>
하면 (사례가 아님) 시간을 절약하기 위해 무시합니다.
return (SELF) this;
이로 컴파일되어 있음을 return this;
알 수 있습니다.
그게 무슨 뜻인지 궁금한 사람은 아닙니다. 혼란스러운 Java 블로그를 참조하십시오 .
"클래스가이 클래스를 확장하면 매개 변수 E를 전달해야합니다. 매개 변수 E의 경계는이 매개 변수를 동일한 매개 변수 E로 확장하는 클래스에 대한 것입니다."
이 게시물은 '재귀 적 제네릭 형식'이라는 문제를 완전히 명확하게 설명했습니다. 이 특정 구조가 필요한 다른 사례를 추가하고 싶었습니다.
일반 그래프에 일반 노드가 있다고 가정하십시오.
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와 관련이없는 곳 을 만들 수 있습니다 .
class Node<T>
합니까?
class Node<T>
귀하의 예와 완전히 일치합니다.
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>
.
Wikipedia에 따르면이 패턴을 Curiously recurring template pattern 이라고 합니다. 기본적으로 CRTP 패턴을 사용하면 유형 캐스팅없이 하위 클래스 유형을 쉽게 참조 할 수 있습니다. 즉, 패턴을 사용하여 가상 기능을 모방 할 수 있습니다.