Загальне ключове слово Java


76

Я перебирав ці теми

Однак я все ще, здається, збився з superключовим словом:

  1. Коли ми оголошуємо колекцію таким чином:

    List<? super Number> list = null;
    list.add(new Integer(0)); // this compiles
    list.add(new Object()); // this doesn't compile
    

чи не повинно бути навпаки - у нас є список, який містить деякі об'єкти (невідомого типу), батьків яких Number. Тому Objectмає відповідати (оскільки він є батьком Number), і Integerне повинен. Справа чомусь навпаки.

  1. За умови, що ми маємо такий код

    static void test(List<? super Number> param) {
      param.add(new Integer(2));
    }
    
    public static void main(String[] args) {
      List<String> sList = new ArrayList<String>();
      test(sList);            // will never compile, however...
    }
    

Неможливо скласти вищезазначений код (і мій розум підказує, що це правильна поведінка), але основна логіка може довести протилежне:

String is Object, Object is superclass of Number. So String should work.

Я знаю, що це божевілля, але чи не в цьому причина, чому вони не дозволяли <S super T>конструкти? Якщо так, то чому<? super T> це дозволено?

Хтось може допомогти мені відновити відсутню частину цього логічного ланцюга?

Відповіді:


98

Обмежений узагальнюючий знак у List<? super Number>може захоплювати Numberта будь-який його супертип. Оскільки Number extends Object implements Serializableце означає, що на даний момент єдиними типами, які можна конвертувати, List<? super Number>є:

  • List<Number>
  • List<Object>
  • List<Serializable>

Зверніть увагу, що ви можете add(Integer.valueOf(0))до будь-якого з перерахованих вище типів. однак ви НЕ МОЖЕТЕ add(new Object()) a List<Number>або a List<Serializable>, оскільки це порушує загальне правило безпеки типу.

Отже, НЕ вірно, що ви можете addбудь-який супертип Numberдо a List<? super Number>; це просто не так, як працюють перетворення обмежених символів підстановки та захоплення. Ви не заявляєте a, List<? super Number>оскільки, можливо, захочете додати Objectйого (не можете!); Ви робите це тому, що хочете додати Numberдо нього об'єкти (тобто це "споживач" Number), а просто a List<Number>занадто обмежує.

Список літератури

Дивитися також

  • Ефективне друге видання Java , пункт 28: Використовуйте обмежені символи підстановки, щоб збільшити гнучкість API
    • "PECS означає" виробник " extends," споживач "super

Пов’язані запитання

  • Забагато для переліку, PECS, new Integer(0)проти valueOfта ін

16
Я не можу підтвердити, правильна чи неправильна ваша відповідь, оскільки вона занадто заплутана і передбачає занадто багато знань. Це питання не ставилося б, якби ми могли зрозуміти таку відповідь!
Алекс Ворден,

@AlexWorden Ця відповідь технічно дуже правильна, жодного питання щодо неї, якщо ви хочете легке пояснення, можливо, це може допомогти
Євген

23

Перша частина List<Number>підходить, List<? super Number>але ви не можете додати знак Objecta List<Number>. Ось чому ви не можете додати Objectдо List<? super Number>.

З іншого боку, ви можете додати до свого списку кожен підклас Number( Numberвключений).

Для другої частини String- це Object, але Stringне є суперкласом Number.

Якби це працювало так, оскільки кожен клас є підкласом Object,super мало б значення.


Давайте розберемо всі можливі випадки з List<? super Number> :


  • Переданий список - це List<Object>
    • List<Object> буду працювати
    • Object вписується <? super Number>
    • Ви можете додати будь-який підтип NumberдоList<Object>
    • Навіть якщо ви також можете додати Stringдо нього єдине, у чому ви впевнені, це те, що ви можете додати будь-який підклас Number.

  • Переданий список List<Number>:
    • List<Number> буду працювати
    • Number вписується <? super Number>
    • Ви можете додати будь-який підтип NumberдоList<Number>

  • Переданий список є List<Integer>(або будь-яким підкласом Number):
    • List<Integer> не буде працювати
    • Ціле число - це підклас, Numberтому саме цього ми хочемо уникати
    • Навіть якщо a Integerвписується в a, Numberви не зможете додати будь-який підклас Numberв a List<Integer>(наприклад a Float)
    • super не означає підклас.

  • Переданий список є List<String>(або будь-яким класом, що не поширюється, Numberа також не перебуває у "суперієрархії" Number(тобто. NumberІ Object):
    • List<String> не буде працювати
    • String не вкладається Number "супер ієрархію"
    • Навіть якщо Stringвходить Object(що є супер класом Number), ви не будете впевнені, що зможете додати a Numberдо, Listщо містить будь-який підклас одного з супер класівNumber )
    • super не означає будь-який підклас одного з супер класів, це означає лише один із супер класів.

Як це працює ?

Можна сказати, що до тих пір, поки ви можете додати будь-який підклас Numberіз набраним List, він поважає superключове слово.


1
@Vuntic, Важко бути абсолютно зрозумілим із генериками, але я оновив свою відповідь і спробував :)
Колін Хеберт,

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

це хороший спосіб пояснити загальні відомості, дати всі можливі сценарії, але Ви можете додати кілька кольорів та жирне форматування, щоб зробити речі зрозумілішими для читання;)
Анджей Реманн,

7

Я деякий час не отримував. Багато відповідей тут, а інші запитання конкретно показують, коли і де певні звичаї є помилками, але не стільки чому.

Ось так я нарешті зрозумів. Якщо у мене є функція, яка додає Numbers до a List, я можу додати їх типу, MySuperEfficientNumberякий є моїм власним класом, що реалізує Number(але не є підкласом Integer). Тепер абонент може не знати нічого про це MySuperEfficientNumber, але до тих пір, поки він знає, як обробляти елементи, додані до списку, як ніщо більш конкретне, ніжNumber , вони будуть добре.

Якби я оголосив свій метод як:

public static void addNumbersToList(List<? extends Number> numbers)

Тоді абонент міг перейти в List<Integer>. Якщо не мій метод доданий MySuperEfficientNumberдо кінця numbers, то абонент більше не матиме Listз Integerї та наступний коду не працюватиме:

List<Integer> numbers = new ArrayList<Integer>();
addNumbersToList(numbers);

// The following would return a MySuperEfficientNumber not an Integer
Integer i = numbers.get(numbers.size()-1)

Очевидно, це не може працювати. І помилка буде всередині addNumbersToListметоду. Ви отримаєте щось на зразок:

The method add... is not applicable for the arguments (MySuperEfficientNumber)

Тому що це numbersможе бути будь-який конкретний вид Number, не обов'язково те, що MySuperEfficientNumberсумісно. Якби я перегорнув декларацію для використання super, метод компілювався б без помилок, але код абонента зазнав би помилки:

The method addNumbersToList(List<? super Number>)... is not applicable for the arguments (List<Integer>)

Оскільки мій метод говорить: "Не думай, що твоє Listможе бути чимось більш конкретним, ніж Number. Я можу додати Numberдо списку всілякі дивні речі, тобі просто доведеться з цим боротися. Якщо ти хочеш думати про них як щось навіть більш загальне, ніж Number- як Object- це добре, я гарантую, що вони будуть принаймні Numbers, але ви можете ставитись до них більш загально, якщо хочете ".

Тоді extendsяк кажуть: "Мені насправді байдуже, що Listти мені даруєш, якщо кожен елемент хоча б є Number. Це можуть бути будь-які Number, навіть твої власні дивні, нестандартні вигадані Numberречі. Поки вони реалізують цей інтерфейс, ми добре. Я не збираюся нічого додавати до вашого списку, оскільки я не знаю, який конкретний тип конкретного ви там використовуєте ".


4

