Java Generics: недопонимание подстановочных знаков

Читая онлайн-учебник по Java, я ничего не понимаю о захвате с подстановочными знаками Например:

    import java.util.List;
    public class WildcardError {
     void foo(List<?> i) {
      i.set(0, i.get(0));
     }
    }

почему компилятор не может сохранить присваивание в безопасности? Он знает, что, выполняя, например, метод со списком целых чисел, он получает из i.get значение целого числа. Поэтому попытайтесь установить значение Integer с индексом 0 в тот же список Integer (i). Итак, что же не так? Зачем писать помощник Wildcard?

Ответ 1

почему компилятор не может сохранить присвоение безопасным?

Компилятор ничего не знает о типе элементов в List<?> i, по определению ?. Подстановочный знак не означает "любой тип"; это означает "какой-то неизвестный тип".

Он знает, что, выполняя, например, метод с целым списком, он получает от i.get значение Integer.

Это правда, но, как я сказал выше: компилятор может знать только - время компиляции, помните - что i.get(0) возвращает Object, что является верхней границей ?, Но нет гарантии, что ? находится во время выполнения Object, поэтому компилятор не знает, что i.set(0, i.get(0)) является безопасным вызовом. Это как написано:

List<Foo> fooz = /* init */;
Object foo = fooz.get(0);
fooz.set(0, foo); // won't compile because foo is an object, not a Foo

Дополнительная информация:

Ответ 2

почему компилятор не может сохранить присвоение безопасным? Он знает, что, выполняя, например, метод с целым списком, он получает от i.get значение Integer. Поэтому он пытается установить значение Integer в индексе 0 в тот же список Integer (i).

По-другому, почему компилятор не знает, что два использования подстановочного типа List<?> в

i.set(0, i.get(0));

относятся к одному и тому же фактическому типу?

Ну, это потребует от компилятора знать, что i содержит один и тот же экземпляр для обеих оценок выражения. Поскольку i даже не является окончательным, компилятор должен был бы проверить, возможно ли было бы i между оценкой двух выражений. Такой анализ прост для локальных переменных (кто знает, будет ли вызываемый метод обновлять конкретное поле определенного объекта?). Это довольно сложная задача в компиляторе, редко проявляющая преимущества. Я полагаю, что разработчики языка программирования Java все упрощали, указав, что разные виды использования одного и того же типа подстановочных знаков имеют разные захваты.

Ответ 3

Я также затрудняюсь понять этот вопрос; разделение одной команды на две команды помогло мне.

Ниже код - это то, что на самом деле происходит в фоновом режиме, когда исходный метод проверяется и компилируется, компилятор создает свою локальную переменную: результат вызова i.get(0) помещается в регистр в стек локальной переменной.

И это - для понимания этой проблемы - так же, как создание локальной переменной, которая для удобства я дал имя element.

import java.util.List;
public class WildcardError {
 void foo(List<?> i) {
  Object element = i.get(0);  // command 1
  i.set(0, element);          // command 2
 }
}

Когда команда 1 проверяется, она может устанавливать только тип element в Object (- > понятие верхнего уровня, см. ответ Matt), поскольку он не может использовать ? как тип переменной; ? используется только для указания того, что общий тип неизвестен.

Переменные типы могут быть только реальными типами или универсальными типами, но поскольку вы не используете общий тип в этом методе, например, <T>, это принудительный, чтобы использовать реальный тип. Это форсирование выполняется из-за следующих строк в спецификации java (jls8, 18.2.1):

Формула ограничения формы <Выражение → T > уменьшается следующим образом:

[...]

- Если выражение представляет собой выражение создания экземпляра класса или выражение вызова метода, ограничение сводится к связанному множеству B3, который будет использоваться для определения типа вызова выражения при таргетинге на T, как определено в §18.5.2. (Для выражения создания экземпляра класса соответствующий "метод", используемый для вывода, определен в п. 15.9.3).

Ответ 4

Решение будет,

import java.util.List;

    public class WildcardError {

    private void fooHelper(List<T> i){
         i.set(0, i.get(0));
    }
    public void foo(List<?> i){
         fooHelper(i);
    }
}

здесь fooHelper будет захватывать тип T подстановочного знака? (так как имя подстановочного знака).

Ответ 5

По принципу Get-Put:

  1. Если у вас есть подстановочный знак расширения, как в List<? extends Something>, то:

    1А. Вы можете получить из структуры, используя Something или его ссылку superclass.

    void foo(List<? extends Number> nums) {
       Number number = nums.get(0); 
       Object number = nums.get(0); // superclass reference also works.
    }
    

    1B. Вы не можете ничего добавить к структуре (кроме null).

    void foo(List<? extends Number> nums) {
       nums.add(1); Compile error
       nums.add(1L); Compile error
       nums.add(null); // only null is allowed.
    }
    
  2. Точно так же, если у вас есть супер подстановочный знак, как в List<? super Something>, то:

    2А. Вы можете добавить к структуре, которая является Something или его subclass. Например:

    void foo(List<? super Number> nums) {
        nums.add(1); // Integer is a subclass of Number
        nums.add(1L); // Long is a subclass of Number
        nums.add("str"); // Compile error: String is not subclass of Number         
    }
    

    2A. Вы не можете получить из структуры (кроме как через ссылку на объект). Например:

    void foo(List<? super Integer> nums) {
        Integer num = nums.get(0); // Compile error
        Number num = nums.get(0); // Compile error
        Object num = nums.get(0); // Only get via Object reference is allowed.        
    }
    

Возвращаясь к вопросу OP, List<?> i является лишь кратким представлением для List<? extends Object> i. А поскольку это подстановочный знак extends, операция set завершается неудачей.

Последний оставшийся фрагмент - ПОЧЕМУ операция не удалась? Или почему принцип Get-Put на первом месте? - Это связано с безопасностью типов, как ответил Джон Скит здесь.

Ответ 6

Я полагаю, что ваше неправильное понимание ограничения происходит от замены ? на any type, Object или чего-то подобного в вашем уме. Но это предположение неверно, ? фактически означает unknown type. Итак, в следующей строке

fooz.set(0, foo);

вы пытаетесь присвоить переменную некоторого типа переменной неизвестного типа (поскольку сигнатура функции похожа на void set(int, ?)), что никогда не может быть возможным, независимо от типа foo. В вашем случае тип foo - Object, и вы не можете присвоить его переменной неизвестного типа, которая на самом деле может быть Foo, Bar или любым другим.