Мне показалось, что я достаточно хорошо понимаю дженерики Java, но потом я наткнулся на следующее в java.lang.Enum:
class Enum<E extends Enum<E>>
Может кто-нибудь объяснить, как интерпретировать этот параметр типа? Бонусные баллы за предоставление других примеров использования аналогичного параметра типа.
Ответы:
Это означает, что аргумент типа для enum должен происходить от enum, который сам имеет такой же аргумент типа. Как это может случиться? Делая аргумент типа новым самим типом. Так что если бы у меня был enum с именем StatusCode, это было бы эквивалентно:
Теперь, если вы проверите ограничения, у нас есть
Enum<StatusCode>
- такE=StatusCode
. Давайте проверим:E
расширяетсяEnum<StatusCode>
ли? Да! Мы в порядке.Вы можете спросить себя, в чем смысл этого :) Ну, это означает, что API для Enum может ссылаться на себя - например, быть в состоянии сказать, что
Enum<E>
реализуетComparable<E>
. Базовый класс может выполнять сравнения (в случае перечислений), но он может убедиться, что он сравнивает только правильные типы перечислений друг с другом. (РЕДАКТИРОВАТЬ: Ну, почти - см. Редактирование внизу.)Я использовал нечто подобное в моем C # порту ProtocolBuffers. Существуют «сообщения» (неизменяемые) и «строители» (изменяемые, используемые для создания сообщения) - и они представляют собой пары типов. Используемые интерфейсы:
Это означает, что из сообщения вы можете получить соответствующего компоновщика (например, взять копию сообщения и изменить некоторые биты), а из компоновщика вы можете получить соответствующее сообщение, когда вы закончите его сборку. Это хорошая работа, и пользователям API не нужно на самом деле заботиться об этом - это ужасно сложно и потребовалось несколько итераций, чтобы добраться до места, где он находится.
РЕДАКТИРОВАТЬ: Обратите внимание, что это не мешает вам создавать нечетные типы, которые используют аргумент типа, который сам по себе, но не тот же тип. Цель состоит в том, чтобы дать преимущества в правильном случае, а не защитить вас от неправильного случая.
Таким образом, если бы
Enum
в любом случае не обрабатывались «специально» в Java, вы могли бы (как отмечено в комментариях) создавать следующие типы:Second
будет реализовывать,Comparable<First>
а неComparable<Second>
... ноFirst
само по себе было бы хорошо.источник
Enum
не имеет никаких методов экземпляра, которые возвращают тип параметра типа.class Enum<E>
достаточно во всех случаях. А в Generics вы должны использовать более ограничительную границу, только если это действительно необходимо для обеспечения безопасности типов.Enum
подклассы не всегда генерироваться автоматически, единственная причина , вам нужно будетclass Enum<E extends Enum<?>>
черезclass Enum<E>
это возможность получить доступordinal
кcompareTo()
. Однако, если вы подумаете об этом, с точки зрения языка не имеет смысла позволять вам сравнивать два различных типа перечислений через их ординалы. Следовательно, реализацияEnum.compareTo()
этого используетordinal
смысл только в контекстеEnum
автоматически генерируемых подклассов. Если бы вы могли вручную создать подклассEnum
,compareTo
вероятно, должно бытьabstract
.Ниже приведена модифицированная версия объяснения из книги Java Generics and Collections : у нас есть
Enum
объявленныйкоторый будет расширен до класса
где
...
должен быть каким-то параметризованным базовым классом для Enums. Давайте решим, что это должно быть. Ну, одним из требованийSeason
является то, что он должен реализоватьComparable<Season>
. Итак, нам понадобитсяЧто вы могли бы использовать
...
, чтобы это сработало? Учитывая, что это должна быть параметризацияEnum
, единственный выборEnum<Season>
, так что вы можете иметь:Так
Enum
что параметризовано по типам вродеSeason
. Абстрагироваться отSeason
и вы получите, что параметрEnum
любого типа, который удовлетворяетМорис Нафталин (соавтор Java Generics and Collections)
источник
Season
реализуетComparable<Season>
?compareTo
метода должен быть объявлен какEnum
подтип, иначе компилятор (правильно) скажет, что у него нет порядкового номера.Enum
, то было бы возможноclass OneEnum extends Enum<AnotherEnum>{}
, даже с тем, какEnum
объявлено прямо сейчас. Это не имеет особого смысла , чтобы иметь возможность сравнить один тип перечисления с другой, так , тоEnum
«ScompareTo
не будет иметь смысла , как заявлено в любом случае. Границы не оказывают никакой помощи в этом.public class Enum<E extends Enum<?>>
было бы достаточно.Это можно проиллюстрировать на простом примере и методике, которая может быть использована для реализации связанных вызовов методов для подклассов. В приведенном ниже примере
setName
возвращаетNode
цепочку не будет работать дляCity
:Таким образом, мы могли бы ссылаться на подкласс в общем объявлении, так что
City
теперь возвращает правильный тип:источник
return (CHILD) this;
попробуйте добавить метод getThis ():protected CHILD getThis() { return this; }
См. Angelikalanger.com/GenericsFAQ/FAQSections/…Node<T>
это не так), я игнорирую их, чтобы сэкономить время.return (SELF) this;
компилируетсяreturn this;
, поэтому вы можете просто его пропустить.Вы не единственный, кто интересуется, что это значит; см. хаотический Java-блог .
«Если класс расширяет этот класс, он должен передать параметр E. Границы параметра E предназначены для класса, который расширяет этот класс тем же параметром E».
источник
Этот пост полностью прояснил мне проблему «рекурсивных родовых типов». Я просто хотел добавить еще один случай, когда эта конкретная структура необходима.
Предположим, у вас есть общие узлы в общем графике:
Тогда вы можете иметь графики специализированных типов:
источник
class Foo extends Node<City>
Foo, не связанный с City.class Node<T>
?class Node<T>
полностью соответствует вашему примеру.Если вы посмотрите на
Enum
исходный код, он имеет следующее:Первым делом первым, что это
E extends Enum<E>
значит? Это означает, что параметр типа является чем-то, что происходит от Enum, и не параметризован с необработанным типом (он параметризован сам по себе).Это актуально, если у вас есть enum
который, если я правильно знаю, переводится на
Так что это означает, что MyEnum получает следующие методы:
И что еще более важно,
Это делает
getDeclaringClass()
приведение к нужномуClass<T>
объекту.Более ясный пример - тот, на который я ответил на этот вопрос, где вы не можете избежать этой конструкции, если хотите указать общую границу.
источник
compareTo
или неgetDeclaringClass
требуетextends Enum<E>
привязки.Согласно википедии, этот шаблон называется Curiously recurring template pattern . По сути, используя шаблон CRTP, мы можем легко ссылаться на тип подкласса без приведения типов, что означает, что с помощью шаблона мы можем имитировать виртуальную функцию.
источник