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

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

class Enum<E extends Enum<E>>

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

Вот объяснение, которое мне больше всего нравится: Groking Enum (он же Enum & lt; E расширяет Enum & lt; E >>)

Alan Moore 01.01.2014 07:26

На этот вопрос есть ответы лучше: stackoverflow.com/a/3068001/2413303

EpicPandaForce 05.06.2015 16:22
Arne Burmeister 18.02.2020 18:09
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
158
3
22 635
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

public class StatusCode extends Enum<StatusCode>

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

Вы вполне можете спросить себя, в чем смысл этого :) Что ж, это означает, что API для Enum может ссылаться на себя - например, иметь возможность сказать, что Enum<E> реализует Comparable<E>. Базовый класс может выполнять сравнения (в случае перечислений), но он может гарантировать, что он сравнивает только правильные типы перечислений друг с другом. (Обновлено: ну, почти - см. Правку внизу.)

Я использовал нечто подобное в моем порте 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 подойдет.

Меня действительно интересует полезность такого рода вещей по сравнению со сложностью. Когда я пишу на C# ваше английское описание, я получаю следующее: <pre> interface IBuilder <TMessage> {IMessage <TMessage> GetMessage (); } interface IMessage <TMessage> {IBuilder <TMessage> GetBuilder (); } </pre>

artsrc 22.03.2012 06:12

@artsrc: Я не могу вспомнить, почему он должен быть универсальным как в конструкторе, так и в сообщении. Я почти уверен, что не пошел бы по этому пути, если бы мне это не понадобилось :)

Jon Skeet 22.03.2012 10:45

@JonSkeet: Возможно, я опоздал, но не мог перестать спрашивать. Действительно ли <E extends Enum<E>> мешает мне передать аргумент типа, который не является определяемым новым типом? Я имею в виду, например, что я мог бы определить два подтипа - один - public class First extends Enum<First>, а другой - public class Second extends Enum<First>. Объявление не мешает мне смешивать здесь подтипы.

MD Sayem Ahmed 18.07.2013 13:49

@SayemAhmed: Да, это не мешает который смешивать типы. Я добавлю об этом примечание.

Jon Skeet 18.07.2013 13:55

«Цель состоит в том, чтобы дать преимущества в правильном случае, а не защитить вас в неправильном случае». Но в случае с Enum нет никаких преимуществ, кроме тех, которые дает только class Enum<E>.

newacct 25.02.2015 08:17

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

newacct 25.02.2015 08:18

@newacct: У них есть compareTo, который принимает является параметром типа, хотя он позволяет работать другим вещам, таким как EnumMap. Мне слишком рано об этом думать, но я почти уверен, что являются приносит пользу ...

Jon Skeet 25.02.2015 09:51

@JonSkeet: Ну, 1) Enum нельзя разделить на подклассы вручную, поэтому уже гарантировано, что параметр совпадает с типом, даже без привязки. 2) Если Enum можно разделить на подклассы вручную, граница не препятствует тому, чтобы параметр типа был типом перечисления, отличным от типа экземпляра. 3) Если вы говорите, что compareTo просто нужно использовать ordinal из любого Enum, тогда ограничения class Enum<E extends Enum<?>> будет достаточно.

newacct 25.02.2015 12:53

@JonSkeet: И даже если Enum можно было бы подклассифицировать вручную, вся эта вещь compareTo все еще может быть сделана с class Enum<E>, сделав compareTo абстрактным, язык предоставит реализацию compareTo для типов enum, и если вы вручную подклассифицируете Enum, тогда вам необходимо предоставить соответствующий compareTo, соответствующий параметру вашего типа. Тот факт, что compareTo не является абстрактным, является проблемой дизайна библиотеки.

newacct 25.02.2015 12:55

@newacct: я подозреваю, что class Enum<E extends Enum<?>> действительно будет достаточно в большинстве случаев ... но, учитывая, что классы перечисления являются всегда генерируются автоматически, почему бы не выразить эту дополнительную информацию в системе типов? Я не вижу, чтобы это причиняло никакого вреда, кроме того, что вызывает немного головокружения ... и я подозреваю, что есть случаи являются, в которых это полезно, даже если мне все еще трудно придумать немедленный пример. Обычно лучше выражать больше информации в системе шрифтов, чем выражать меньше ...

Jon Skeet 25.02.2015 13:01

@JonSkeet: Учитывая, что классы перечисления всегда генерируются автоматически, я утверждаю, что class Enum<E> достаточно во всех случаях. А в Generics вы должны использовать более ограничительную границу, только если это действительно необходимо для обеспечения безопасности типов.

newacct 26.02.2015 03:01

@JonSkeet: Кроме того, если бы подклассы Enum не всегда создавались автоматически, единственная причина, по которой вам может понадобиться class Enum<E extends Enum<?>> вместо class Enum<E>, - это возможность доступа к ordinal для compareTo(). Однако, если подумать, с точки зрения языка не имеет смысла позволять сравнивать два разных типа перечислений через их порядковые номера. Следовательно, реализация Enum.compareTo(), использующая ordinal, имеет смысл только в контексте автогенерирования подклассов Enum. Если бы вы могли вручную создать подкласс Enum, compareTo, вероятно, должен был бы быть abstract.

