Java Enum определение - PullRequest
       21

Java Enum определение

142 голосов
/ 17 октября 2008

Мне показалось, что я достаточно хорошо понимаю дженерики Java, но потом я наткнулся на следующее в java.lang.Enum:

class Enum<E extends Enum<E>>

Может ли кто-нибудь объяснить, как интерпретировать этот параметр типа? Бонусные баллы за предоставление других примеров использования аналогичного параметра типа.

Ответы [ 6 ]

103 голосов
/ 17 октября 2008

Это означает, что аргумент типа для enum должен происходить от enum, который сам имеет такой же аргумент типа. Как это может случиться? Делая аргумент типа новым типом. Так что если бы у меня был enum с именем StatusCode, он был бы эквивалентен:

public class StatusCode extends Enum<StatusCode>

Теперь, если вы проверите ограничения, у нас есть Enum<StatusCode> - так что E=StatusCode. Давайте проверим: E распространяется Enum<StatusCode>? Да! Мы в порядке.

Вы вполне можете спросить себя, в чем смысл этого :) Ну, это означает, что API для Enum может ссылаться на себя - например, быть в состоянии сказать, что Enum<E> реализует Comparable<E>. Базовый класс может выполнять сравнения (в случае перечислений), но он может убедиться, что он сравнивает только правильные типы перечислений друг с другом. (РЕДАКТИРОВАТЬ: Ну, почти - см. Редактирование внизу.)

Я использовал нечто подобное в моем C # -порте 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 не нужно на самом деле заботиться об этом - это ужасно сложно, и потребовалось несколько итераций, чтобы добраться туда, где он есть.

РЕДАКТИРОВАТЬ: обратите внимание, что это не мешает вам создавать нечетные типы, которые используют аргумент типа, который сам по себе, но не тот же тип. Цель состоит в том, чтобы предоставить преимущества в правильном деле, а не защитить вас от неправильного дела.

Таким образом, если бы Enum не обрабатывалось «специально» в Java, вы могли бы (как отмечено в комментариях) создать следующие типы:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Second будет реализовывать Comparable<First> вместо Comparable<Second> ... но сам First будет в порядке.

26 голосов
/ 17 апреля 2009

Ниже приведена модифицированная версия объяснения из книги Обобщения и коллекции Java : У нас есть 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)

5 голосов
/ 28 ноября 2013

Это можно проиллюстрировать на простом примере и методике, которая может быть использована для реализации вызовов связанных методов для подклассов. В приведенном ниже примере 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!
    }
}
2 голосов
/ 13 октября 2009

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

Предположим, у вас есть общие узлы в общем графике:

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(){...}
}
2 голосов
/ 17 октября 2008

Вы не единственный, кому интересно, что это значит; см. Хаотический Java-блог .

«Если класс расширяет этот класс, он должен передать параметр E. Границы параметра E относятся к классу, который расширяет этот класс тем же параметром E».

1 голос
/ 05 июня 2015

Если вы посмотрите на исходный код 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> объекту.

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

...