Using common wildcard types in return parameters in Java is usually not recommended. For example, effective Java, paragraph 28:
Do not use lookup types as return types. Instead of providing additional flexibility for your users, this will force them to use wildcard types in client code.
Properly used wildcard types are almost invisible to class users. They invoke methods for accepting the parameters that they must accept, and reject those that they must reject. If a class user needs to think about wildcard types, then something may be wrong with the class APIs.
However, in some cases, it seems to be the most optimal choice, for example, in the code below:
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()); } }
The example, of course, is simplified, let's say that there is a true reason that Type<T> is common.
A method that returns a collection of all elements ( getAllItems() ) is required by design. Elements stored in itemMap may have a different type - therefore, a parameter of type T cannot be moved until the container class is declared. For the same reason, we cannot return Collection<Type<T>> , since this implies that all elements have the same type T, which they do not have. According to Effective Java, there is something wrong with this method or with this class API, none of which, in my opinion, is true.
I know that similar questions have been asked here before (for example, Generic lookup types should not be used in return parameters ), but they either focus on a specific example that is slightly different from mine, or we have answers that revolve around the fact that there isn’t sense to return collections of the form Collection<? extends ...> Collection<? extends ...> if you can use a parameter of a limited type instead. This does not apply to the above use case (and I am not interested in others because I see how they can be solved).
The question is, if effective Java is correct (and other sources, such as SonarQube rules and various articles on the Internet), what is an alternative to returning Collection<Type<?>> in the above code, or what is wrong with the design of the Container class?
UPDATE:
A slightly modified / extended example for solving some issues raised in the comments (this is closer to the real code I'm working with):
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();
If the return type of BaseContainer.getItems() declared as Collection<Type<Object>> , we provide a loophole for rogue clients to put the value of the invalid type in the Type instance. For the same reason that List<Object> does not match List<Integer> - it’s easy to see how returning the former, and not the last, would allow anyone to put as String in a list of integers ( List<Integer> however List<? extends Object> List<? extends Object> ).
Following the approach suggested by @isi, we could force Type to implement some interface (not a generic one) and use it instead of the return type getItems . I see how this can work. But in real code, this would mean a completely new interface that mimics most methods of a generic type without a type parameter that seems somewhat awkward. It also looks like, essentially, writing a Type<T> interface with T = Object, which borders on code duplication. In a sense, I see Type<?> As a convenient shorthand to avoid this work and an additional interface.