简体   繁体   中英

In Java is it possible to create a type-safe Map of classes to instances of their class?

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.

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

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?

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)

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 How to iterate over a JSONObject?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM