Using generic wildcard types in return parameters in Java is generally discouraged. For example Effective Java, Item 28 states:
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.
However in certain cases it appears to be the most optimal choice, such as in 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 is of course simplified, let's just assume there is a genuine reason for Type<T>
to be generic.
A method that returns collection of all items (getAllItems()
) is required by design. Items stored in itemMap
can have different type - therefore the type parameter T cannot be moved up to Container class declaration. For the same reason we cannot return Collection<Type<T>>
as that would imply all items have the same type T which they don't. According to Effective Java there is either something wrong with this method or with this class API, neither of which seem to be true in my opinion.
I know that similar questions have been asked here before (e.g. Generic wildcard types should not be used in return parameters) but they either focus on specific example, which is slightly different from mine, or have answers revolving around the fact that it doesn't make sense to return collections of form Collection<? extends ...>
when instead a bounded type parameter can be used. That does not apply to the above use case (and I'm not interested in others because I can see how they can be addressed).
The question is this - if Effective Java is right (and so are other sources such as SonarQube rules and various articles on the web), what is the alternative to returning Collection<Type<?>>
in the code above, or what is wrong with the design of class Container?
UPDATE:
A slightly modified/extended example to address some points 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();
// ... 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);
}
}
If BaseContainer.getItems()
return type is declared as Collection<Type<Object>>
we provide a loophole for "rogue" clients to put a value of invalid type into an instance of Type. It is for the same reason that List<Object>
is not the same as List<Integer>
- it is easy to see how returning former instead of latter would allow anybody to put as String into a List of integers (List<Integer>
is however List<? extends Object>
).
Following an approach suggested by @isi we could make Type implement some interface (non-generic) and use that in the return type of getItems
instead. I can see how that can work. But in the real code that would mean a whole new interface mimicking most methods of the generic type without type parameter, which feels somewhat clumsy. It also looks like one essentially writes down the interface of Type<T>
with T = Object, which is bordering on code duplication. In a way I see Type<?>
as a convenient shorthand for avoiding this work and extra interface.
If you truly intend to store objects of absolutely any type then use Object. There is no need to add type checking sugar when you specifically do not want types checked.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With