Существуют ли какие-либо планы по Java для добавления общей ковариации сбора?

Я пытался написать код, который выглядит так:

public List<IObject> getObject(){
  ArrayList<ConcreteObject> objects = new ArrayList<ConcreteObject>();
  return objects;
}

(где ConcreteObject реализует IObject)

Это не работает вообще. Это дает ошибку компилятора. Планирует ли Java поддерживать это в будущем? Какое лучшее обходное решение до этого? То, что я закончил, было:

public List<IObject> getObject(){
  List<IObject> objects = new ArrayList<IObject>();
  return objects;
}

Это работает, и, возможно, на самом деле нет никаких плохих побочных эффектов для этого. Является ли это общепринятым наилучшим подходом?

Ответы

Ответ 1

Java уже поддерживает эту функцию, вам просто нужно ее использовать. Для праймера прочитайте Учебник по Wildcards от Sun.

Вы хотите следующее:

public List<? extends IObject> getObject(){
  ArrayList<ConcreteObject> objects = new ArrayList<ConcreteObject>();
  return objects;
}

В качестве альтернативы можно использовать небезопасный листинг:

public <T extends IObject> List<T> getObject(){
  ArrayList<T> objects = (ArrayList<T>) new ArrayList<ConcreteObject>();
  return objects;
}

... но этот метод довольно хрупкий и будет генерировать исключение во время выполнения (кроме сигнализации об ошибке компиляции) при попытке доступа к его элементам с недопустимым типом:

@SuppressWarnings("unchecked")
public <T extends IObject> List<T> getObject(){
  ArrayList<T> objects = (ArrayList<T>) new ArrayList<ConcreteObject>();
  objects.add(new ConcreteObject());
  return objects;
}

…
List<OtherConcreteObject> objects = getObject(); // Works.
OtherConcreteObject obj = OtherConcreteObject.get(0); // Throws CCE.

Это приведет к следующему в ClassCastException во время выполнения: "ConcreteObject не может быть добавлено к OtherConcreteObject" - это довольно плохо, потому что, поскольку код стоит выше, он должен преуспеть.

По этой причине вы должны попытаться избежать этого метода.

Ответ 2

Это незаконно по причинам, наилучшим образом описанным в Учебник Java Generics

Взяв пример оттуда и применив его к вашему делу:

List<ConcreteObject> concreteObjects = new ArrayList<ConcreteObject>();
List<IObject> objects = concreteObjects; // assume it legal
objects.add(new IObject() { // anonymous or some other (incompatible with ConcreteObject) implementation
});
ConcreteObject co = concreteObjects.get(0); // Profit! er... I mean error

Ответ 3

Чтобы поддерживать ковариацию так, как вы ожидали, Java нуждается в "восстановленных" дженериках. Оверенный List<ConcreteObject> знал бы, что его элементы должны быть экземплярами ConcreteObject. Поэтому, если вызывающий объект со ссылкой на этот список, объявленный как List<IObject>, попытался добавить AnAlternateImplObject, операция завершилась неудачно во время выполнения с исключением — так же, как аналогичный случай с массивами сегодня бросает ArrayStoreException.

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

Тем временем решение, которое вы использовали, является обычным способом обработки этого случая.

Ответ 4

Просто для интереса, если вы хотите действительно педантичную и строгую обработку типов на языке, несколько похожем на Java, вы вряд ли сможете лучше, чем Scala. Барочестически сложные определения типов - вот что такое Scala. Профессор Одерский, автор Scala, является тем же парнем, который изначально создавал дженерики (робко) на Java.