Почему в java enum объявлен как Enum < E расширяет Enum < E > > [Дубликат]

37
задан Community 23 May 2017 в 12:09
поделиться

2 ответа

Это общий вопрос, и это понятно. Взгляните на эту часть часто задаваемых вопросов по обобщениям , чтобы получить ответ (и на самом деле, прочтите как можно больше всего документа, он довольно хорошо сделан и информативен).

Если коротко, то класс будет параметризован сам по себе; это требуется суперклассам для определения методов с использованием универсального параметра, которые работают прозрачно («изначально», если хотите) со своими подклассами.

Изменить: в качестве (не) примера, например, рассмотрим метод clone () для объекта . В настоящее время определено, что он возвращает значение типа Object . Благодаря ковариантным типам возврата определенные подклассы могут (и часто действительно) определять, что они возвращают более конкретный класс, но это не может быть принудительно и, следовательно, не может быть выведено для произвольного класса.

Теперь , если Объект был определен как Enum, т.е. Object > , тогда вам нужно будет определить все классы как что-то вроде открытого класса MyFoo . Следовательно, clone () может быть объявлен для возврата типа T , и вы можете гарантировать во время компиляции, что возвращаемое значение всегда будет точно того же класса, что и сам объект (даже не подклассы будут соответствовать параметрам).

В этом случае Object не параметризован таким образом, потому что было бы очень неприятно иметь этот багаж на всех классах, когда 99% из них вообще не собираются его использовать. Но для некоторых иерархий классов это может быть очень полезно - я сам раньше использовал подобную технику с типами абстрактных рекурсивных синтаксических анализаторов выражений с несколькими реализациями. Эта конструкция позволяла писать код, который был «очевидным», без необходимости выполнять приведение всех типов или копировать и вставлять только для изменения определений конкретных классов.

Редактировать 2 (Чтобы ответить на ваш вопрос!):

Если Enum был определен как Enum , то, как вы правильно сказали, кто-то мог бы определить класс как A расширяет Enum . Это лишает смысла основную конструкцию, которая должна гарантировать, что общий параметр всегда будет точным типом рассматриваемого класса. Приводя конкретный пример, Enum объявляет свой метод compareTo как

public final int compareTo(E o)

В этом случае, поскольку вы определили A для расширения Enum , экземпляры A можно сравнить только с экземплярами B (независимо от того, какой B), что почти наверняка не очень полезно. С дополнительной конструкцией вы знаете, что любой класс, расширяющий Enum, сопоставим только с самим собой. И, следовательно, вы можете предоставить реализации методов в суперклассе, которые останутся полезными и конкретными во всех подклассах.

(Без этого трюка с рекурсивными обобщениями единственным другим вариантом было бы определение compareTo как public final int compareTo (Enum o) . Это не совсем то же самое, поскольку тогда можно было бы сравнить java.math.RoundingMode против java.lang.Thread.State без жалоб компилятора, что опять же не очень полезно.)


Хорошо, давайте уйдем от ] Enum , поскольку мы, кажется, зацикливаемся на нем. Вместо этого вот абстрактный класс:

public abstract class Manipulator<T extends Manipulator<T>>
{
    /**
     * This method actually does the work, whatever that is
     */
    public abstract void manipulate(DomainObject o);

    /**
     * This creates a child that can be used for divide and conquer-y stuff
     */
    public T createChild()
    {
        // Some really useful implementation here based on
        // state contained in this class
    }
}

У нас будет несколько конкретных реализаций этого - SaveToDatabaseManipulator, SpellCheckingManipulator, что угодно. Кроме того, мы также хотим позволить людям определять свои собственные, поскольку это очень полезный класс. ; -)

Теперь вы заметите, что мы используем рекурсивное универсальное определение, а затем возвращаем T из метода createChild . Это означает, что:

1) Мы знаем и компилятор знает , что если я позвоню:

SpellCheckingManipulator obj = ...; // We have a reference somehow
return obj.createChild();

, то возвращаемое значение определенно будет SpellCheckingManipulator , даже если он использует определение из суперкласса. Рекурсивные обобщения здесь позволяют компилятору знать, что для нас очевидно, поэтому вам не нужно постоянно приводить возвращаемые значения (как, например, вам часто приходится делать с clone () ).

2) Обратите внимание, что я не объявлял метод final, поскольку, возможно, некоторые конкретные подклассы захотят заменить его более подходящей версией для себя.Определение дженериков означает, что независимо от того, кто создает новый класс или как он определен, мы все равно можем утверждать, что возврат, например, BrandNewSloppilyCodedManipulator.createChild () по-прежнему будет экземпляром BrandNewSloppilyCodedManipulator . Если неосторожный разработчик попытается определить, что он возвращает только Манипулятор , компилятор им не позволит. И если они попытаются определить класс как BrandNewSloppilyCodedManipulator , он им тоже не позволит.

По сути, можно сделать вывод, что этот трюк полезен, когда вы хотите предоставить некоторую функциональность в суперклассе, которая каким-то образом становится более специфичной в подклассах. Объявляя суперкласс таким образом, вы блокируете общий параметр для любых подклассов, чтобы они были самим подклассом. Вот почему вы можете написать общий метод compareTo или createChild в суперклассе, чтобы он не стал излишне расплывчатым, когда вы имеете дело с конкретными подклассами.

38
ответ дан 27 November 2019 в 04:59
поделиться

Единственная разница теперь будет в том, что кто-то может написать

 A extends Enum 

, но поскольку в java не разрешено расширять перечисления, это все равно незаконно. Я также думал о том, чтобы кто-то предоставил jvm байт-код, который определяет что-то как расширение перечисления - но дженерики не могут повлиять на это, поскольку все они стираются.

Вы правы, язык может помешать кому-то это сделать. Однако преимущество его E extends Enum , а не просто extends Enum , заключается в том, что некоторые из методов в Enum теперь будут возвращать правильные, конкретный тип.

Плакат просил кое-что, что не сработало. Если Enum был определен как Enum , то вы могли бы сделать это:

// Would be legal, because element types of `DaysOfWeek` and `Color` both
//   extend `Enum<E>`, not `Enum<E extends Enum<E>>`.
DaysOfWeek d = Enum.valueOf(Color.class, "Purple");
1
ответ дан 27 November 2019 в 04:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: