Параметризованных типов Java головоломок, расширение класса и с использованием шаблонов


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

import java.util.*;

class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}

class Base {}
class Derived extends Base {}

public class Test {
  public static void main(String[] args) {
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
    test.add(new Tbin<Derived>());

    TbinList<? extends Base> test2 = new TbinList<>();
    test2.add(new Tbin<Derived>());
  }
}

Использование Java 8. Мне кажется, что это прямое создание контейнера в test эквивалентно контейнеру в test2, но компилятор говорит:

Test.java:15: error: no suitable method found for add(Tbin<Derived>)
    test2.add(new Tbin<Derived>());
         ^

как я пишу Tbin и TbinList Итак, последняя строка приемлема?

обратите внимание, что я на самом деле буду добавлять typed TbinС почему я указал Tbin<Derived> в последней строке.

6   51  

6 ответов:

это происходит из-за пути конверсии захвата работает:

существует преобразование захвата из параметризованного типа G<T1,...,Tn> к параметризованному типу G<S1,...,Sn>, где 1 ≤ i ≤ n:

  • если Ti является аргументом типа подстановочного знака формы ? extends Bi, потом Si - это переменная нового типа [...].

преобразование захвата не применяется рекурсивно.

обратите внимание на конечный бит. Итак, это означает, что, учитывая такой тип:

    Map<?, List<?>>
//      │  │    └ no capture (not applied recursively)
//      │  └ T2 is not a wildcard
//      └ T1 is a wildcard

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

переносим это на TbinList, если у нас есть ArrayList<Tbin<?>>, подстановочный знак находится в том месте, где он не попасть в плен, но если у нас есть TbinList<?>, подстановочный знак находится в том месте, где он захватывается.

как я уже упоминал в комментариях, один очень интересный тест это:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();

мы получаем эту ошибку:

error: incompatible types: cannot infer type arguments for TbinList<>
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
                                                        ^
    reason: no instance(s) of type variable(s) T exist so that
            TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

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


кроме того, подумайте об этом таким образом.

предположим, что у нас было:

class Derived1 extends Base {}
class Derived2 extends Base {}

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

TbinList<? extends Base> test4 = new TbinList<Derived1>();

должны ли мы быть в состоянии добавить Tbin<Derived2> до test4? Нет,это будет кучное загрязнение. Мы могли бы закончить с Derived2 s плавает вокруг в TbinList<Derived1>.

замена определения TbinList С

class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

и определения test2 С

TbinList<Base> test2 = new TbinList<>();

вместо того, чтобы решить эту проблему.

С вашим определением вы в конечном итоге с ArrayList<Tbin<T>> где T-любой фиксированный класс расширения Base.

вы используете ограниченный подстановочный знак (TbinList<? extends Base>> ...). Этот подстановочный знак будет препятствовать добавлению каких-либо элементов в список. Если вы хотите получить дополнительную информацию, вот раздел о Шаблоны в документации.

вы можете определить общие типы следующим образом:

class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}

затем вы создадите экземпляр типа:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());

хорошо, вот ответ:

import java.util.*;

class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

class Base {}
class Derived extends Base {}

public class Test {
  public static void main(String[] args) {

    TbinList<Base> test3 = new TbinList<>();
    test3.add(new Tbin<Derived>());

  }
}

Как я и ожидал, вид очевидный, как только я это увидел. Но чтобы добраться сюда, нужно много метаться. Генераторы Java кажутся простыми, если вы только посмотрите на рабочий код.

спасибо всем за то, что выслушала.

вы не можете добавить какие-либо объекты в TbinList<? extends Base>, не гарантируется, какие объекты вы вставляете в список. Он должен считывать данные из test2 при использовании подстановочного знака extends

если вы объявили как TbinList<? extends Base> Что означает, что вы это любой подкласс базы класса или самой базы класса, и когда вы инициализируете его, вы используете Алмаз, отличный от конкретного имени класса, он делает ваш test2 не очевидно, что затрудняет определение того, какие объекты могут быть вставлены. Мое предложение что избежать такого объявления это опасно, он может не иметь ошибок компиляции, но это ужасный код, вы можете добавить что-то, но вы также можете добавить неправильную вещь, которая сломает ваш код.