newacct 26.02.2015 03:06

@newacct: Может ты и прав. Я еще не уверен, но, возможно, вы правы. В любом случае, я не думаю, что уместно вдаваться в подробности в комментариях к ответу старше 6 лет, поэтому я откажусь от этого.

Jon Skeet 26.02.2015 09:44

да. Я тоже заметил, что то, что вы упомянули в Edit, все еще возможно. В этом случае B.compareTo(A a) все равно будет компилироваться, хотя семантически он неверен.

Rajan Prasad 28.12.2017 15:22

Не только вам интересно, что это значит; см. Блог о хаотической Java.

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

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

Конечно, я согласен с тем, что Season extends Enum<Season>. Но это не объясняет, почему Enum имеет привязку к параметру типа, а не просто class Enum<E>.

newacct 30.11.2013 12:07

@newacct Поскольку в Season нет ничего особенного - приведенный выше аргумент применим к типу Любые, для которого можно параметризовать Enum. Итак, Weekday (или что-то еще) должен расширять Enum<Weekday> и т.д., и вообще любой E должен расширять Enum<E>. И именно об этом говорится в декларации.

Maurice Naftalin 12.12.2013 13:39

Вы все еще не поняли того, что я сказал. То, что декларация удовлетворяется, не объясняет, почему это необходимо.

newacct 12.12.2013 23:31

@newacct Хорошо, теперь я понял: вы хотите, чтобы все перечисления были экземплярами Enum <E>, верно? (Потому что, если они являются экземплярами подтипа Enum, применяется приведенный выше аргумент.) Но тогда у вас больше не будет типобезопасных перечислений, поэтому вообще теряется смысл наличия перечислений.

Maurice Naftalin 15.12.2013 04:17

Я не понимаю, что вы подразумеваете под «быть экземплярами Enum <E>». Я хотел бы, чтобы Season заменяли extend Enum<Season> и Weekday заменяли extend Enum<Weekday. Но я говорю, что здесь нет причин, по которым класс Enum не следует объявлять просто как abstract class Enum<E>. Он по-прежнему будет на 100% таким же типобезопасным, как и раньше.

newacct 16.12.2013 13:16

@newacct Разве вы не хотите настаивать на том, что Season реализует Comparable<Season>?

Maurice Naftalin 17.12.2013 20:23

Что ж, если Season создается с помощью объявления enum, компилятор уже заставляет его расширять Enum<Season>. Нет смысла «настаивать». Если вы определите свой собственный класс, расширяющий Enum, то уже возможно сделать Foo расширяющим Enum<Season>.

newacct 18.12.2013 00:57

@newacct Посмотрите определение Enum. Чтобы сравнить один экземпляр с другим, он должен сравнить их порядковые номера. Таким образом, аргумент метода compareTo должен быть объявлен как подтип Enum, иначе компилятор (правильно) скажет, что у него нет порядкового номера.

Maurice Naftalin 19.12.2013 16:35

@MauriceNaftalin, ваш последний комментарий проясняет это для меня. Предлагаю добавить к ответу.

Roland 07.04.2014 20:20

@MauriceNaftalin: Enum.compareTo() объявлен как принимающий параметр типа E. Это стандартная библиотечная функция, и ее реализация не является заботой пользователя. Язык Java специально запрещает пользователям создавать подклассы Enum вручную, и все сгенерированные компилятором перечисления в любом случае имеют параметр типа, такой же, как и сам тип, поэтому реализация стандартной библиотеки может полагаться на это для выполнения небезопасного преобразования.

newacct 25.02.2015 09:09

@MauriceNaftalin: Если бы Java не запрещала создание подклассов Enum вручную, тогда можно было бы иметь class OneEnum extends Enum<AnotherEnum>{}, даже с учетом того, как Enum объявлен прямо сейчас. Было бы бессмысленно сравнивать один тип перечисления с другим, поэтому EnumcompareTo в любом случае не имел бы смысла в том виде, в каком он был объявлен. Границы этому не помогают.

newacct 25.02.2015 09:12

@MauriceNaftalin: Если бы порядковый номер был причиной, то public class Enum<E extends Enum<?>> также было бы достаточно.

newacct 25.02.2015 09:24

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

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

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.

newacct 24.11.2011 02:43

Конечно, и это неправильно? Я так не думаю. Базовый контракт, предоставляемый Node <City>, по-прежнему соблюдается, только ваш подкласс Foo менее полезен, поскольку вы начинаете работать с Foos, но получаете Cities из ADT. Для этого может быть случай использования, но, скорее всего, проще и полезнее просто сделать общий параметр таким же, как подкласс. Но в любом случае у дизайнера есть выбор.

mdma 24.04.2012 15:30

@mdma: согласен. Так что же тогда связанного с class Node<T>?

newacct 30.11.2013 12:09

@nozebacle: Ваш пример не демонстрирует, что «эта конкретная структура необходима». class Node<T> полностью соответствует вашему примеру.

