Подстановочный знак (Java)
В языке программирования Java знак подстановочный ?
это особый вид аргумента типа [1] который контролирует безопасность типов при использовании универсальных (параметризованных) типов. [2] Его можно использовать в объявлениях переменных и созданиях экземпляров, а также в определениях методов, но не в определении универсального типа. [3] [4] Это форма от места использования аннотации отклонения , в отличие от аннотаций отклонения от места определения, встречающихся в C# и Scala .
Ковариация для универсальных типов
[ редактировать ]В отличие от массивов (которые являются ковариантными в Java [2] ), разные экземпляры универсального типа несовместимы друг с другом даже явно. [2] Например, декларации Generic<Supertype> superGeneric; Generic<Subtype> subGeneric;
заставит компилятор сообщать об ошибках преобразования для обоих типов приведения (Generic<Subtype>)superGeneric
и (Generic<Supertype>)subGeneric
.
Эту несовместимость можно смягчить с помощью подстановочного знака, если ?
используется как фактический параметр типа. [2] Generic<?>
является супертипом всех параметризаций универсального типа Generic
. Это позволяет объектам типа Generic<Supertype>
и Generic<Subtype>
быть безопасно присвоено переменной или параметру метода типа Generic<?>
. [2] С использованием Generic<? extends Supertype>
позволяет то же самое, ограничивая совместимость Supertype
и его дети. [5] Другая возможность заключается в Generic<? super Subtype>
, который также принимает оба объекта и ограничивает совместимость Subtype
и все его родители. [5]
Подстановочный знак как тип параметра
[ редактировать ]В теле универсального модуля параметр (формального) типа обрабатывается как его верхняя граница (выраженная с помощью extends
; Object
если нет ограничений). [5] Если возвращаемый тип метода является параметром типа, результат (например, типа ?
) может ссылаться на переменную типа верхней границы (или Object
). В другом направлении подстановочный знак не подходит ни для одного другого типа, даже для Object
: Если ?
был применен как формальный параметр типа метода, ему нельзя передать никакие фактические параметры. Однако объекты неизвестного типа могут быть прочитаны из универсального объекта и присвоены переменной супертипа верхней границы.
Пример кода для Generic<T extends UpperBound>
сорт:
class Generic <T extends UpperBound> { private T t; void write(T t) { this.t = t; } T read() { return t; }}
Пример кода, использующий Generic<T extends UpperBound>
сорт:
...final Generic<UpperBound> concreteTypeReference = new Generic<UpperBound>();final Generic<?> wildcardReference = concreteTypeReference;final UpperBound ub = wildcardReference.read(); // Object would also be OKwildcardReference.write(new Object()); // type errorwildcardReference.write(new UpperBound()); // type errorconcreteTypeReference.write(new UpperBound()); // OK...
Ограниченные подстановочные знаки
[ редактировать ]Ограниченный подстановочный знак — это знак с верхним или нижним ограничением наследования . Границей подстановочного знака может быть тип класса, тип интерфейса , тип массива или переменная типа. Верхние границы выражаются с помощью ключевого слова « extensions» , а нижние границы — с помощью ключевого слова «super» . Подстановочные знаки могут указывать либо верхнюю , либо нижнюю границу, но не обе.
Верхние границы
[ редактировать ]Верхняя граница подстановочного знака должна быть подтипом верхней границы соответствующего параметра типа, объявленного в соответствующем универсальном типе. [5] Пример подстановочного знака, который явно указывает верхнюю границу:
Generic<? extends SubtypeOfUpperBound> referenceConstrainedFromAbove;
Эта ссылка может содержать любую параметризацию Generic
аргумент типа которого является подтипом SubtypeOfUpperBound
. Подстановочный знак, в котором явно не указана верхняя граница, фактически аналогичен знаку, имеющему ограничение extends Object
, поскольку все ссылочные типы в Java являются подтипами Object.
Нижние границы
[ редактировать ]Подстановочный знак с нижней границей, например
Generic<? super SubtypeOfUpperBound> referenceConstrainedFromBelow;
может содержать любую параметризацию Generic
любой аргумент типа которого является одновременно подтипом верхней границы соответствующего параметра типа и супертипом SubtypeOfUpperBound
. [5]
Создание объекта с подстановочным знаком
[ редактировать ]Никакие объекты не могут быть созданы с аргументом типа подстановочного знака: например, new Generic<?>()
запрещено. На практике в этом нет необходимости, потому что если бы кто-то захотел создать объект, который можно было бы присвоить переменной типа Generic<?>
, можно просто использовать любой произвольный тип (который подпадает под ограничения подстановочного знака, если таковой имеется) в качестве аргумента типа.
Однако, new ArrayList<Generic<?>>()
разрешено, поскольку подстановочный знак не является параметром экземпляра типа ArrayList
. То же самое справедливо и для new ArrayList<List<?>>()
.
В выражении создания массива тип компонента массива должен быть доступным для повторного использования, как определено Спецификацией языка Java, раздел 4.7. Это означает, что если тип компонента массива имеет какие-либо аргументы типа, все они должны быть неограниченными подстановочными знаками (подстановочные знаки, состоящие только из ?
) . Например, new Generic<?>[20]
правильно, в то время как new Generic<SomeType>[20]
нет.
В обоих случаях другим вариантом является отсутствие параметров. Это вызовет предупреждение, поскольку оно менее безопасно с точки зрения типов (см. Необработанный тип ).
Пример: списки
[ редактировать ]В Java Collections Framework класс List<MyClass>
представляет собой упорядоченную коллекцию объектов типа MyClass
.Верхние границы задаются с помощью extends
:А List<? extends MyClass>
представляет собой список объектов некоторого подкласса MyClass
, т. е. любой объект в списке гарантированно принадлежит к типу MyClass
, поэтому его можно перебирать, используя переменную типа MyClass
[6]
public void doSomething(List<? extends MyClass> list) { for (final MyClass object : list) { // OK // do something }}
Однако не гарантируется, что можно добавить любой объект типа MyClass
в этот список:
public void doSomething(List<? extends MyClass> list) { final MyClass m = new MyClass(); list.add(m); // Compile error}
Обратное верно для нижних границ, которые задаются с помощью super
:А List<? super MyClass>
представляет собой список объектов некоторого суперкласса MyClass
, т.е. список гарантированно сможет содержать любой объект типа MyClass
, поэтому можно добавить любой объект типа MyClass
:
public void doSomething(List<? super MyClass> list) { final MyClass m = new MyClass(); list.add(m); // OK}
Однако не гарантируется, что можно будет перебирать этот список, используя переменную типа MyClass
:
public void doSomething(List<? super MyClass> list) { for (final MyClass object : list) { // Compile error // do something }}
Чтобы иметь возможность делать и то, и другое, добавьте объекты типа MyClass
в список и перебрать его, используя переменную типа MyClass
, а List<MyClass>
необходим, и это единственный тип List
это и то и другое List<? extends MyClass>
и List<? super MyClass>
. [7]
Мнемоника PECS (Producer Extends, Consumer Super) из книги Эффективная Java» « Джошуа Блоха дает простой способ запомнить, когда использовать подстановочные знаки (соответствующие ковариации и контравариантности) в Java. [5]
См. также
[ редактировать ]- Ограниченная количественная оценка
- Ковариантность и контравариантность (информатика)
- В разделе «Общие символы Java#Type» поясняются нижние и верхние границы подстановочных знаков.
Цитаты
[ редактировать ]- ^ «Глава 4. Типы, значения и переменные» . docs.oracle.com . Проверено 3 ноября 2020 г.
- ^ Перейти обратно: а б с д и Bloch 2018 , стр. 117–122, глава §5, пункт 26. Не используйте необработанные типы.
- ^ Гилад Брача (июнь 2004 г.), «4. Подстановочные знаки», Generics in the Java Programming Language (PDF) , получено 6 марта 2016 г.
- ^ «8.1.2 Универсальные классы и параметры типа», Спецификация языка Java , Oracle , получено 6 марта 2016 г.
- ^ Перейти обратно: а б с д и ж Bloch 2018 , стр. 139–145, глава §5, пункт 31. Используйте ограниченные подстановочные знаки для повышения гибкости API.
- ^ Наследование (объектно-ориентированное программирование)
- ^ Синтаксис Java (обобщенные)
Ссылки
[ редактировать ]- Блох, Джошуа (2018). «Эффективная Java: Руководство по языку программирования» (третье изд.). Аддисон-Уэсли . ISBN 978-0134685991 .
- Спецификация языка Java, третье издание (Sun), ISBN 978-0-321-24678-3 http://java.sun.com/docs/books/jls/
- Учебники по Java, шаблоны уроков http://download.oracle.com/javase/tutorial/java/generics/index.html
- Захват подстановочных знаков, http://bayou.io/draft/Capturing_Wildcards.html.
- Совместимость типов в Java http://public.beuth-hochschule.de/~solymosi/veroeff/typkompatibilitaet/Typkompatibilitaet.html#Joker (на немецком языке)