Смущает лить список в обобщенном методе? - PullRequest
0 голосов
/ 29 февраля 2020
    class A {
        private int a;
    }

    public static <T> List<T> listStrToListT(String str) {
        String[] idStrs = str.replace(" ", "").split(",");
        List<T> uids = new ArrayList<>();
        for (String idStr : idStrs) {
            uids.add((T) idStr);
        }
        return uids;
    }

    public static void main(String[] args) {
        List<A> lst = listStrToListT("1,2,3");
        System.err.println(lst);
    }

В этой программе нет ошибок. Но когда я отлаживаю (на рисунке ниже): lst - это List<String>. Почему я непосредственно назначаю List<String> (правая сторона) для List<A> (левая сторона)?

Debug error Image

1 Ответ

2 голосов
/ 29 февраля 2020

Помните, что дженерики в Java - это всего лишь время компиляции. Во время выполнения все универсальные c параметры стираются в не универсальные c типы.

С точки зрения компилятора, listStrToListT может возвращать любой тип List, который хочет вызывающий, а не только List<String>. Вы убедили компилятора в этом не факте, сделав (1) listStrToListT generi c и (2) приведение idStr к T. Вы говорите: «Я уверен, что этот актерский состав сработает, когда он запустится. Не волнуйтесь, компилятор! Этот актерский состав, безусловно, пахнет рыбой, не так ли? Что если T равно A ...

В любом случае, теперь List<A> lst = listStrToListT("1,2,3"); компилируется, так как listStrToListT "может возвращать любой тип List", как упоминалось ранее. Вы можете себе представить, что T подразумевается как A, и ваше приведение в listStrToListT завершится неудачно во время выполнения, но это не то, что происходит.

Теперь это время выполнения, все общие c типы стереть, и ваш код будет выглядеть следующим образом:

public static List listStrToListT(String str) {
    String[] idStrs = str.replace(" ", "").split(",");
    List uids = new ArrayList();
    for (String idStr : idStrs) {
        uids.add((Object)idStr);
    }
    return uids;
}

// main method:
List lst = listStrToListT("1,2,3");
System.out.println(lst);

Обратите внимание, что приведение к T становится приведением к Object, что на самом деле просто избыточно.

Распечатка список включает в себя вызов toString для каждого из Object s, поэтому приведение там не выполняется.

Обратите внимание, что то, что "пахло рыбой" во время компиляции, полностью действует во время компиляции. Рыбный бросок стал совершенно действительным (и избыточным) броском до Object! Где приведение go?

Приведение будет вставлено только при необходимости. Именно так работают дженерики в Java. Итак, давайте создадим такую ​​ситуацию. Допустим, в A у вас есть геттер для поля a, и вместо печати всего списка вы печатаете a первого элемента:

// main method:
List<A> lst = listStrToListT("1,2,3");
System.out.println(lst.get(0).getA());

Ну, чтобы иметь возможность для доступа к getA необходимо вставить приведение:

List lst = listStrToListT("1,2,3");
System.out.println(((A)lst.get(0)).getA());

в противном случае lst.get(0) будет иметь тип Object, а Object s не имеет getA метода.

В это время ваша программа обработает sh.

...