Яка різниця між <? розширює Base> і <T розширює Base>?


29

У цьому прикладі:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() не вдалося компілювати:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

при цьому compiles()приймається компілятором.

Ця відповідь пояснює, що різниця полягає лише в тому, що на відміну від цього <? ...>, <T ...>ви можете пізніше згадати тип, що, здається, не так.

Яка різниця між <? extends Number>і <T extends Number>в цьому випадку, і чому не складається перший збір?


Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Самуель Liew

[1] Загальний тип Java: різниця між списком <? розширює Число> та Список <T розширює Число>, схоже, задають те саме питання, але, хоча це може зацікавити, насправді це не дублікат. [2] Хоча це гарне запитання, заголовок не належним чином відображає конкретне запитання, яке задають у заключному реченні.
skomisa


Як щодо пояснення тут ?
jrook

Відповіді:


14

Визначивши метод із наступним підписом:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

і викликати його так:

compiles(new HashMap<Integer, List<Integer>>());

В jls § 8.1.2 знаходимо, що (цікава частина, виділена жирним шрифтом):

Загальне оголошення класу визначає набір параметризованих типів (§4.5), по одному для кожного можливого виклику розділу параметра типу за аргументами типів . Усі ці параметризовані типи мають однаковий клас під час виконання.

Іншими словами, тип Tпорівнюється з типом введення та присвоюється Integer. Підпис фактично стане static void compiles(Map<Integer, List<Integer>> map).

Що стосується doesntCompileметоду, jls визначає правила підтипу ( §4.5.1 , виділений мною).

Кажуть, що аргумент типу T1 містить ще один аргумент типу T2, написаний T2 <= T1, якщо набір типів, позначений T2, є, мабуть, підмножиною набору типів, позначених T1, під рефлексивним та транзитивним закриттям наступних правил ( де <: позначає підтипізацію (§4.10)):

  • ? розширює T <=? розширює S, якщо T <: S

  • ? розширює T <=?

  • ? супер Т <=? супер S, якщо S <: T

  • ? супер Т <=?

  • ? супер Т <=? розширює Об'єкт

  • Т <= Т

  • Т <=? розширює Т

  • Т <=? супер Т

Це означає, що ? extends Numberдійсно містить Integerабо навіть List<? extends Number>містить List<Integer>, але це не так Map<Integer, List<? extends Number>>і для Map<Integer, List<Integer>>. Більше про цю тему можна знайти в цій темі SO . Ви все ще можете зробити версію з ?робочими символами, заявивши, що очікуєте підтип List<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

[1] Я думаю, що ти мав на увазі ? extends Number, ніж ? extends Numeric. [2] Ваше твердження, що "це не так для списку <? Розширює номер> та списку <інтеграл>", є невірним. Як вже вказав @VinceEmigh, ви можете створити метод static void demo(List<? extends Number> lst) { }і назвати його таким demo(new ArrayList<Integer>());чи цим demo(new ArrayList<Float>());, а код компілюється і працює ОК. Або я, можливо, неправильно читаю чи не розумію те, що ви заявили?
skomisa

@ Ви маєте рацію в обох випадках. Коли йдеться про ваш другий пункт, я написав це в омані. Я мав на увазі List<? extends Number>як параметр типу всієї карти, а не себе. Дуже дякую за коментар.
Андронік

@skomisa з тієї ж причини List<Number>не містить List<Integer>. Припустимо, у вас є функція static void check(List<Number> numbers) {}. Коли виклик з check(new ArrayList<Integer>());ним не компілюється, ви повинні визначити метод як static void check(List<? extends Number> numbers) {}. З картою це те саме, але з більш гніздування.
Андронік

1
@skomisa так само, як Numberпараметр типу списку, і його потрібно додати, ? extendsщоб зробити його коваріантом, List<? extends Number>є параметром типу, Mapа також необхідним ? extendsдля коваріації.
Андронік

1
ДОБРЕ. Оскільки ви запропонували рішення для багаторівневої підстановки (він же "вкладений підстановочний знак" ?) Та пов'язаний з відповідною довідкою JLS, отримайте виграш.
skomisa

6

У дзвінку:

compiles(new HashMap<Integer, List<Integer>>());

T узгоджується з Integer, тому типом аргументу є a Map<Integer,List<Integer>>. Це не стосується методу doesntCompile: тип аргументу залишається Map<Integer, List<? extends Number>>незалежним від фактичного аргументу у виклику; і це не можна віднестиHashMap<Integer, List<Integer>> .

