Почему в java перечисление объявляется как Enum <E extends Enum <E>> - программирование
Подтвердить что ты не робот

Почему в java перечисление объявляется как Enum <E extends Enum <E>>

Возможный дубликат:
определение java Enum

Лучше сформулированный вопрос, который не считается дубликатом:
Что будет отличаться в Java, если декларация Enum не имеет рекурсивной части

если разработчики языка должны были просто использовать Enum < E extends Enum > как это повлияет на язык?

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

A extends Enum<B>

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

Итак, в чем смысл такой декларации?

Спасибо!

Изменить для простоты рассмотрим пример:

interface MyComparable<T> {
    int myCompare(T o);
}

class MyEnum<E extends MyEnum> implements MyComparable<E> {
    public int myCompare(E o) { return -1; }
}

class FirstEnum extends MyEnum<FirstEnum> {}

class SecondEnum extends MyEnum<SecondEnum> {}

что не так с этой структурой класса? Что можно сделать, чтобы "MyEnum < E extends MyEnum < E → " ограничивал?

4b9b3361

Ответ 1

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

Короткий ответ заключается в том, что он заставляет класс параметризуться сам по себе; это необходимо, чтобы суперклассы определяли методы, используя общий параметр, которые работают прозрачно ( "изначально", если хотите) с их подклассами.

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

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

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

Изменить 2 (Чтобы ответить на ваш вопрос!):

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

public final int compareTo(E o)

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

(Без этого рекурсивного трюка generics единственным вариантом будет определение 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, так как, возможно, некоторые подклассы захотят переопределить его с более подходящей версией для себя. Определение generics означает, что независимо от того, кто создает новый класс или как он определяется, мы можем все же утверждать, что возврат из, например, BrandNewSloppilyCodedManipulator.createChild() все равно будет экземпляром BrandNewSloppilyCodedManipulator. Если небрежный разработчик пытается определить его, чтобы вернуть только Manipulator, компилятор не позволит им. И если они попытаются определить класс как BrandNewSloppilyCodedManipulator<SpellCheckingManipulator>, это тоже не позволит им.

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

Ответ 2

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

A extends Enum<B>

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

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

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

// 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");