Общие нижние несвязанные и верхние подстановочные знаки

import java.util.List;
import java.util.ArrayList;

interface Canine {}
class Dog implements Canine {}
public class Collie extends Dog {
    public static void main(String[] args){
        List<Dog> d = new ArrayList<Dog>();
        List<Collie> c = new ArrayList<Collie>();
        d.add(new Collie());
        c.add(new Collie());
        do1(d); do1(c);
        do2(d); do2(c);
    }
    static void do1(List<? extends Dog> d2){
        d2.add(new Collie());
        System.out.print(d2.size());
    }
    static void do2(List<? super Collie> c2){
        c2.add(new Collie());
        System.out.print(c2.size());
    }
}

Ответ на этот вопрос говорит о том, что когда метод принимает универсальный тип с подстановочными знаками, к коллекции можно получить доступ или изменить ее, но не то и другое одновременно. (Кэти и Берт)

Что означает "когда метод принимает универсальный тип с подстановочными знаками, к коллекции можно получить доступ или изменить ее, но не и то, и другое"?

Насколько я знаю, метод do1 имеет List<? extends Dog> d2, поэтому доступ к d2 возможен только, но не к изменению. Метод d2 имеет List<? super Collie> c2, поэтому к c2 можно получить доступ и изменить его, и нет ошибки компиляции.

Общие рекомендации


person Joe    schedule 15.10.2012    source источник
comment
comment
См. раздел Java Generics: Что такое PECS?   -  person Jesper    schedule 15.10.2012


Ответы (5)


Ответ на этот вопрос говорит о том, что когда метод принимает универсальный тип с подстановочными знаками, к коллекции можно получить доступ или изменить ее, но не то и другое одновременно. (Кэти и Берт)

Это справедливое первое приближение, но не совсем правильное. Правильнее будет:

Вы можете добавить null к Collection<? extends Dog> только потому, что его метод add принимает аргумент ? extends Dog. Всякий раз, когда вы вызываете метод, вы должны передавать параметры, которые относятся к подтипу объявленного типа параметра; но для типа параметра ? extends Dog компилятор может быть уверен, что аргумент имеет совместимый тип, только если выражение имеет значение null. Однако вы, конечно, можете изменить коллекцию, вызвав clear() или remove(Object).

С другой стороны, если вы читаете из Collection<? super Dog>, его итератор имеет возвращаемый тип ? super Dog. То есть он будет возвращать объекты, которые являются подтипом некоторого неизвестного супертипа Dog. Но иначе Коллекция может быть Collection<Object>, содержащей только экземпляры String. Поэтому

for (Dog d : collection) { ... } // does not compile

поэтому единственное, что мы знаем, это то, что экземпляры Object возвращаются, т.е. единственный правильный способ итерации такой коллекции - это

for (Object o : collection) { ... }

но можно читать из коллекции, вы просто не знаете, какие типы объектов вы получите.

Мы можем легко обобщить это наблюдение на: Учитывая

class G<T> { ... }

и

G<? extends Something> g;

мы можем передавать null только параметрам метода с объявленным типом T, но мы можем вызывать методы с возвращаемым типом T и присваивать результату переменную типа Something.

С другой стороны, для

G<? super Something> g;

мы можем передавать любое выражение типа Something в параметры метода с объявленным типом T, и мы можем вызывать методы с возвращаемым типом T, но присваивать результат только переменной типа Object.

Подводя итог, можно сказать, что ограничения на использование типов с подстановочными знаками зависят только от формы объявлений методов, а не от того, что эти методы делают.

person meriton    schedule 15.10.2012
comment
Да, null, его можно присвоить любой ссылке любого типа, включая все типы, расширяющие Dog. - person Joe; 15.10.2012

Вы не можете добавить Cat к List<? extends Animal>, потому что вы не знаете, что это за список. Это также может быть List<Dog>. Таким образом, вы не хотите бросать свой Cat в Black Hole. Вот почему modification из List заявлено, что путь недопустим.

Точно так же, когда вы получаете что-то из List<? super Animal>, вы не знаете, что вы из этого получите. Вы даже можете получить Object или Animal. Но вы можете безопасно добавить Animal в этот List.

person Rohit Jain    schedule 15.10.2012

Я вставил ваш код в свою IDE. Внутри do1 было сообщено о следующей ошибке:

Метод add(capture#1-of ? extends Dog) в типе List неприменим для аргументов (колли)

Это, конечно, как и ожидалось.

person Marko Topolnik    schedule 15.10.2012

Вы просто не можете добавить Collie к List<? extends Dog>, потому что эта ссылка может содержать, например, List<Spaniel>.

person LuGo    schedule 15.10.2012

Я вставил ваш код в IDEONE http://ideone.com/msMcQ. Он не скомпилировался для меня, чего я и ожидал. Вы уверены, что у вас не было ошибок компиляции?

person emory    schedule 15.10.2012
comment
Я знаю, что в методе do1 возникает ошибка компилятора при попытке добавить элемент. Я сомневаюсь в том, что говорят Кэти и Берт. «когда метод принимает универсальный тип с подстановочным знаком, к коллекции можно получить доступ или изменить ее, но не то и другое» А с ‹? супер-колли› Я могу получить доступ и добавить элементы.¿? - person Joe; 15.10.2012
comment
@Джо. ОК, я неправильно понял ваш вопрос и подумал, что вы подразумеваете, что ваш код скомпилирован. Теперь вы не обращаетесь к элементам в методе do2. c2.size() не считается доступом. Доступ будет выглядеть примерно так: Collie c = c2 . get ( 0 ) ; Я исправил свой пример ideone.com/msMcQ, добавив код, иллюстрирующий - person emory; 15.10.2012