Есть ли у 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;
}

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

4 ответа

Решение

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"- что довольно плохо, потому что, поскольку код стоит выше, он должен преуспеть.

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

Это незаконно по причинам, лучше всего описанным в Обучающем руководстве по Java

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

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

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

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

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

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

Другие вопросы по тегам