дженерики java: ограниченное подстановочное затмение затмение

1

Существует внешний интерфейс Shape (имя изменено, но вы получаете идею).

У меня есть класс, у которого есть конструктор, который принимает

Collection<? extends Shape>

Этот класс находится под контролем. Я пишу модульные тесты. Я хочу создать коллекцию, которая расширяет форму для предоставления конструктору.

Я замечаю, что ни один класс в нашем проекте не реализует Shape, поэтому я пишу внутренний класс:

private class TestShape implements Shape {
    private static final long serialVersionUID = 1L;

    @Override
    public String getAuthority() {
        return "Foo";
    }
}

Затем я пытаюсь создать свою коллекцию...

private ArrayList<? extends Shape> shapes;
shapes = new ArrayList<Shape>();
shapes.add(new TestShape());

Однако Eclipse недовольна мной...

The method add(capture#1-of ? extends Shape) in the type ArrayList<capture#1-of ? extends Shape> is not applicable for the arguments (Test.TestShape)

Зачем?

edit: Много редактирования, потому что синтаксис generics делает SO несчастным

  • 0
    Это УИК на работе!
  • 1
    @ user3580294 Я не знаком с этой аббревиатурой.
Показать ещё 4 комментария
Теги:
generics

3 ответа

4
Лучший ответ

Представьте, что у нас TestShape реализует Shape, а EvilShape также реализует Shape. Теперь представьте себе следующее:

private ArrayList<? extends Shape> shapes; //OK
shapes = new ArrayList<EvilShape>(); // OK
authorities.add(new TestShape()); // Type safety issue

Ясно, что такая вещь не сработает. Вместо этого выполните следующие действия:

private ArrayList<Shape> shapes; //OK
shapes = new ArrayList<Shape>(); // OK
authorities.add(new TestShape()); // OK

Или следующее:

private ArrayList<? super Shape> shapes; //OK
shapes = new ArrayList<Shape>(); // Or, for that matter, an 'ArrayList<Object>'.
authorities.add(new TestShape()); // OK

Это будет работать, поскольку вы можете добавить подкласс/реализацию Shape в List<Shape>. Использование точного параметра типа, а не подстановочного знака, предотвратит следующее:

private ArrayList<Shape> shapes; //OK
shapes = new ArrayList<EvilShape>(); // DANGER! Java does not allow this

Аббревиатура PECS помогает запомнить это: продюсер продюсеров, Consumer Super. Это происходит от Joshua Bloch Effective Java.

  • 0
    Это, конечно, кажется правильным ответом, но я делаю именно это и все еще получаю ошибку компиляции затмения.
  • 0
    Ах, я думал, что нашел ответ с моим импортом формы, но нет, этот импорт правильный. Все еще в замешательстве.
Показать ещё 1 комментарий
0

Если это для единичного теста, просто создайте коллекцию Shapes:

import java.util.ArrayList;
import java.util.Collection;

public class test {
    public interface Shape {
        String getAuthority();

    }

    private static class TestShape implements Shape {

        @Override
        public String getAuthority() {
            return "Foo";
        }
    }

    public static void method(Collection<? extends Shape> shapes){

    }

    public static void main(String ... args){
        ArrayList<Shape> shapes;
        shapes = new ArrayList<Shape>();
        shapes.add(new TestShape());
        method(shapes);
    }
}

это компилируется чисто и, вероятно, как метод используется в реальной практике.

0

У меня есть класс, у которого есть конструктор, который принимает

Collection<? extends Shape>

...

Затем я пытаюсь создать свою коллекцию...

private ArrayList<? extends Shape> shapes;

Я думаю, что одна вещь, которая может сбить с толку, состоит в том, что только потому, что существует такой параметр метода (Collection<? extends Shape>), вам не нужно создавать такую переменную. Подумайте о цели переменной/параметра, использующей общие подстановочные знаки (? Extends X или? Super Y) как способ написать некоторый "общий" код, который будет работать с любым значением из family of similar types а не с обычным "a значение определенного типа ".

-

Прекрасным примером этого является подпись для Collection.copy():

public static <T> void copy(List<? super T> dest,List<? extends T> src)

Этот метод позволяет мне передать экземпляр List<Integer> и скопировать его значения в экземпляр List<Number>. Это также позволяет мне скопировать List<Integer> в List<Integer>. Он позволяет использовать один и тот же код с разными типами ("семейство типов"). Я могу проверить его с использованием общих переменных подстановочных знаков:

List<Integer> src = new ArrayList<>();
src.add(1);
List<Number> dest = new ArrayList<>();

Collection.copy(src, dest);

assert(src.get(0), dest.get(0));

-

Большой компромисс с использованием переменной или параметра, например List<? super T> List<? super T> или List<? extends T> List<? extends T> заключается в том, что поскольку это не переменная для определенного типа, существуют ограничения на то, что вы можете предположить о том экземпляре, на который он указывает, и потому вы ограничены тем, что вы можете с ним сделать.

PECS - это крошечный бит упрощения, но он делает большую мнемонику: PECS= "продюсер продлевает, потребитель супер" означает, что переменная (или параметр) для универсального типа подстановочных знаков, использующая extends может использоваться только для "создания" значений, то есть вы можете спокойно читать только из него, вы не можете добавить к нему. Аналогично, переменная для универсального типа подстановочных знаков, использующая super может использоваться только для "потребления" значений - вы можете безопасно только подавать ее значения (добавить), но есть ограничения на то, что вы можете предположить о значениях, которые можно "читать" " от него.

Таким образом, для вашего примера я бы вообще не использовал переменную с общим подстановочным знаком, просто передав конструктору конструкцию List <>. Если вы должны использовать переменную с общим подстановочным знаком, которая использует переменную для определенного типа, чтобы заполнить ее, потому что переменную с общим подстановочным знаком, которая использует extends не может быть использована для ее заполнения:

private ArrayList<Shape> shapesSpecific;
private ArrayList<? extends Shape> shapesExtends;
shapesExtends = shapesSpecific = new ArrayList<Shape>();

shapesSpecific.add(new TestShape());   // fine
//shapesExtends.add(new TestShape());  // error - but the line above added a value already

// Another option - create it already filled:
// shapeExtends = Arrays.asList(new TestShape());

new Foo(shapesSpecific);  // fine
new Foo(shapesExtends);   // fine

-

Вы можете найти этот вопрос/ответ полезным:

Как добавить в список <? расширяет Number> структуры данных? - вот пример из него:

Подстановочное объявление List<? extends Number> foo3 List<? extends Number> foo3 означает, что переменная foo3 может содержать любое значение из семейства типов (а не любое значение определенного типа). Это означает, что любые из них являются юридическими заданиями:

List<? extends Number> foo3 = new ArrayList<Number>;  // Number "extends" Number
List<? extends Number> foo3 = new ArrayList<Integer>; // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>;  // Double extends Number

Итак, с учетом этого, какой тип объекта вы могли бы добавить в List foo3 который был бы законным после любого из вышеперечисленных назначений ArrayList:

  • Вы не можете добавить Integer потому что foo3 может указывать на List<Double>.
  • Вы не можете добавить Double потому что foo3 может указывать на List<Integer>.
  • Вы не можете добавить Number потому что foo3 может указывать на List<Integer>.

Вы не можете добавить какой-либо объект в List<? extends T> List<? extends T> потому что вы не можете гарантировать, какой вид List он действительно указывает, поэтому вы не можете гарантировать, что объект разрешен в этом List. Единственная "гарантия" заключается в том, что вы можете читать только ее, и вы получите T или подкласс T

Это аналогичный вопрос/ответ:

Разница между <? супер T> и <? расширяет T> в Java

Ещё вопросы

Сообщество Overcoder
Наверх
Меню