Подтвердить что ты не робот

Понимание верхней и нижней границ? в 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 является, таким образом, нижней границей. Самый низкий класс в иерархии наследования

4b9b3361

Ответ 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 = ...