Понимание верхней и нижней границ? в Java Generics
Мне действительно сложно понять параметр wild card. У меня есть несколько вопросов по этому поводу.
-
?
как параметр типа может использоваться только в методах. например: printAll(MyList<? extends Serializable>)
Я не могу определить классы с ?
как параметр типа.
-
Я понимаю верхнюю границу на ?
. printAll(MyList<? extends Serializable>)
означает: "printAll
будет печатать MyList
, если у него есть объекты, реализующие интерфейс Serialzable
".
У меня есть проблема с super
. printAll(MyList<? super MyClass>)
означает: "printAll
будет печатать MyList
, если он имеет объекты MyClass
или любой класс, который расширяет MyClass
(потомки MyClass
)".
Исправьте меня, где я ошибся.
Короче говоря, в качестве параметров типа для определения общих классов могут использоваться только T
или E
или K
или V
или N
. ?
может использоваться только в методах
Обновление 1:
public void printAll(MyList<? super MyClass>){
// code code code
}
В соответствии с книгой Ivor Horton MyList<? super MyClass>
означает, что я могу напечатать MyList
, если у него есть объекты MyClass
или любые интерфейсы или классы, которые он реализует. То есть MyClass
является нижней границей. Это последний класс в иерархии наследования. Это означает, что мое первоначальное предположение было неправильным.
Итак, скажем, если MyClass
выглядит следующим образом:
public class MyClass extends Thread implements ActionListener{
// whatever
}
тогда printAll()
будет печатать, если
1. В списке есть объекты MyClass
2. Есть объекты Thread
или ActionListener
в List
Обновление 2:
Итак, прочитав много ответов на вопрос, вот мое понимание:
-
? extends T
означает любой класс, который расширяет T
. Таким образом, мы имеем в виду детей T
. Следовательно, T
- верхняя граница. Высший класс в иерархии наследования
-
? super T
означает любой класс/интерфейс, который super
от T
. Таким образом, мы имеем в виду всех родителей T
. T
является, таким образом, нижней границей. Самый низкий класс в иерархии наследования
Ответы
Ответ 1
?
как параметр типа может использоваться только в методах. например: printAll(MyList<? extends Serializable>)
Я не могу определить классы с ?
как параметр типа.
Подстановочный знак (?
) не является формальным параметром типа, а скорее может использоваться как аргумент типа. В приведенном примере ? extends Serializable
задается как аргумент типа для общего типа MyList
параметра printAll
.
Методы также могут объявлять параметры типа, например, классы:
static <T extends Serializable> void printAll(MyList<T> myList)
Я понимаю верхнюю границу на ?
. printAll(MyList<? extends Serializable>)
означает, что printAll будет печатать MyList, если у него есть объекты, реализующие интерфейс Serialzable
Точнее, это означает, что вызов printAll
будет компилироваться, только если ему передан MyList
с каким-то общим типом, который реализует или реализует Serializable
. В этом случае он принимает MyList<Serializable>
, MyList<Integer>
и т.д.
У меня есть проблема с super
. printAll(MyList<? super MyClass>)
означает, что printAll будет печатать MyList, если у него есть объекты MyClass или любого класса, который расширяет MyClass (потомки MyClass)
Подстановочный знак, ограниченный super
, является нижней границей. Таким образом, мы могли бы сказать, что вызов printAll
будет компилироваться, только если ему передан MyList
с каким-то общим типом, который является MyClass
или некоторым супертипом MyClass
. Поэтому в этом случае он принимает MyList<MyClass>
, например. MyList<MyParentClass>
или MyList<Object>
.
Итак, скажем, если MyClass выглядит так:
public class MyClass extends Thread implements ActionListener{
// whatever
}
тогда printAll() будет печатать, если
- В списке есть объекты MyClass
- В списке есть объекты Thread или ActionListener
Ты на правильном пути. Но я думаю, что, например, "он будет печатать, если в списке есть объекты MyClass
". Это звучит так, как будто вы определяете поведение во время выполнения. Генерики - это все, что нужно для проверки времени компиляции. Например, не удалось бы передать MyList<MySubclass>
в качестве аргумента для MyList<? super MyClass>
, хотя он мог бы содержать экземпляры MyClass
по наследованию. Я бы переписал его:
Вызов printAll(MyList<? super MyClass>)
будет компилироваться, только если он передан a:
-
MyList<MyClass>
-
MyList<Thread>
-
MyList<Runnable>
-
MyList<ActionListener>
-
MyList<EventListener>
-
MyList<Object>
-
MyList<? super X>
где X
- MyClass
, Thread
, Runnable
, ActionListener
, EventListener
или Object
.
Итак, прочитав много ответов на вопрос, вот мой понимание:
? extends T
означает любой класс, который расширяет T. Таким образом, мы имеем в виду дети T. Следовательно, T - верхняя граница. Высший класс в иерархии наследования
? super T
означает любой класс/интерфейс, который равен super
T. Таким образом, мы ссылаясь на всех родителей T. T, следовательно, является нижней границей. младший класс в иерархии наследования
Закрыть, но я бы не сказал "дети T
" или "родители T
", так как эти ограничения являются инклюзивными - было бы точнее сказать "T
или его подтипы", и "T
или его супертипы".
Ответ 2
Прежде всего T
или E
или K
или все, что не является фиксированными именами. Они всего лишь переменные типа, и вы определяете их имя. T
, E
, K
- это просто примеры, но вы можете назвать его Foo
или что-то еще.
Теперь перейдем к вашему первому вопросу: поскольку подстановочный знак ?
представляет собой "любой и неизвестный" тип, неопределенный, не имеет смысла объявлять общий класс по неопределенному типу. Полезно иметь подстановочный знак в параметрах методов или переменных, если вам не нужен тип.
Теперь о вашем втором вопросе: нижняя граница дает еще большую гибкость вашим общим методам. оба extends
и super
противоположны:
-
? extends T
: неизвестный тип, который является подтипом T
-
? super T
: неизвестный тип, который является супер-типом T
Последнее может быть полезно, когда вы хотите принять тип, совместимый с T (так что T - это тот тип). Практический пример можно найти здесь.
Ответ 3
Давайте начнем с начала.
Строго говоря, любой действительный java-идентификатор может использоваться как общий тип типа - это просто специальный тип переменной:
public static final class MyGenericClass<MyGenericType> {
}
Совершенно корректная Java.
Затем вы можете использовать ?
везде, где вы можете сделать объявление. Вы можете использовать подстановочный знак при объявлении переменных, но не при их создании:
public static final class MyGenericClass {
private final Collection<? extends String> myThings;
public MyGenericClass(Collection<? extends String> myThings) {
this.myThings = myThings;
}
public void doStuff(final Collection<? extends String> myThings) {
}
}
Повторяется все, вы не можете сделать это:
final Collection<? extends String> myThings = new ArrayList<? extends String>();
Когда дело доходит до extends
vs super
, это называется co-variance vs contra-variance. Он определяет, какое направление по классам, указанным в иерархии классов, разрешено путешествовать:
final Collection<? extends Runnable> example1 = new ArrayList<Runnable>();
final Collection<? extends Runnable> example2 = new ArrayList<TimerTask>();
final Collection<? super Runnable> example3 = new ArrayList<Runnable>();
final Collection<? super Runnable> example4 = new ArrayList<Object>();
Первые два примера демонстрируют extends
- самая жесткая граница, которую вы можете предположить из Collection
, равна Runnable
, поскольку пользователь может передать Collection
всего, что имеет Runnable
в своей иерархии наследования.
Во втором примере демонстрируется super
- наименьшая граница, которую вы можете предположить из Collection
, равна Object
, поскольку мы разрешаем все, что находится в иерархии наследования Runnable
.
Ответ 4
Для первого вопроса: вы не можете определить метод с ?
как параметр типа. Следующие команды не будут компилироваться:
void <?> foo() {}
?
используется для привязки к другим генерикам без предоставления параметра типа. Вы можете написать для методов:
void foo(List<?> e) {}
И вы также можете писать для классов:
public class Bar<E extends List<?>> { }
Для использования super
:
public void printAll(MyList<? super MyClass>){
// code code code
}
Это не так, как вы говорите, распечатайте список "если у него есть объекты MyClass". Он может иметь объекты любого класса, который является подклассом класса, который является родителем MyClass. Компилятор во время компиляции не знает, какие объекты будут в списке в любом случае.
Чтобы обдумать это, рассмотрите простой пример с иерархией классов Number
. Float
и Integer
являются дочерними элементами Number
. Вы можете написать свой метод следующим образом:
public void printAll(List<? super Float>){
// code code code
}
Затем вы можете вызвать этот метод с помощью List<Number>
:
List<Number> numbers = new ArrayList<>();
numbers.add(1); // actually only add an Integer
printAll(numbers); // compiles.
Это возможно, было бы не очень полезно в этом случае. Там, где это было бы полезно, например, когда вы хотите добавить Float
в коллекцию, не желая, чтобы это был только список, например:
public void addFloat(List<? super Float> list){
list.add(2.5);
}
Ответ 5
Хмммм, ваше утверждение на super
(printAll(MyList<? super MyClass>)
) не ясно. Что это означает, предполагая, что Myclass extends Object
состоит в том, что вы можете printAll(MyList<MyClass>)
, а вы можете printAll(MyList<Object>)
, но ничего больше... это означает, что общий тип MyList должен быть суперклассом (а не подклассом) MyClass, Это отличается от того, что вы сказали.
Что касается T, E, K, V или N, ну, это бессмысленные имена сами по себе. Вы можете использовать все, что хотите. Конвенция предлагает однобуквенные значения верхнего регистра, и T часто используется для общих методов, а E для классов....
Ответ 6
?
также может использоваться как возвращаемый тип метода
List<? extends Number> xxx() {
...
}
или как поле или тип переменной
List<? extends Number> xxx = ...
List<? super Nember> xxx = ...