List<? super Number> означає, що посилальний тип змінної передбачає, що ми маємо список Чисел, Об'єктів або Серіалізованих.

Причиною того, що ви не можете додати об'єкт, є те, що компілятор не знає, ЯКІ з цих класів містяться в загальному визначенні фактичного екземпляра об'єкта, тому він дозволяє передавати лише число або підтипи числа, такі як Double, Integer та так далі.

Скажімо, у нас є метод, який повертає a List<? super Number>. Створення об'єкта всередині методу інкапсульовано з нашого погляду, ми просто не можемо сказати, чи це щось подібне:

List<? super Number> returnValue = new LinkedList<Object>();

або

List<? super Number> returnValue = new ArrayList<Number>();

Отже, загальним типом може бути Object або Number. В обох випадках нам буде дозволено додавати число, але лише в одному випадку нам буде дозволено додавати об’єкт.

У цій ситуації потрібно розрізнити тип посилання та фактичний тип об’єкта.


2

List<? super Number>це таке, List<AncestorOfNumber>де ми можемо неявно перевести кожен Numberна свій супер тип AncestorOfNumber.

Розгляньте це: Який загальний тип повинен бути ????в наступному прикладі?

InputStream mystream = ...;

void addTo(List<????> lsb) {
    lsb.add(new BufferedInputStream(mystream));
}

List<BufferedInputStream> lb = new ArrayList<>();
List<InputStream> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();

...
{ addTo(lb); addTo(li); addTo(lo); }

Відповідь: ????це все, на що ми можемо кинути BufferedInputStream, що є тим самим або одним із його предків:? super BufferedInputStream


2

Можна навести дуже простий приклад.

public void add(List<? super Number> list) {
}

це дозволить ці дзвінки

add(new LinkedList<Number>());

і все, що над номером подобається

add(new LinkedList<Object>());

але нічого нижче ієрархії так немає

add(new LinkedList<Double>());

або

add(new LinkedList<Integer>());

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

Наприклад, Список не приймає Об'єкт, незважаючи на Об'єкт, який приймає Число. Але оскільки це незрозуміло, єдиним допустимим входом буде число та його підтипи.


1

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


? extends NumberСпочатку розглянемо справу. Коли визначена колекція з такими межами, ми знаємо, що: кожен елемент матиме верхню межу як Number. Ми не знаємо точного типу (може бути an Integer/Long/etc), але точно знаємо, що його верхня межа Number.

Тож читання з такої збірки отримує нам Number. Це єдиний гарантований тип, який ми можемо отримати від нього.

Писати до такої колекції заборонено. Але чому? Хіба я не казав, що поки ми читаємо - ми завжди отримаємо Number, так навіщо забороняти писати до нього? Тут ситуація дещо більше пов'язана:

 List<Integer> ints = ....;
 List<? extends Number> numbers = ints;
 numbers.add(12D); // add a double in here

Якби додавання було дозволено numbers, ви могли б ефективно додати a Doubleу List of Integers.


Тепер до вашого прикладу:

 List<? super Number> list = null;
 list.add(new Integer(0));
 list.add(new Object());

Ми знаємо про те, listщо вона містить певний супертіп з Number, наприклад ,Object .

Читання з такого списку дасть нам певний тип X, де Xбуде батько Number. То що це? Ви насправді не можете знати. Це може бути теоретичним MyNumber extends Numberабо набагато простішим: an Object. Оскільки ви точно не можете знати, єдине безпечне, що можна прочитати з цього, - це супер-тип всього - Object.

Щось дивне може бути:

List<? super String> list = ...;
String s = list.get(0); // fails, compiler does not care that String is final

Написати на нього трохи складніше, але лише трохи. Пам'ятайте, що ми знаємо всередині цього list: це тип, який Number розширюється / реалізується (якби це був інтерфейс), тому ви завжди можете призначити підтип (або Numberсебе) цьому супертипу .

             Some type X
                 / \
                  |
                Number
                 / \
                  |
    Some type Y that we an put in here
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.