Определение Java Enum
Мне показалось, что я хорошо разбираюсь в Java-дженериках, но потом я нашел в java.lang.Enum следующее:
class Enum<E extends Enum<E>>
Может кто-нибудь объяснить, как интерпретировать этот параметр типа? Бонусные баллы за предоставление других примеров того, где можно использовать аналогичный параметр.
Ответы
Ответ 1
Это означает, что аргумент type для перечисления должен выводиться из перечисления, которое имеет один и тот же аргумент типа. Как это может произойти? Сделав аргумент типа новым типом. Поэтому, если у меня есть перечисление, называемое StatusCode, оно будет эквивалентно:
public class StatusCode extends Enum<StatusCode>
Теперь, если вы проверите ограничения, у нас есть Enum<StatusCode>
- so E=StatusCode
. Пусть проверка: E
extend Enum<StatusCode>
? Да! Мы в порядке.
Вы можете спросить себя, в чем смысл этого. Ну, это означает, что API для Enum может ссылаться на себя - например, имея возможность сказать, что Enum<E>
реализует Comparable<E>
. Базовый класс способен выполнять сравнения (в случае перечислений), но он может убедиться, что он сравнивает только правильный тип перечислений друг с другом. (EDIT: ну, почти - см. Редактирование внизу.)
Я использовал что-то подобное в моем С# -порте ProtocolBuffers. Существуют "сообщения" (неизменяемые) и "строители" (изменяемые, используемые для создания сообщения), и они являются парами типов. В число задействованных интерфейсов входят:
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, которые не должны действительно заботиться об этом, хотя это ужасно сложно, и потребовалось несколько итераций, чтобы добраться туда, где они есть.
EDIT: Обратите внимание, что это не мешает вам создавать нечетные типы, которые используют аргумент типа, который сам по себе хорошо, но не тот же тип. Цель состоит в том, чтобы приносить пользу в правильном случае, а не защищать вас от неправильного дела.
Так что, если Enum
в любом случае не обрабатывались "специально" в Java, вы могли (как отмечено в комментариях) создать следующие типы:
public class First extends Enum<First> {}
public class Second extends Enum<First> {}
Second
будет реализовывать Comparable<First>
, а не Comparable<Second>
... но сам First
будет.
Ответ 2
Ниже приведена модифицированная версия объяснения из книги Java Generics and Collections:
У нас есть объявленный Enum
enum Season { WINTER, SPRING, SUMMER, FALL }
который будет расширен до класса
final class Season extends ...
где ...
должен быть каким-то параметризованным базовым классом для Enums. Пусть работа
что это должно быть. Ну, одним из требований для 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>
Морис Нафталин (соавтор, Java Generics and Collections)
Ответ 3
Это может быть проиллюстрировано простым примером и техникой, которая может быть использована для реализации цепных вызовов методов для подклассов. В приведенном ниже примере setName
возвращает a 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!
}
}
Ответ 4
Вы не единственный, кто задается вопросом, что это значит; см. Хаотический блог Java.
"Если класс расширяет этот класс, он должен передать параметр E. Параметр Es ограничивается для класса, который расширяет этот класс с тем же параметром E".
Ответ 5
Это сообщение полностью разъяснило мне эту проблему "рекурсивных родовых типов".
Я просто хотел добавить еще один случай, когда эта конкретная структура необходима.
Предположим, что у вас есть общие узлы в общем графике:
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(){...}
}
Ответ 6
В случае Enum
это бесполезно. Все будет работать одинаково, если оно будет объявлено как
class Enum<E>
Ответ 7
Если вы посмотрите исходный код 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>
? Это означает, что параметр типа является чем-то, что продолжается от 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>
.
Ярким примером является тот, который я ответил на этот вопрос, где вы не можете избежать этой конструкции, если хотите указать общую границу.