Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding Returning Wildcard Types

I have a class with a collection of Wildcard Types that is a singleton, something like:

public ObliviousClass{

    private static final ObliviousClass INSTANCE = new ObliviousClass();

    private Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> type){
        map.put(type);
    }

    // returns the singleton
    public static ObliviousClass getInstance(){
        return INSTANCE;
    }

}

I'd like to be able to add different Parameterized types to this collection in client code:

void clientMethod(){
    ObliviousClass oc = ObliviousClass.getInstance();

    Type<Integer> intType = ...
    Type<String> stringType = ...

    oc.putType(new Key(0), intType);
    oc.putType(new Key(1), stringType);
}

Up to this point, as I understand it, everything is ok. But a client also needs to be able to get a Type<?> provided the Key. So a method something like the following would be added to ObliviousClass:

public Type<?> getType(Key key){
    return map.get(key);
}

But in my handy copy of Effective Java, I read:

Do not use wildcard types as return types.

I understand the issue, as the client would have to cast the returned Type<?>. But I really do not want to make ObliviousClass a generic type, ObliviousClass<T>, because then my client code above would not work...

Is there a better design for what I am trying to do? -My current solution is to provide a static method for the client; something along the lines of:

public static <T> void getType(ObliviousClass instance, Key key, Type<T> dest){
    dest = (Type<T>)instance.getType(key);
}

I searched around, but wasn't able to find an answer that totally cleared my confusion.

like image 890
sethro Avatar asked May 08 '12 22:05

sethro


People also ask

Can return type be wildcard?

Do not use wildcard types as return types.

Do not use bounded wildcard types as return types?

It is highly recommended not to use wildcard types as return types. Because the type inference rules are fairly complex it is unlikely the user of that API will know how to use it correctly. Let's take the example of method returning a "List<? extends Animal>".

Which wildcard is known as unbounded wildcard?

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach: If you are writing a method that can be implemented using functionality provided in the Object class.


2 Answers

Here's a type-safe way to store multiple instances of a given type in a map. The key is that you need to provide a Class instance when retrieving values in order to perform runtime type-checking, because static type information has been erased.

class ObliviousClass {

  private final Map<Key, Object> map = new HashMap<Key, Object>();

  public Object put(Key key, Object value)
  {
    return map.put(key, value);
  }

  public <T> T get(Key key, Class<? extends T> type)
  {
    return type.cast(map.get(key)); 
  }

}

Usage would look like this:

oc.put(k1, 42);
oc.put(k2, "Hello!");
...
Integer i = oc.get(k1, Integer.class);
String s = oc.get(k2, String.class);
Integer x = oc.get(k2, Integer.class); /* Throws ClassCastException */
like image 72
erickson Avatar answered Oct 16 '22 19:10

erickson


Simply type your class:

public ObliviousClass <T> {

    private Map<Key, Type<T>> map = new HashMap<Key, Type<T>>();

    public void putType(Key key, Type<T> type){
        map.put(type);
    }

    public Type<T> getType(Key key){
        map.get(key);
    }
}

FYI, at this point you have the delegation pattern in play.

Your example client code would need to declare two instances of ObliviousClass: ObliviousClass<String> and ObliviousClass<Integer>.

Edit:

If you must have a mixed bag of Types, you can impose a type on your method, but you'll get a compiler warning for an unsafe cast:

public class ObliviousClass {

    private final Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> value) {
       map.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType1(Key key, Class<T> typeClass) {
       return (Type<T>)map.get(key); 
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType2(Key key) {
        return (Type<T>) map.get(key);
    }
}

Clients can type the calls to these methods like this:

Type<Integer> x = obliviousClass.getType1(key, Integer.class);
Type<Integer> y = obliviousClass.<Integer>getType2(key);

Take your pick as to which one you prefer and use that.

like image 41
Bohemian Avatar answered Oct 16 '22 18:10

Bohemian