Generic map with wildcard - java

I need to store one instance of objects for some classes.
I'd like to create a generic map like this:
<T> Map<Class<T>, T> objects;
But it's not possible and
Map<Class<?>, ?> objects;
Is not what I want because I would like to avoid casts:
MyClass c = (MyClass)objects.get(MyClass.class);
Also, I'd like Java to forbid me to do this:
objects.put(MyClass.class, new MyClass2());
One solution is to create a custom map so I would have a generic parameter <T>, but is there a solution without creating a new class?

The standard Map interface is not suitable for such scenario. However you can implement your own class which parameterizes get and put methods delegating them to the private Map field:
public class ClassObjectMap {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> clazz, T value) {
assert clazz.isInstance(value);
map.put(clazz, value);
}
public <T> T get(Class<T> clazz) {
return clazz.cast(map.get(clazz));
}
}
Adding other methods like remove() or size() is not very hard as well.

If you can, instead of using the map directly, put in these methods to access the map always. That way you put the casting into the method and you dont see it anywhere else.
private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
private <T> T getMapValue(Class<T> key){
return (T) map.get(key);
}
private <T> void putMapValue(Class<T> key, T value){
map.put(key, value);
}
And also it will forbid you from doing
putMapValue(MyClass.class, new MyClass2());

Related

Pass a generic enum with custom implemented methods as a parameter

