Как я могу использовать универсальный класс с объявлением подстановочного знака? - PullRequest
7 голосов
/ 07 мая 2019

В моем классе есть следующий участник:

List<? extends SomeObject> list;

Когда я пытаюсь сделать:

list.add(list.get(0));

Я получаю:

Test.java:7: error: no suitable method found for add(CAP#1)
        list.add(list.get(0));
            ^
    method Collection.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
    method List.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ? extends Object
    CAP#2 extends Object from capture of ? extends Object

У меня двоякий вопрос:

  1. Почему он не компилируется? Почему я не могу передать результат get() в add()?

  2. И как я могу достичь этого по-другому, не прибегая к кастингу?


Я понимаю, что в методе с <T extends SomeObject> я не могу просто сказать:

T someObject = list.get(0);
list.add(someObject);

, поскольку мой T может быть другим расширением, чем расширение ?.

Я также понимаю, что не могу сказать:

List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));

Но так как add и get должны работать с одним и тем же универсальным типом в list.add(list.get(0)), я не понимаю, почему компилятор не принимает его.


Что мне действительно нужно, это

[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);

чтобы я смог позже

list.add(someObject);

Не думаю, что мне нужно шаблонировать весь класс, чтобы этого добиться, не так ли?

class MyClass<T extends SomeObject> {
List<T> list;

, а затем метод с

T someObject = list.get(0);

конечно работает, но привинчивает другие части моего кода.

Итак, первый вопрос: почему это не работает, второй вопрос: какой лучший обходной путь?

1 Ответ

3 голосов
/ 07 мая 2019

У меня двойной вопрос, почему я не могу:

list.add(list.get(0));

Поскольку компилятор недостаточно умен, чтобы знать, что вы добавляете что-то из list обратно в list. Компилятор не считает, list.get(0) имеет какое-либо отношение к list после его оценки: это просто "некоторое выражение" типа ? extends SomeObject.

Чтобы решить эту проблему, добавьте метод с собственной переменной типа:

private <T> void addFirst(List<T> list) {
  list.add(list.get(0));
}

и замените исходный list.add(list.get(0)); вызовом этого:

addFirst(list);

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


Возможно, стоит отметить, что это аналог Collections.swap метода : в нем используется set вместо add, но, с точки зрения обобщенного, это то же самое:

@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
    // instead of using a raw type here, it's possible to capture
    // the wildcard but it will require a call to a supplementary
    // private method
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

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

Я бы предположил, что это так только в целях обратной совместимости. Если бы у вас была возможность написать это снова, вы можете просто определить переменную типа, как в методе addFirst выше.

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