Как можно добавить элементы в универсальную коллекцию подстановочных знаков? - PullRequest
16 голосов
/ 07 октября 2008

Почему я получаю ошибки компилятора с этим кодом Java?

1  public List<? extends Foo> getFoos()
2  {
3    List<? extends Foo> foos = new ArrayList<? extends Foo>();
4    foos.add(new SubFoo());
5    return foos;
6  }

Где SubFoo - конкретный класс, реализующий Foo, а Foo - интерфейс.

Ошибки, которые я получаю с этим кодом:

  • В строке 3: «Не удалось создать экземпляр ArrayList <? Extends Foo>»
  • В строке 4: «Метод add (capture # 1-of? Extends Foo) в списке типа не применим для аргументов (SubFoo)»

Обновление: Благодаря Джеффу С я могу изменить Строку 3 на "new ArrayList ();". Но у меня все еще есть проблема со Строкой 4.

Ответы [ 5 ]

29 голосов
/ 07 октября 2008

Используйте это вместо:

1  public List<? extends Foo> getFoos()
2  {
3    List<Foo> foos = new ArrayList<Foo>(); /* Or List<SubFoo> */
4    foos.add(new SubFoo());
5    return foos;
6  }

Как только вы объявите foos как List<? extends Foo>, компилятор не будет знать, что безопасно добавлять SubFoo. Что если ArrayList<AltFoo> был присвоен foos? Это было бы допустимым назначением, но добавление SubFoo могло бы загрязнить коллекцию.

15 голосов
/ 16 июля 2012

Просто подумал, что добавлю к этому старому потоку, суммируя свойства параметров List, инстанцируемых с типами или подстановочными знаками ....

Когда метод имеет параметр / результат, который является списком, использование экземпляра типа или подстановочных знаков определяет

  1. Типы List, которые могут быть переданы методу в качестве аргумента
  2. Типы List, которые можно заполнить из результата метода
  3. Типы элементов, которые можно записать в список в методе
  4. Типы, которые могут быть заполнены при чтении элементов из списка в методе

Параметр / Тип возврата: List< Foo>

  1. Типы List, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
  2. Типы List, которые можно заполнить из результата метода:
    • List< Foo>
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
  3. Типы элементов, которые можно записать в список в методе:
    • Foo и подтипы
  4. Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Параметр / Тип возврата: List< ? extends Foo>

  1. Типы List, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
    • List< Subfoo>
    • List< SubSubFoo>
    • List< ? extends Foo>
    • List< ? extends SubFoo>
    • List< ? extends SubSubFoo>
  2. Типы List, которые можно заполнить из результата метода:
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
    • List< ? extends SuperSuperFoo>
  3. Типы элементов, которые можно записать в список в методе:
    • Отсутствует! Невозможно добавить.
  4. Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Параметр / Тип возврата: List<? super Foo>

  1. Типы List, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
    • List< Superfoo>
    • List< SuperSuperFoo>
    • List< ? super Foo>
    • List< ? super SuperFoo>
    • List< ? super SuperSuperFoo>
  2. Типы List, которые можно заполнить из результата метода:
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? super SubSubFoo>
  3. Типы элементов, которые можно записать в список в методе:
    • Foo и супертипы
  4. Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Интерпретация / Комментарий

  • потребности внешних абонентов определяют дизайн объявления метода, т. Е. Общедоступный API (как правило, основное внимание)
  • потребности внутреннего логического метода приводят к любым дополнительным решениям относительно фактических типов данных, объявленных и сконструированных внутренне (обычно вторичное рассмотрение)
  • используйте List<Foo>, если код вызывающей стороны всегда ориентирован на манипулирование классом Foo, поскольку это максимизирует гибкость как для чтения, так и для записи
  • используйте List<? extends UpperMostFoo>, если может быть много разных типов вызывающих, сосредоточенных на манипулировании другим классом (не всегда Foo), и в иерархии типов Foo есть единственный верхний класс, и если метод предназначен для внутренней записи в манипулирование списком и списком абонентов - чтение. Здесь метод может внутренне использовать List< UpperMostFoo> и добавлять к нему элементы перед возвратом List< ? extends UpperMostFoo>
  • если может быть много разных типов вызывающих, ориентированных на манипулирование другим классом (не всегда Foo), и если требуется чтение и запись в список, и в иерархии типов Foo есть единственный низший класс, тогда имеет смысл использовать List< ? super LowerMostFoo>
6 голосов
/ 07 октября 2008

Попробуйте:

public List<Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}

Общий конструктор ArrayList должен иметь определенный тип для параметризации, вы не можете использовать '?' подстановочный знак там. Изменение экземпляра на «новый ArrayList ()» решит первую ошибку компиляции.

Объявление переменной 'foos' может иметь подстановочные знаки, но, поскольку вы знаете точный тип, имеет больше смысла ссылаться на ту же информацию о типе. То, что у вас сейчас есть, говорит о том, что foos содержит какой-то определенный подтип Foo, но мы не знаем, какой именно. Добавление SubFoo может быть запрещено, поскольку SubFoo не является «всеми подтипами Foo». Изменение объявления на «List foos =» устраняет вторую ошибку компиляции.

Наконец, я бы изменил тип возвращаемого значения на «List », поскольку клиенты этого метода не смогут многое сделать с возвращаемым значением, как определено в настоящее время. Вы должны редко использовать подстановочные знаки в возвращаемых типах. При необходимости используйте параметризованную сигнатуру метода, но предпочитайте, чтобы ограниченные типы появлялись только в аргументах метода, так как это оставляет вызывающей стороне, которая может передавать определенные типы и работать с ними соответственно.

2 голосов
/ 07 октября 2008

Следующее будет работать нормально:

public List<? extends Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}
1 голос
/ 23 марта 2011

Чтобы получить представление о том, как работают дженерики, посмотрите этот пример:

    List<SubFoo> sfoo = new ArrayList<SubFoo>();
    List<Foo> foo;
    List<? extends Foo> tmp;

    tmp = sfoo;
    foo = (List<Foo>) tmp;

Дело в том, что он не был предназначен для локальных переменных / переменных-членов, но для сигнатур функций, поэтому он так задом наперед.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...