Определение 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>.

Ярким примером является тот, который я ответил на этот вопрос, где вы не можете избежать этой конструкции, если хотите указать общую границу.