ОНОВЛЕННЯ

У doesntCompileметоді ніщо не заважає вам зробити щось подібне:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

Тож очевидно, що він не може сприйняти HashMap<Integer, List<Integer>>аргумент.


Яким би був такий дійсний дзвінок doesntCompile? Просто цікаво про це.
Xtreme Biker

1
@XtremeBiker працював doesntCompile(new HashMap<Integer, List<? extends Number>>());би, як би doesntCompile(new HashMap<>());.
skomisa

@XtremeBiker, навіть це працювало б, карта <Цілий список, список <? розширює Число >> map = новий HashMap <Integer, List <? розширює число >> (); map.put (null, новий ArrayList <Integer> ()); doesntCompile (карта);
MOnkey

"що не призначається з HashMap<Integer, List<Integer>>", чи можете ви, будь ласка, пояснити, чому це не відводиться від нього?
Dev Null

@DevNull дивіться моє оновлення вище
Моріс Перрі

2

Простий приклад демонстрації. Цей же приклад можна візуалізувати, як нижче.

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>> є багаторівневим типовим символом, тоді як List<? extends Number> типовим символом, як стандартним типовим символом.

Дійсні конкретні дані типової картки List<? extends Number>включають Numberі будь-які підтипи, Numberтоді як у випадкуList<Pair<? extends Number>> це аргумент типу, аргумент типу і сам має конкретну інстанцію родового типу.

Дженріки інваріантні, тому Pair<? extends Number>тип дикої картки може приймати лише Pair<? extends Number>>. Внутрішній тип ? extends Numberвже коваріантний. Ви повинні зробити тип додавання як коваріант, щоб дозволити коваріантність.


Як це <Pair<Integer>>не працює, <Pair<? extends Number>>але працює <T extends Number> <Pair<T>>?
jaco0646

@ jaco0646 Ви, по суті, задаєте те саме питання, що і ОП, і відповідь від Andronicus була прийнята. Дивіться приклад коду в цій відповіді.
skomisa

@skomisa, так, я задаю те саме питання з кількох причин: одна полягає в тому, що ця відповідь насправді не відповідає на питання ОП; але два - це те, що я вважаю цю відповідь легшою для розуміння. Я не можу дотримуватися відповіді від Andronicus жодним чином, що приводить мене до розуміння вкладених проти не вкладених дженериків або навіть Tпроти ?. Частина проблеми полягає в тому, що коли Андронік досягає життєвої точки свого пояснення, він переходить до іншої нитки, яка використовує лише тривіальні приклади. Я сподівався отримати тут більш чітку і повну відповідь.
jaco0646

1
@ jaco0646 Гаразд. У документі "Поширені запитання про Java Generics - Аргументи типу" від Анжеліки Лангер є FAQ, що має назву Що означають багаторівневі (тобто вкладені) макіяжі? . Це найкраще джерело, про яке я знаю, для пояснення питань, порушених у питанні ОП. Правила щодо вкладених макіяжів не є простими та інтуїтивно зрозумілими.
skomisa

1

Я рекомендую вам ознайомитись із документацією про загальні символи, особливо рекомендації щодо використання підстановочних кодів

Відверто кажучи ваш метод #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

і дзвоніть, як

doesntCompile(new HashMap<Integer, List<Integer>>());

Принципово невірно

Додамо юридичну реалізацію:

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

Це дійсно добре, тому що подвійний розширює номер, так що List<Double>це абсолютно добре, так List<Integer>чи не так?

Однак ви все ще вважаєте, що законно перейти сюди new HashMap<Integer, List<Integer>>()зі свого прикладу?

Компілятор не вважає цього, і робить все, що можна зробити, щоб уникнути подібних ситуацій.

Спробуйте виконати таку ж реалізацію з методом #compile, і компілятор, очевидно, не дозволяє вам скласти список пар.

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

В основному ви не можете нічого не ставити, але List<T>тому безпечно називати цей метод за допомогою new HashMap<Integer, List<Integer>>()або new HashMap<Integer, List<Double>>()абоnew HashMap<Integer, List<Long>>() або new HashMap<Integer, List<Number>>().

Тож у двох словах, ви намагаєтеся обдурити компілятор, і він справедливо захищає від такого обману.

Примітка: відповідь, опублікована Морісом Перрі , абсолютно правильна. Я просто не впевнений, що це досить зрозуміло, тому спробував (дуже сподіваюся, що мені вдалося) додати більш обширну посаду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.