Using generic patterns in return parameters

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(); // ... 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 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.

+5
source share
3 answers

If you are really going to store objects of absolutely any type, use Object. There is no need to add sugar type checks when you specifically do not want types checked.

+2
source

I think the Type class silently provides two interfaces * . One for users who care about the generic type T , and another for users who aren't. I will try to make this a little clearer with some code:

 interface TypeWithoutT { Object getValueObj(); } // exposes untyped api class Type<T> implements TypeWithoutT { // exposes several apis private final T value; // exposes typed part of the api (can be on a separate interface) public T getValue() { return value; } public Type(T value) { this.value = value; } // overrides commonly available apis @Override public String toString() { return "Type(" + value + ")"; } // exposes untyped part of the api and Object api public Object getValueObj() { return value; } } 

The typed interface of the Type<T> class can expose more specific types of operations than the opponent, the non-typed TypeWithoutT interface. The toString method is available for each instance, so it is part of the public interface for Object instances. There will probably be other useful operations besides those provided by Object , which should be exposed to the user, which is the untyped part of the api.

Rephrase

Now the question is: what can a user do with a bunch of mixed instances of Type ? What do they expect from each instance in a mixed collection of Type instances?

Is Object sufficient?

They probably expect nothing more than the same set of operations that are common to all instances of Type<T> for any of the possible variants of T Like the toString method of Object . If all API users should be aware of the use of collections of Type instances, it is definitely sufficient to use Collection<Type<Object>> as the return type for the getAllItems method.

Or no?

If API users expect much more than the interface provided by Object , the above approach can be used to return a more specialized collection. In this case, the return type of the getAllItems method can be changed to Collection<TypeWithoutT> .

That way, you can explicitly provide a useful API for both users — the one who cares about the type and the one who doesn't.

but

I would adhere to best practices and not return a wildcard type, except that I have a reason for this. One of these decisions should make - I think.


interface * : I mean the interface in a general sense, and not explicitly java interfaces. So something with a contract and a set of operations.

+2
source

However, in some cases, this is apparently the best choice, for example, in the code below:

But this is NOT a "template type". The “wildcard type” will look like Foo<?> Or Foo<? extends Something> Foo<? extends Something> or Foo<? super Something> Foo<? super Something> where is the type argument (possibly limited) ? . You can assign Foo arguments of another type to a variable of type Foo<?> . There is a kind of polymorphism that gives a type of wildcard.

You are talking about type Collection<Type<?>> . In this case, an argument of type Type<?> , Which is a specific type. This is completely different. Is there somewhere ? but it is at a deeper level. Something like Collection<Type<?>> , you cannot assign Collection type arguments of type to a variable of type Collection<Type<?>> . The type argument is fixed as Type<?> ; nothing is compatible.

+1
source

Source: https://habr.com/ru/post/1240024/


All Articles