Вопрос по wildcard, generics, java – Java Generics: недопонимание захвата подстановочных знаков

17

Читая онлайн-учебник по 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?

возможный дубликатCapturing wildcards in java generics thecoop
возможный дубликатCan't add value to the Java collection with wildcard generic type Matt Ball
Хм, это не противоречит тому, чтобы голосовать, чтобы закрыть вопросand дать ответ? meriton
Это не дубликат (по крайней мере, не тот вопрос). herman
Я уже видел вопросы об этом точном примере ранее, но, похоже, не могу найти их в поиске. Paul Bellora

Ваш Ответ

5   ответов
1

разделение одной команды на две мне помогло.

Ниже приведен код, который на самом деле происходит в фоновом режиме, когда исходный метод проверяется и компилируется, компилятор создает собственную локальную переменную: результат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 (-> концепция верхней границы, см. ответ Мэтта), так как она не может использовать? как тип переменной;? используется только для указания того, что универсальный тип неизвестен.

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

A constraint formula of the form ‹Expression → T› is reduced as follows:

[...]

– If the expression is a class instance creation expression or a method invocation expression, the constraint reduces to the bound set B3 which would be used to determine the expression's invocation type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the corresponding "method" used for inference is defined in §15.9.3).

1

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 подстановочного знака? (так как имя подстановочного знака).

0

If you have extends wildcard as in List<? extends Something>, then:

1A. You can get from the structure using Something or its superclass reference.

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

1B. You cannot add anything to the structure (except null).

void foo(List<? extends Number> nums) {
   nums.add(1); Compile error
   nums.add(1L); Compile error
   nums.add(null); // only null is allowed.
}

Similarly, if you have super wildcard as in List<? super Something>, then:

2A. You can add to the structure which is Something or its subclass. For eg:

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. You cannot get from the structure (except via Object reference). For eg:

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 на первом месте? - Это связано с безопасностью типов, как ответил Джон СкитВот.

20

t retain the assignment safe? It knows that,by executing for instance, the method with an Integer List, it gets from i.get an Integer value. So it try to set an Integer value at index 0 to the same Integer list (i).

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

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

ссылаются на тот же фактический тип?

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

17

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

It knows that,by executing for instance, the method with an Integer List, it gets from i.get an Integer value.

Это правда, но, как я сказал выше: компилятор можетonly знать & # x2013; вcompile timeпомните & # x2013; тотi.get(0) возвращаетObject, которая является верхней границей?, Но нет никакой гарантии, что? являетсяat runtime 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
More reading: Can't add value to the Java collection with wildcard generic type Java Collections using wildcard Generic collection & wildcard in java Generics - Cannot add to a List with unbounded wildcard What is the difference betwen Collection<?> and Collection<T>
Конечно, но это был именно вопрос.
Только в этом особом случае - добавление элемента в коллекцию, откуда он появился, - это правда. В общем,you can never add to a wildcarded collection из-заPECS'd method signatures участвует.
Ответ @meriton более правильный. Компилятору не нужно ничего знать о фактическом типе времени выполнения элементов. Еслиi было гарантировано, что в обоих случаях один и тот же экземпляр, (неизвестный) тип возвращаемого значенияget метод будет таким же, как (неизвестный) 2-й тип аргументаset метод, поэтому он будет компилироваться. Однако, как указал Меритон, эта гарантия никогда не принимается (даже еслиi были местными или окончательными).

Похожие вопросы