I'd like to map the Label -> Value of a generic enum instance. This is feasible with this code:
public static <T extends Enum<T>> List<Map<String, String>> mapEnumValues(T[] myEnumValues) {
List<Map<String, String>> list = new ArrayList<>();
for (T myEnumValue : myEnumValues) {
Map<String, String> map = new HashMap<>();
map.put("label", myEnumValue.name());
map.put("value", myEnumValue.toString());
list.add(map);
}
return list;
}
But now I want to use another key for the map, which is for instance a method getLabel() implemented by every one of my enums. For example, given:
public interface Labelizable{
public String getLabel();
}
static public enum Options implements Labelizable {
option01("Option 01", 1), option02("Option 02", 2)
... constructor, getLabel(), ....
So I'd like to replace that row of code with:
map.put("label", myEnumValue.getLabel()); // instead of .name()
In order to do that I should declare something on the signature thst forces the parameter to be an enum that implements the getLabel() method. I tried, for example,
public static <T extends Enum<T implements Labelizable>>
and some other syntax like this with no success. Is there any way to achieve this behaviour, or the fact that enums can't inherit prevent such a method?
The syntax is:
public static <T extends Enum<T> & Labelizable>
List<Map<String, String>> mapEnumValues(T[] myEnumValues) {
You put the superclass that your generic parameter has to inherit first, then the interfaces that it has to implement, all separated by &.

Change Map of Map to generic type

I have a method which takes a Map which has Key as String and value as an object of type T which is a serializable object.
public <T extends Serializable> void set(String key, Map<String, T> fields) {
......
.......
}
When I'm trying to pass map of map, it's giving an error for wrong argument type. I want this method to take a generic object and don't want to change it to set(String key, Map<String, Map<String, String>> fields)
Map<String, String> fieldmapL1 = new HashMap<>();
fieldmapL1.put("ABC", "XYZ");
Map<String, Map<String, String>> fieldmapL2 = new HashMap<>();
fieldmapL2.put("Key", fieldmapL1);
instance.set("key", fieldmapL2);
You need to indicate the type, you're using:
instance.<Map<String, String>>set("key", fieldmapL2);
Then type T is resolved to String. Also this will work only if instance is properly instantiated (you can set T to String on instantiation, then you don't need the above line or you can omit explicit types and use my solution).
You issue is that you type is not matching you requirements. As explained by #Pankaj in others comments of this topic, Map is not serializable so your definition of fieldmap2 does not meet the criteria for T. Updating instantiations of maps to properly use HashMap which implements serializable do the trick (aka no more warning in intelliJ)
public static class Test{
public <T extends Serializable> void set(String key, Map<String, T> fields) {
}
}
public static void main(String[] args) {
HashMap<String, String> fieldMapL1 = new HashMap<>();
fieldMapL1.put("ABC", "XYZ");
Map<String, HashMap<String, String>> fieldmap2 = new HashMap<>();
fieldmap2.put("key", fieldMapL1);
Test test = new Test();
test.set("key", fieldmap2);
}

How to get a type with multiple generic types to match the type parameters?

For example, say I want a Map<Class<?>, List<?>>, so I can put in a class and get out a list of that type - is there something I can replace the question marks with to make that happen?
You can do the trick if you delegate type check to the method:
private class TypedMap {
private Map<Class<?>, List<?>> map = new HashMap<>();
public <T> void put(Class<T> key, List<T> value) {
map.put(key, value);
}
#SupressWarnings("unchecked")
public <T> List<T> get(Class<T> clazz) {
return (List<T>) map.get(clazz);
}
}
Wildcard ? in map declaration does not ensure that key Class<?> and value List<?> would be of the same type. Method put() ensures that. You can not declare map as Map<Class<T>, List<T>> if your class is not generic - that's why you have to use a method.
Get method is unchecked. The cast is safe if entries are added with put() method. (There's still a problem with raw types - but this is unavoidable)
You can add more methods to TypedMap class, but remember about this restrictions.
public static void main(String[] args) {
TypedMap map = new TypedMap();
List<Cat> cats = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
adder.put(Cat.class, cats);
adder.put(Dog.class, dogs);
adder.put(Cat.class, dogs); // compilation error
}
Java doesn't completely enforce this, but one way to at least get a warning about it, is by using encapsulation:
public class MyClass {
// private, private, private
private Map<Class<?>, List<?>> myMap;
public <T> void put(Class<T> clazz, List<T> list) { // both must have the same T.
myMap.put(clazz, list);
}
...
}
You can still break this by doing something like:
MyClass mc = new MyClass();
Class c = Main.class;
List<String> l = new ArrayList<String>();
mc.put(c, l);
But you'll at least get a warning about unchecked conversion of c to Class<String>. And the unchecked invocation of MyClass::put
Not sure what you're trying to accomplish here, but Map<Class<T>, List<T>> would be the closest thing. The T is one single class type, though, so you can't put multiple classes into one Map.
You'll get a ClassCastException if you try to put objects of different classes into the same Map.

TreeMap with Classes as Key

I am trying to program a kind of registry for objects of different classes.
I have the following:
public interface DbObject{
void setId(long id);
Long getId();
}
A prototypic class implementing this interface would be the following:
public class BasicDbObject implements DbObject{
private long id=null;
void setId(long id){
this.id=id;
}
Long getId(){
return id;
}
}
I want to build various different Implementations of this Interface.
And I want to be able to have a Map object, that maps from each implementing class to a Map of instances.
Something like this:
Map <Class<C implements DbObject> , Map<Long, C>> registry = new TreeMap/HashMap/SomeOtherKindOfMap (...)
I know I could do something like
Map <String,Map<Long,DbObjects>> registry = new ...
But this way I would have to write some more code for determining names, comparing classes and so on. Is there an easier way to accomplish this?
So what I want to know: is it possible to have class objects as keys in a tree map?
What would be the syntax to declare a map object, that maps from implementing classes C to a map objects each mapping from a long object (the id) to instances of C?
I want to be able to do requests like the following:
BasicObject bo = registry.get(BasicObject.class).get(42);
assuing id did
BasicObject bo=new BasicObject(...);
innerMap = new SomeMap<Long,BasicObject>();
innerMap.put(42,bo);
registry.put(BasicObject.class,innerMap);
before.
Please tell me, if this still is not clear, I have difficulties to explain, since english is not my mother tongue.
Thank you in advance.
Edit:
It turns out, i can do something very close to what I want, when defining a generic class around the map:
public class ObjectRegistry <T extends DbObject>{
private HashMap<Class<T>, TreeMap<Long,T>> registry=null;
ObjectRegistry(){
registry=new HashMap<Class<T>, TreeMap<Long,T>>();
}
public void register(T dbObject){
TreeMap<Long, T> map = registry.get(dbObject.getClass());
if (map==null){
map=new TreeMap<Long,T>();
registry.put((Class<T>) dbObject.getClass(),map);
}
map.put(dbObject.getId(),dbObject);
}
public <T extends DbObject>T get(Class<T> objectClass,long id){
TreeMap<Long, T> map = (TreeMap<Long, T>) registry.get(objectClass);
if (map != null){
return map.get(id);
}
return null;
}
public TreeMap<Long,T> getAll(Class<T> dbObjectClass) {
return registry.get(dbObjectClass);
}
}
I use a TreeMap for the inner mappings since I want to easily return Class instances sorted by id.
So the refined question is:
Is there a way to do this, without the <T extends DbObject> clause in the Class head?
Edit 2:
Thinking through it again, it turns out that John's answer is exactly the solution to this.
Here is my final code:
HashMap<Class<? extends DbObject>, TreeMap<Long, ? extends DbObject>> registry = null;
public <T extends DbObject> T get(Class<T> clazz, long id) {
TreeMap<Long, T> map = (TreeMap<Long, T>) registry.get(clazz);
if (map != null) {
return map.get(id);
}
return null;
}
public <T extends DbObject> void register(T dbObject) {
TreeMap<Long, T> map = (TreeMap<Long, T>) registry.get(dbObject.getClass());
if (map == null) {
map = new TreeMap<Long, T>();
registry.put((Class<T>) dbObject.getClass(), map);
}
map.put(dbObject.getId(), dbObject);
}
public <T extends DbObject> TreeMap<Long, T> getAll(Class<T> dbObjectClass) {
return (TreeMap<Long, T>) registry.get(dbObjectClass);
}
It does not need the <T extends DbObject> clause in the Class head.
So what I want to know: is it possible to have class objects as keys in a tree map?
TreeMap depends on there being a total order over the key space, as established by the key type having a natural order (by implementing Comparable) or by a separate Comparator object that you provide. Classes do not have a natural order. It is conceivable that you could write a suitable Comparator, but that seems very contrived to me.
But why do you specifically need a TreeMap? You didn't describe any requirement that would not be at least as well addressed by any other kind of Map. In particular, I almost always find HashMap to be a better choice, and I don't see any reason why it would be unsuitable in this one. It can certainly have objects of type Class as keys.
Moreover, if indeed you don't need any particular implementation, then you are best off declaring the type simply as a Map. That way you can actually provide any Map implementation, and even change which one you do provide if you ever discover a reason to do so.
What would be the syntax to declare a map object, that maps from implementing classes C to a map objects each mapping from a long object (the id) to instances of C?
You ask that the constraints on the type of each value be dependent on the type of the associated key, but there is no way to declare a type that enforces such a relationship. Whether a particular key or a particular value is appropriate for the Map is a function of the type of the map alone, not of each others' type.
You can write generic methods around access to your map that provide the appearance of what you want, but the data retrieval methods will need to cast. For example:
Map<Class<? extends DbObject>, Map<Long, ? extends DbObject>> registry = /*...*/;
<T extends DbObject> Map<Long, T> getRegistryMap(Class<T> clazz) {
return (Map<Long, T>) registry.get(clazz);
}
<T extends DbObject> T get(Class<T> clazz, Long id) {
Map<Long, T> map = getRegistryMap(clazz);
return (map == null) ? null : map.get(id);
}
<T extends DbObject> T put(Class<T> clazz, Long id, T obj) {
Map<Long, T> map = getRegistryMap(clazz);
if (map == null) {
map = new HashMap<>();
registry.put(clazz, map);
}
return map.put(id, obj);
}
Updated to add:
So the refined question is: Is there a way to do this, without the <T extends DbObject> clause in the Class head?
Yes, what I already wrote. Just slap a plain class declaration around it. You do not need a generic class to have generic methods. In fact, the two are orthogonal. Regular methods of a generic class can use that class's type parameters. That does not make them generic methods. A method is generic if it declares its own type parameter(s), as mine above do. Your get() method also does that, and it is important to understand that the type parameter <T> you declare explicitly in the method signature shadows the class's type parameter of the same name: it is a different T.

Is my code type safe?

I am pretty sure this is type safe, but just wanted to check as Eclipse is asking me to put a #SuppressWarnings("unchecked") annotation.
Map<String, IFace> faces;
public <T extends IFace> T getFace(String key)
{
return (T) faces.get(key);
}
It is not type safe. You are upcasting here so if you cast to an incompatible derived class you will come across an error at some point.
For example if A_Face and B_Face both extend IFace. You might at some point be casting a B_Face as an A_Face which is not type safe.
Look at the extreme case. Lets say IFace is acutally Object, your code then looks like this:
static Map<String, Object> myMap = new HashMap<>();
public static void main(String[] args) throws Exception {
myMap.put("ONE", 1);
myMap.put("TWO", "TWO");
myMap.put("THREE", new Date());
final Calendar calendar1 = getThing("ONE");
final Calendar calendar2 = getThing("TWO");
final Calendar calendar3 = getThing("THREE");
}
public static <T> T getThing(String key) {
return (T) myMap.get(key);
}
So you are putting at class than extends Object into your Map (so any class).
But, when you call getThing you are doing an implicit cast to your desired type. It should be fairly obvious that I can call getThing with any class also and it will blindly attempt to cast to it.
In the above example I am putting some things into my Map and then attempting to retrieve them all as Calendars.
A classic way to handle this is with a "typesafe heterogeneous container":
Map<Class<?>, IFace> faces;
public <T extends IFace> T getFace(Class<T> key) {
return t.cast(faces.get(key));
}
You use the class of the interface as a key, rather than a string, and then you can use the class passed as a key to safely cast the return value to the right type.

Categories

Resources