15

I would like to create a map that uses a class as a key to return an instance of that class. Something like:

<T> Map< Class<T>, T > instanceMap = new HashMap< Class<T>, T > ();
instanceMap.put( Boolean.class, Boolean.TRUE );
instanceMap.put( String.class, "asdf" );   
instanceMap.put( Integer.class, 11 );

Boolean b = instanceMap.get( Boolean.class );
Integer i = instanceMap.get( Integer.class );
String s  = instanceMap.get( String.class  );

Is this possible? I have a feeling that no it is not because I cannot indicate that T is meant to be a generic type rather than a class named "T". It is feels somehow like "higher-order generics".

EDIT: I know I could try to extend Map and implement my own wrapper etc, but I am specifically asking about doing this just using using Java's generic support. I am more interested in the idea rather than this particular case.

EDIT 2: As pointed out below, this is a duplicate (or more specifically a subcase) of the question: Java map with values limited by key's type parameter . Had I been able to find wording as eloquent as that, I would have likely found that answer and not posted this.

Community
  • 1
  • 1
Sled
  • 18,541
  • 27
  • 119
  • 168
  • You could store only one value per class (key)? – home Jul 22 '11 at 19:32
  • @home Yes, this would be used a way of accessing DAOs where you only want one dao per dao-type. – Sled Jul 22 '11 at 19:37
  • JsonObject might be an option. You can put whatever you like as the value of the jsonobject. What you need is package org.json.JSONObject. However, you may not be able to use foreach with JSONObject, but some alternative methods are there https://stackoverflow.com/questions/9151619/java-iterate-over-jsonobject – Cong Zhao Oct 22 '13 at 23:17
  • If you can stick whatever you want then it's not going to be typesafe. I was specifically looking to tie the key's type to the value's type. What you are suggesting seems as safe as saying `Map`. – Sled Oct 23 '13 at 12:21

5 Answers5

15

You mean something like this ?

public class Favorites {
  private Map<Class<?>, Object> favorites =
    new HashMap<Class<?>, Object>();

  public <T> void setFavorite(Class<T> klass, T thing) {
    favorites.put(klass, thing);
  }

  public <T> T getFavorite(Class<T> klass) {
    return klass.cast(favorites.get(klass));
  }

  public static void main(String[] args) {
    Favorites f = new Favorites();
    f.setFavorite(String.class, "Java");
    f.setFavorite(Integer.class, 0xcafebabe);
    String s = f.getFavorite(String.class);
    int i = f.getFavorite(Integer.class);
  }
}

see as reference: Java map with values limited by key's type parameter

Community
  • 1
  • 1
fyr
  • 20,227
  • 7
  • 37
  • 53
  • I knew this could be done with a wrapper, I was asking about using purely generics, but I'm assuming you answered before my edit. – Sled Jul 22 '11 at 19:39
  • To clarify my earlier comment, I was more interested in the limitations of the generic type system and cooked up the illustrative example to help frame the question. That is why I have not accepted your much upvoted and otherwise reasonable answer (ie. it answers the particular rather than general after which I was going) – Sled Jan 16 '13 at 15:13
3

As I understand you, you're saying that after you create this map, you want to populate it with something like ...

f.put(String.class, "Hello");
f.put(Integer.class, new Integer(42));
f.put(x.getClass(), x);

etc. Right?

In that case, I think the answer is no, you cannot do that with generics. Generics say that for a given instance of the class -- the map in this case -- you are specifying the types that are applicable. So if you say new HashMap<String,Integer>;, you are saying that all operations against this map will use a key that is a string and a value that is an integer. But that's not what you want to do in this case. You want to be able to put any sort of object into the class, and then constrain the acceptable types for the key based on the type of the object. That's not how generics work. They're not a relationship between each other, they're a constant for any given instance.

You could, of course, create such a map as new HashMap<Class,Object>;. This wouldn't force the class to be the class of the corresponding object, but it would allow you to enter such values.

Besides that, I think you'd need a wrapper. Should I point out that the wrapper's put would only need one parameter, as it could presumably determine the class of the parameter by doing getClass() on it, there'd be no need to tell it?

Sled
  • 18,541
  • 27
  • 119
  • 168
Jay
  • 26,876
  • 10
  • 61
  • 112
1

In your example, T would have to be different for each key/value, whereas with generics, T must be the same for each key/value. So no, this is not possible using generics. (but of course the implementation is certainly possbile with casting and/or not using generics)

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • I suspected as much, I just wanted to make sure that I wasn't missing something. But `with generics, T must be the same for each key/value` sounds like a limitation inherited from C++ rather than an limitation that Java generics needs to have; after all, in my example it is still statically verifiable. – Sled Jul 22 '11 at 19:35
0

The point of Map<T, U> is to map T's to U's. T and U can be any class (assuming no wildcards), but once they are determined, they can't change. In your case, you'd like a mapping that returned a different class depending on what key you used, which is obviously different.

The usual way to go about this is just make U Object, since you can go ahead and cast it to whatever you need. But of course this breaks the type-safety that generics are supposed to provide.

In short, I don't think this would be possible without writing a wrapper of some kind.

dlev
  • 48,024
  • 5
  • 125
  • 132
0

T must be set to some specific type when an instance is created, so you wouldn't be able to add different types to the map using the put method. Generic methods, on the other hand, have their type arguments set when they are called, allowing you to do what you want.

Guava has a ClassToInstanceMap type that uses generic methods to handle safely putting instances in the map and getting them out.

ColinD
  • 108,630
  • 30
  • 201
  • 202