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