corsiKa
corsiKa

Reputation: 82589

enforce generic types in maps (in relation to key/value generics)

Let's say I want to have some kind of a cache that did something like

Map<Class<T extends SomeBaseClass>, List<T>> cache = new ...

When I try to do this is throws compile errors. I don't want to make them generics of the class I'm working with, because they don't belong there. I also will have many different kinds of T there. But I also don't want to use <? extends SomeBaseClass> because you don't have a compile time guarantee that the list going in is of the same type the class that maps to it is...

What are my options here? These terms are so overused here and on google I can't seem to search for them :-(

Upvotes: 2

Views: 680

Answers (4)

Aaron Digulla
Aaron Digulla

Reputation: 328724

I've implemented this in TypedMap: http://blog.pdark.de/2010/05/28/type-safe-object-map/

Here is some demo code:

TypedMap map = new TypedMap();

String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 ); // Look Ma, no cast!
assertEquals( expected, value );

List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 ); // Even with generics
assertEquals( list, valueList );

The magic is in the key:

final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );

Upvotes: 0

PaulMurrayCbr
PaulMurrayCbr

Reputation: 1260

so you want a map of

String.class --> list of strings
Int.class --> List of Integers

Ok, no prob. But: in a particular instance of your map:

Map<K, V> x = …; 

What particular, specific class is K? You want it to be a whole buch of different ones at once, which aint going to happen.

You are going to have to do some casting.

Upvotes: 0

Luis Casillas
Luis Casillas

Reputation: 30237

I also will have many different kinds of T there.

If I understand you correctly, what you want is to use generics to somehow guarantee that each entry in the map will associate a Class object with some object of the class in question.

Generics cannot do that. Let's quote your code snippet:

Map<Class<T extends SomeBaseClass>, List<T>> cache = new ...

It looks like you want each entry in the Map to associate a Class object with an instance of that class. Well, generics can't do anything to help you there, because the type parameter T in Map<Class<T>,T> will have one and only one type substituted for it when the type is specialized to a particular T. This means a specialized Map<Class<Foo>,Foo> version of the class will have a method with this signature:

public Set<Map.Entry<Class<Foo>,Foo>> entries();

And the Map.Entry<Class<Foo>,Foo>> references you get out of the Set's iterator method would have these methods:

public Class<Foo> getKey();
public Foo getValue();

In this case, well, there is only one value for Class<Foo>, which is the class object for the Foo class. But if we go with your example and use Class<T extends SomeBaseClass> doesn't really add much; getKey() will get you some Class object whose newInstance method produces something that can be cast to Foo, but there is no guarantee that getValue() will get you a value of the same runtime class, or even of a class that can be cast to the same class as the one returned by getKey().

EDIT: You probably want something like this (untested, not even tried to compile it):

public class InstanceTable {
    private final Map<Class,Object> table =
        new ConcurrentHashMap<Class,Object>();

    public <T> void put(Class<T> klass, T value)
        throws ClassCastException {
        table.put(klass.cast(value));
    }

    public <T> T get(Class<T> klass) {
        return klass.cast(table.get(klass))
    }

}

This offers compile-time type-safety only in the case where you statically name the classes you're using in the source code, using the Foo.class syntax:

InstanceTable t = new InstanceTable();
t.put(String.class, "foo");          // compiles
t.put(Integer.class, "bar")          // doesn't compile
String foo = t.get(String.class);    // compiles
Integer one = new Integer(1);
Integer bar = t.get(one.getClass()); // doesn't compile

But understand that beyond this, it isn't using the type system to enforce the invariant you want at compilation time —it's still checking it at runtime (the calls to Class.cast()).

Upvotes: 3

Louis Wasserman
Louis Wasserman

Reputation: 198211

There are no options. This isn't avoidable, although you can wrap it up in your own API that hides the type casts...but there's no way to guarantee this sort of thing in Java at compile time.

That said, you can create an API that's typesafe for use on the outside, and suppresses warnings on the inside. (@ysdx suggested looking at Guava's ClassToInstanceMap, which is indeed a good example.) But you're not going to get something that's internally type-safe: there are going to be casts on the inside.

Upvotes: 3

Related Questions