newacct 30.11.2013 12:10

Это можно проиллюстрировать простым примером и техникой, которая может использоваться для реализации связанных вызовов методов для подклассов. В приведенном ниже примере 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/…

Roland 28.11.2013 21:36

@Roland спасибо за ссылку, я позаимствовал из нее идею. Не могли бы вы объяснить мне или сослаться на статью, объясняющую, почему это плохая практика в данном конкретном случае? Метод в ссылке требует большего набора текста, и это главный аргумент, почему я этого избегаю. Я никогда не видел ошибок приведения в этом случае + я знаю, что есть некоторые неизбежные ошибки приведения - т.е. когда один хранит объекты нескольких типов в одной коллекции. Так что, если непроверенные приведения не являются критичными, а дизайн несколько сложен (Node<T> - нет), я игнорирую их, чтобы сэкономить время.

Andrey Chaschev 29.11.2013 02:46

Ваше изменение ничем не отличается от предыдущего, кроме добавления некоторого синтаксического сахара, учтите, что следующий код действительно будет компилироваться, но выдаст ошибку времени выполнения: `Node <City> node = new Node <City> () .setName (" node "). setSquare (1); `Если вы посмотрите на байт-код java, вы увидите, что из-за стирания типа оператор return (SELF) this; компилируется в return this;, так что вы можете просто не указывать его.

Roland 29.11.2013 18:39

@Roland спасибо, это то, что мне нужно - обновлю пример, когда буду свободен.

Andrey Chaschev 29.11.2013 19:00

Следующая ссылка тоже хороша: angelikalanger.com/GenericsFAQ/FAQSections/…

Roland 29.11.2013 19:57

Это не правильно. (SELF) this небезопасен. SELF является подтипом Node<SELF>, но Node<SELF> (тип this) не является подтипом SELF.

newacct 30.11.2013 12:02

Все бы работало с abstract class Node<SELF>. extends Node<SELF> особо ничего не добавляет.

newacct 01.12.2013 08:59

@newacct Node<SELF> позволит подклассу возвращать что угодно - и это не предназначено, потому что цель возврата SELF - связать вызовы методов. Вдобавок ваш аргумент можно применить к объявлению Enum, не так ли? Возможно, вы захотите взглянуть на реальный пример: grepcode.com/file/repo1.maven.org/maven2/org.jdbi/jdbi/2.49/ org /…. Кстати, он использует непроверенные приведения, поэтому, хотя моя первоначальная версия не является канонической, она может быть практичной.

Andrey Chaschev 01.12.2013 12:47

@AndreyChaschev: «Node<SELF> позволит подклассу возвращать что угодно, потому что цель возврата SELF - связать вызовы методов». Так? Если кто-то хочет создать класс, возвращающий что-то еще, в чем проблема? Вы все еще можете использовать классы, которые возвращают сами себя и просто связывают их в цепочку. Оба случая полностью безопасны по типу, а Generics - для безопасности типов. Кроме того, даже с вашим объявлением можно создать класс, расширяющий Node, с аргументом другого типа. Например, class Foo extends Node<City>.

newacct 02.12.2013 03:28

@AndreyChaschev: "Кроме того, ваш аргумент может быть применен к объявлению Enum, не так ли?" Я не утверждал, что это не может быть применено к декларации Enum. "посмотрите на пример из реального мира:" Итак? Какой-то случайный человек использует то, в чем нет необходимости. Что это доказывает?

newacct 02.12.2013 03:29

@newacct Вы бы лучше заявили это для Enum, чтобы было понятнее. Это намерение разработчиков языка. Это добавляет ограничение к системе типов, и вы говорите, что все было бы нормально без такого ограничения. Вы правы, и в некоторых языках даже нет Generics. Вы также можете сказать, что можно регулярно использовать краткую форму размышлений, но это просто не способ Java.

Andrey Chaschev 02.12.2013 10:48

@Andrey Хорошо, вы заново изобрели колесо, в данном случае метод взять это. Почему бы не назвать его соответствующим образом, если вы просто возвращаете указатель this?

Roland 02.12.2013 11:44

@Roland Я не являюсь автором статьи, в моей первоначальной версии ответа отсутствовал getThis. Формально это небезопасно по типу, потому что производит неконтролируемое приведение. В своих проектах я не использую это, потому что это многословно, и я до сих пор не видел ошибки приведения в этом случае.

Andrey Chaschev 02.12.2013 12:01

@AndreyChaschev: Без Generics вам потребовалось бы приведение типов, потому что нет безопасности типов. Это не добавляет безопасности типов и, по сути, даже не делает того, что вы утверждаете.

newacct 02.12.2013 14:09

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

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

Ничто из того, что вы показали в compareTo или getDeclaringClass, не требует привязки к extends Enum<E>.

newacct 31.05.2016 10:03

Согласно Википедии, этот шаблон называется Любопытно повторяющийся шаблон шаблона. По сути, используя шаблон CRTP, мы можем легко ссылаться на тип подкласса без приведения типа, что означает, что с помощью шаблона мы можем имитировать виртуальную функцию.

Другие вопросы по теме