Использование общих типов подстановочных знаков в возвращаемых параметрах в Java обычно не рекомендуется. Например, «Эффективная Java», пункт 28 гласит:
Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.
Properly used, wildcard types are nearly invisible to users of a class. They cause methods to accept the parameters they should accept and reject those they should reject. If the user of a class has to think about wildcard types, there is probably something wrong with the class’s API.
Однако в некоторых случаях это кажется наиболее оптимальным выбором, например, в приведенном ниже коде:
import java.util.*;
import java.lang.*;
import java.io.*;
class Container
{
private final Map<String, Type<?>> itemMap = new HashMap<>();
public <T> void addItem(String key, T t) {
itemMap.put(key, new Type<>(t));
}
public Collection<Type<?>> getAllItems() {
return itemMap.values();
}
public static class Type<T> {
private final T value;
public Type(T value) {
this.value = value;
}
@Override
public String toString() {
return "Type(" + value + ")";
}
}
public static void main (String[] args)
{
Container c = new Container();
c.addItem("double", 1.0);
c.addItem("string", "abc");
System.out.println(c.getAllItems());
}
}
Пример, конечно, упрощен, давайте просто предположим, что существует реальная причина, по которой Type<T>
является общим.
Метод, который возвращает коллекцию всех элементов (getAllItems()
), требуется по дизайну. Элементы, хранящиеся в itemMap
, могут иметь разный тип, поэтому параметр типа T нельзя переместить в объявление класса Container. По той же причине мы не можем вернуть Collection<Type<T>>
, так как это означало бы, что все элементы имеют один и тот же тип T, а на самом деле это не так. Согласно «Эффективной Java», с этим методом или с этим API-интерфейсом что-то не так, но, на мой взгляд, ни то, ни другое не соответствует действительности.
Я знаю, что подобные вопросы задавались здесь раньше (например, Общие типы подстановочных знаков не должны использоваться в возвращаемых параметрах), но они либо сосредоточены на конкретном примере, который немного отличается от моего, либо имеют ответы, вращающиеся вокруг того факта, что нет смысла возвращать коллекции формы Collection<? extends ...>
когда вместо этого можно использовать параметр ограниченного типа. Это не относится к приведенному выше варианту использования (и меня не интересуют другие, потому что я вижу, как их можно решить).
Вопрос в следующем: если Effective Java верна (как и другие источники, такие как правила SonarQube и различные статьи в Интернете), какова альтернатива возврату Collection<Type<?>>
в приведенном выше коде или что не так с дизайном класса Container ?
ОБНОВИТЬ:
Слегка измененный/расширенный пример для решения некоторых вопросов, поднятых в комментариях (это ближе к реальному коду, с которым я работаю):
import java.util.*;
import java.lang.*;
import java.io.*;
class Type<T> {
private T value;
private Class<T> clazz;
public Type(T value, Class<T> clazz) {
this.value = value;
this.clazz = clazz;
}
T getValue() {
return value;
}
void setValue(T value) {
this.value = value;
}
Class<T> getItemClass() {
return clazz;
}
}
abstract class BaseContainer
{
protected abstract Collection<Type<?>> getItems();
// ... other methods
}
class Client {
public void processItems(BaseContainer container) {
Collection<Type<?>> items = container.getItems();
for (Type<?> item: items) {
processItem(item);
}
}
public <T> void processItem(Type<T> item) {
T currentValue = item.getValue();
Class<T> clazz = item.getItemClass();
T newValue = null;
// ... calculate new value
item.setValue(newValue);
}
}
Если возвращаемый тип BaseContainer.getItems()
объявлен как Collection<Type<Object>>
, мы предоставляем лазейку для «мошеннических» клиентов, чтобы поместить значение недопустимого типа в экземпляр Type. По той же причине List<Object>
не то же самое, что List<Integer>
- легко увидеть, как возврат первого вместо последнего позволит любому поместить как String в список целых чисел (List<Integer>
является однако List<? extends Object>
).
Следуя подходу, предложенному @isi, мы могли бы заставить Type реализовать некоторый интерфейс (не универсальный) и вместо этого использовать его в возвращаемом типе getItems
. Я вижу, как это может работать. Но в реальном коде это означало бы совершенно новый интерфейс, имитирующий большинство методов универсального типа без параметра типа, что кажется несколько неуклюжим. Также похоже, что интерфейс Type<T>
с T = Object по существу записывается, что граничит с дублированием кода. В некотором смысле я вижу Type<?>
как удобное сокращение для того, чтобы избежать этой работы и дополнительного интерфейса.
Container
будут использовать возвращаемое значениеgetAllItems
? Текущий пример обеспечивает переопределениеtoString
дляType
. Кроме того, в реальных условиях должны ли вызывающие объекты возвращаться к конкретному типу каждого элемента? Если да, ожидаете ли вы, что это будет сделано с помощью проверки типов во время выполнения и понижения? В текущем примере мне трудно понять, почему вы не вернетеCollection<Type<Object>>
. - person Chris Nauroth   schedule 06.01.2016