Почему параметр типа Java не может иметь нижнюю границу? - PullRequest
46 голосов
/ 04 февраля 2011

Я понимаю, что вы не можете связать параметр типа универсального Java-кода с нижней границей (то есть, используя ключевое слово super). Я читал, что Angelika Langer Generics FAQ должен сказать по этому вопросу . Они говорят, что это сводится к тому, что нижняя граница бесполезна («не имеет никакого смысла»).

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

public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);

Но это неоправданно ограничивает ваших клиентов. Почему они не могут вызвать ваш метод так:

//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5); 
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);

//shouldn't compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);

В этот момент у меня возникнет соблазн попробовать следующее определение:

public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
  List<T> list = new ArrayList<T>(size);
  for(int i = 0; i < size; i++) {
     list.add("");
  }
  return list;
}

Но он не скомпилируется; ключевое слово super в этом контексте недопустимо.

Является ли мой пример выше плохим примером (игнорируя то, что я говорю ниже)? Почему здесь не полезна нижняя граница? И если это будет полезно, какова реальная причина того, что это не разрешено в Java?

приписка

Я знаю, что лучшая организация может быть примерно такой:

public static void populateListWithEmptyStrings(List<? super String> list, int size);

List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);

Можем ли мы для целей этого вопроса сделать вид, что из-за требования нам нужно выполнить обе операции в одном вызове метода?

Редактировать

@ Том Дж (обоснованно) спрашивает, какую выгоду, если бы List<CharSequence> имел бы, чем List<String>. С одной стороны, никто не сказал, что возвращаемый список является неизменным, поэтому вот одно из преимуществ:

List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));

Ответы [ 5 ]

15 голосов
/ 05 февраля 2011

По сути, это недостаточно полезно.

Я думаю, что ваш пример указывает на единственное преимущество нижней границы - функция, которую часто задаваемые вопросы вызывает Restricted Instantiation:

Суть: все, что «супер» привязал бы к вам - это ограничение , что только супертипы Number может использоваться как аргументы типа . ....

Но, как отмечают другие посты, полезность даже этой функции может быть ограничена.

Из-за природы полиморфизма и специализации верхние границы гораздо полезнее, чем нижние, как описано в FAQ ( Доступ к нестатическим элементам и Стирание типа ). Я подозреваю, что сложность, представленная нижними границами, не стоит ее ограниченного значения.


ОП: Я хочу добавить, я думаю, вы показали, что это полезно, но недостаточно. Придумайте неопровержимые случаи использования убийцы, и я верну JSR. : -)

11 голосов
/ 05 февраля 2011

спецификация говорит о нижних границах параметров типа, например

4.10.2

переменная типа является прямым супертипом своей нижней границы.

5.1.10

свежая переменная типа ... нижняя граница которой

Похоже, что переменная типа имеет (ненулевую) нижнюю границу, только если она синтетическая, как результат захвата подстановочного знака. Что, если язык разрешает нижние границы для всех параметров типа? Вероятно, это не доставляет много хлопот, и исключено только упрощение генериков (хорошо ...) Обновление Говорят, что теоретическое исследование параметров нижнего ограниченного типа не проводится полностью.

Обновление: статья, в которой утверждены нижние границы, в порядке: "Достигнута инференция типов Java: можем ли мы это исправить", Даниэль Смит

RETRACT: следующий аргумент неверен. Пример ОП правомерен.

Ваш конкретный пример не очень убедителен. Во-первых, это небезопасно. Возвращенный список действительно List<String>, его небезопасно рассматривать как другой тип. Предположим, ваш код компилируется:

    List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

тогда мы можем добавить к нему не-String, что неправильно

    CharSequence chars = new StringBuilder();
    l2.add(chars); 

Ну, а List<String> нет, но чем-то похоже на список CharSequence. Ваша потребность может быть решена с помощью подстановочного знака:

public static  List<String> createArrayListFullOfEmptyStrings(int size)  

// a list of some specific subtype of CharSequence 
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);

// illegal, won't compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());
1 голос
/ 07 февраля 2017

Больше, чем ответ, это другой (возможно, убийственный?) Вариант использования.У меня есть помощник ModelDecorator.Я хочу, чтобы у него был следующий общедоступный API

class ModelDecorator<T>{
    public static <T> ModelDecorator<T> create(Class<T> clazz);
    public <SUPER> T from(SUPER fromInstance);
}

Итак, с учетом классов A, B расширяется A, его можно использовать так:

A a = new A();
B b = ModelDecorator.create(B.class).from(a);

Но я хочу иметь границына T и SUPER, поэтому я проверяю, что только подпункты могут быть созданы с помощью API.На данный момент я могу сделать:

C c = new C();
B b = ModelDecorator.create(B.class).from(c);

Где B НЕ наследует от C.

Очевидно, если бы я мог сделать:

    public <SUPER super T> T from(SUPER fromInstance);

Это решило бымоя проблема.

0 голосов
/ 16 февраля 2011

Хм, хорошо - давайте работать с этим. Вы определяете метод:

public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {

Что это значит? Это означает, что если я вызову ваш метод, то получу список некоторого суперкласса String. Может быть, он возвращает список строк. Может быть, он возвращает список объектов. Я не знаю.

Cool.

List<Object> l1 = createArrayListFullOfEmptyStrings(5);

По вашему мнению, это должно скомпилироваться. Но это не правильно! Я могу положить Integer в список объектов - l1.add(3). Но если вы возвращаете список строк, то это должно быть незаконно.

List<String> l3 = createArrayListFullOfEmptyStrings(5);

По вашему мнению, это должно скомпилироваться. Но это не правильно! l3.get(1) всегда должен возвращать String ... но этот метод мог бы вернуть список Object, что означает, что l3.get (1) может быть целым числом.

Единственное, что работает, это

List<? super String> l5 = createArrayListFullOfEmptyStrings(5);

Все, что я знаю, это то, что я могу безопасно позвонить l4.put("foo"), и я могу безопасно получить Object o = l4.get(2).

0 голосов
/ 04 февраля 2011

Какое преимущество дает вам набор в этот момент?Когда вы перебираете возвращенную коллекцию, вы все равно сможете сделать следующее:

for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
...