Enforcing generics on a map - java

I'm trying to write a simple caching class. The cache is essentially a map from class to list.
I want to declare the map in a way to enforce that the list item class is the same as the class of the key. The following:
class Cache {
private static Map<Class<? extends CodedEntity>, List<? extends CodedEntity>> map;
}
Is not enough because it won't enforce (in its decleration) what I want.
The map needs to be static and there is no point in declaring the class as T because I don't want it to be limited to a single type. Is there a syntactic way to achieve this?

You map is private, why don't you enforce your need in accessor and mutator methods?
public <T extends CodedEntity> void addToCache(Class<T> key, List<T> values) {
map.put(key, values);
}
public <T extends CodedEntit> List<T> getFromCache(Class<T> key) {
return (List<T>) map.get(key);
}

Related

Java Generics and Map declaration

I am having a hard time declaring a Map using Generics. I'd like to declare a Map with the following properties:
The key is a Class object of any Type T derived from a particular Interface (IFoo)
The value of the Map is another Map whose key is an String and whose value is of the Type T
I thought I can do it like this:
public static Map<Class<T extends IFoo>, Map<String, T>> valueCache =
new HashMap<Class<T extends IFoo>, Map<String, T>>();
I get a syntax error on "extends"
If I replace the T Types with wildcards (?) like this it seems to be syntactically correct:
public static Map<Class<? extends Typ>, Map<Integer, ?>> valueCache=
new HashMap<Class<? extends Typ>, Map<Integer, ?>>();
But I don't think this is what I want since I want to be exactly the type of the Class object to be the value in the second map.
Any help appreciated.
A generic type parameter can only be declared on a class or method declaration.
If you don't care about the reference type of the IFoo that you get back you can do
static Map<Class<? extends IFoo>, Map<String, IFoo>> fooMap;
If you want to use the IFoo returned as its subclass type then you need to do some casting.
// abbreviated example
class FooMap {
private static Map<Class<? extends IFoo>, Map<String, IFoo>> map = ...;
static void put(String key, IFoo foo) {
map.get(foo.getClass()).put(key, foo);
}
static <F extends IFoo> F get(Class<F> cls, String key) {
return cls.cast(map.get(cls).get(key));
}
}
FooMap.put("foo", new Foo());
Foo foo = FooMap.get(Foo.class, "foo");
Move the extends into your class's generic definition:
public class ClassWithGeneric<T extends IFoo> {
Map<Class<T>, Map<String, T>> valueCache = new HashMap<Class<T>, Map<String, T>>();
}
It's not technically possible to do what you want, but you can simulate it using accessor methods with internal casting. For example:
private static Map<Class<?>, Map<String, ?>> valueCache = new HashMap<>();
public <T extends IFoo> Map<String, T> getMap(Class<T> key) {
return (Map<String, T>)value cache.get(key);
}
Try this,
public static Map<IFoo, Map<String, IFoo>> valueCache = new HashMap<IFoo, Map<String, IFoo>>();
By this way, you make use of the map for IFoo Type Classes.
since you Declaring a Map you should Specify all types it helps a lot and should be always Correct so it should be like T should be a type and not Generic since you declare it so java wants to know the type in the newer Versions of java you dont need to declare the second <> generics so i dont know the second type of your inner map so i used Object
public static Map<IFoo,Map<String,Object>valueChache=new Hashmap<>();
Both should be Correct
public static Map<IFoo,Map<String,Object>valueChache=new Hashmap<IFoo,Map<String,Object>();

Java generics: put() on Map<String,capture#3-of ? extends AbstractClass> is not applicable for the arguments (String, AbstractClass)

I'm trying to implement a sort of intern factory for multiple classes that extend from a common parent. Much of the logic is identical, but it can't really be inherited because the lookups need to be static. The desired syntax is something like:
Car c = AbstractClass.valueOf(Car.class, "Ford");
with Car having specific methods related to cars, but the instances are stored in a common cache. Here's what I have so far. My compile error is on the put in the constructor:
"The method put(String, capture#3-of ? extends AbstractClass) in the type Map is not applicable for the arguments (String, AbstractClass)"
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public abstract class AbstractClass {
private static Map<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>> map = new HashMap<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>>();
private static synchronized <T extends AbstractClass> Map<String, T> getNameMap(Class<T> clazz) {
LinkedHashMap<String, T> nameToEnum = (LinkedHashMap<String, T>) map.get(clazz);
if (nameToEnum == null) {
nameToEnum = new LinkedHashMap<String, T>();
map.put(clazz, nameToEnum);
}
return nameToEnum;
}
public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) {
return getNameMap(clazz).get(name);
}
public static <T extends AbstractClass> Collection<T> VALUES(Class<T> clazz) {
return getNameMap(clazz).values();
}
public static <T extends AbstractClass> Set<T> SORTED_VALUES(Class<T> clazz) {
return new TreeSet<T>(getNameMap(clazz).values());
}
AbstractClass(String name) {
AbstractClass.getNameMap(this.getClass()).put(name, this);
}
}
According to the javadoc for Object.getClass(), the returned type is a wildcard based compile-time type of the expression. Since the compiler only knows that this returns an AbstractClass instance, this.getClass() returns Class<? extends AbstractClass>.
This means your call to getNameMap in the constructor will return a Map<String, ? extends AbstractClass>. Which means that, while the returned Map has values of a specific (non-wildcard) type, that exact type isn't known at compile-time; the compiler only knows the Map's values are required to be either AbstractClass or something that inherits from AbstractClass. So the compiler can't safely add this as a value, since it isn't known at compile-time which subtype of AbstractClass this represents.
To use a simpler example: if a method returned Map<String, ? extends Number> then the compiler wouldn't know whether it was safe to add an Integer to the Map, because the Map's actual, non-wildcard type might be Map<String, Double>, Map<String, Short>, etc.
As for a solution: I don't think there is a way to have a Map use generics to match each individual key's type with its corresponding value's type. I would forget about using bounded types on the inner Maps' values, and use dynamic casting instead:
private static Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> map = new HashMap<>();
private static synchronized Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> getNameMap(Class<T> clazz) {
// same as before
}
public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) {
return clazz.cast(getNameMap(clazz).get(name));
}
If you just want to store anything that is an AbstractClass, just declare your map as
private static Map<Class<? extends AbstractClass>, LinkedHashMap<String, AbstractClass>> map =
new HashMap<Class<? extends AbstractClass>, LinkedHashMap<String, AbstractClass>>();
This would allow you to store any instance of AbstractClass or its subclasses in the inner map, against AbstractClass or one of its sub class.
Your problem can basically be boiled down to this:
Given a method with this signature:
public static <T> void foo(T x, Class<T> y);
and a variable of any reference type:
<any reference type> bar;
it is impossible to pass bar and bar.getClass() to this method:
foo(bar, bar.getClass()); // error
even though it is provable that there always exists some T for which it is correct (i.e. T = the actual runtime type of bar).
It is due to the special case in the language for the type of .getClass() that causes this problem.
I can think of two ways to solve this:
1) Cast the class object to be parameterized by the same type as the reference (even though this is technically not true):
AbstractClass(String name) {
AbstractClass.getNameMap((Class<AbstractClass>)this.getClass()).put(name, this);
}
2) Cast the object to the same type as the parameter of the class method. This will require a capture helper due to the wildcard in the class's type:
private static <T> void helper(Class<T> clazz, String name, Object obj) {
AbstractClass.getNameMap(clazz).put(name, (T)obj);
}
AbstractClass(String name) {
helper(this.getClass(), name, this);
}
(if you don't want that unchecked cast you can do AbstractClass.getNameMap(clazz).put(name, clazz.cast(obj));)

How to instance generic parameterized class

I just implement a MapBuilder to build map easy,
But when i try to get an instance of HashMap.class,I suddenly found that I can't use HashMap.class to get such an instance.
It's illegal!
So can anybody tell me why and how to solve this problem?
The MapBuilder is follow:
import java.util.Map;
public abstract class MapBuilder {
public static <K, V, T extends Map<K, V>> InnerMapBuilder<T, K, V> start(
Class<T> clazz) {
return new InnerMapBuilder<>(clazz);
}
public static class InnerMapBuilder<T extends Map<K, V>, K, V> {
private T target;
public InnerMapBuilder(Class<T> clazz) {
try {
target = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public InnerMapBuilder<T, K, V> put(K key, V val) {
target.put(key, val);
return this;
}
public T get() {
return target;
}
}
}
And the test code is below:
public static void main(String[] args) {
HashMap<String, String> v = start(HashMap<String,String>.class).put("a", "b").get();
System.out.println(v);
}
It's impossible to obtain a parameterized class type variable for a generic type, as Reimeus has said. So you have three choices.
First, you can live with the unchecked cast:
Class<? extends Map<String, Integer>> clazz =
(Class<? extends Map<String, Integer>>) HashMap.class;
Second, you can reify the parameters for a class by extending it (in this example, using an anonymous inner class):
Class<? extends Map<String, Integer>> clazz =
new HashMap<String, Integer>() {}.getClass();
Or third, and best, just take the Map instance instead of a class in start(). You're not saving the user any work by taking the Class rather than an instance of Map, and the first thing you do is create an instance of it.
By passing it in, the user can even tweak the settings of the map (e.g. for a HashMap, set the load factor, for TreeMap, specify the Comparator) so it's a better alternative anyway. If you need to, you can assert that it's empty when it's passed in.
If for some reason you really need a factory, don't use Class: it doesn't work well as a factory, because the only way you can customize the instance that Class creates is by subclassing the class and providing a new no-arg constructor. Just create an interface Factory<T> that has a method T create() and then accept a Factory<? extends Map<K, V>.
First since start takes a class you would have to pass it an unparameterized class such as HashMap.class. Second, as you're returning a generic type Map, you would have to make your local type match also, so to use:
Map<String, String> v = start(HashMap.class).put("a", "b").get();

How to declare a map with variable generics?

I have a Map whose keys are of generic type Key<T>, and values are of type List<T>. If the key is an instance of Key<String>, the value must be a List<String>, and the same rule applies to any other key-value pairs. I have tried the following but it does not compile:
Map<T, List<T>> map;
At present I have to declare it with "partial" generics:
Map<Object, List> map;
I know this is bad but I currently have no better choice. Is it possible to use generics in this situation?
UPDATE
Maybe I didn't express my problem clearly. I want a map that is able to:
map.put(new Key<String>(), new ArrayList<String>());
map.put(new Key<Integer>(), new ArrayList<Integer>());
And the following code should not compile:
map.put(new Key<String>(), new ArrayList<Integer>());
The key and value should always have the same generic type while the generic type can be any, and obviously extending a map does not meet my requirement.
I'm not aware of any existing library that does precisely this but it is not too hard to implement yourself. I've done something similar a few times in the past. You cannot use the standard Map interface but you can use a hash map inside to implement your class. To start, it might look something like this:
public class KeyMap {
public static class Key<T> { }
private final HashMap<Object,List<?>> values = new HashMap<Object,List<?>>();
public <T> void put(Key<T> k, List<T> v) {
values.put(k, v);
}
public <T> List<T> get(Key<T> k) {
return (List<T>)values.get(k);
}
public static void main(String[] args) {
KeyMap a = new KeyMap();
a.put(new Key<String>(), new ArrayList<String>());
a.get(new Key<Integer>());
}
}
This is what you want:
public class Test<T> extends HashMap<T, List<T>>
{
}
If you don't want a HashMap as the super class then change it to whatever concrete class you want.

Is it possible to tie nested generics?

Is it possible to tie nested generics/captures together?
I often have the problem of having a Map lookup of class to genericized item of said class. In concrete terms I want something like this (no, T is not declared anywhere).
private Map<Class<T>, ServiceLoader<T>> loaders = Maps.newHashMap();
In short, I want loaders.put/get to have semantics something like these:
<T> ServiceLoader<T> get(Class<T> klass) {...}
<T> void put(Class<T> klass, ServiceLoader<T> loader) {...}
Is the following the best I can do? Do I have to live with the inevitable #SuppressWarnings("unchecked") somewhere down the line?
private Map<Class<?>, ServiceLoader<?>> loaders = Maps.newHashMap();
Let me see If I got your intention: you want a map that stores pairs of Class/ServiceLoader where each pair is parameterized by the same T, but T may be different across pairs?
If this is the case then the best solution is to declare your own class which will exhibit such an interface. Internally it will store these pairs in a generic Map<Class<?>,ServiceLoader<?>> map.
public class MyMap {
private Map<Class<?>, ServiceLoader<?>> loaders
= new HashMaps<Class<?>, ServiceLoader<?>>();
public<T> void put(Class<T> key, ServiceLoader<T> value) {
loaders.put(key, value);
}
#SuppressWarnings("unchecked")
public<T> T get(Class<T> key) { return (ServiceLoader<T>) loaders.get(key); }
}
#SuppressWarnings("unchecked") annotations are not pure evil. You should try to avoid them but there are certain cases where you can figure out that the cast is correct despite the fact that the compiler cannot see that.
My suggestion is to create a new Object for such case. I see you were using Maps.newHashMap() so I take it that you used Google Guava so I will use ForwardingMap.
public class Loader<T> extends ForwardingMap<Class<T>, ServiceLoader<T>> {
private Map<Class<T>, ServiceLoader<T>> delegate = Maps.newHashMap();
}
A simple test proved that my suggestion is working:
public class Loader<T> extends ForwardingMap<Class<T>, Class<T>> {
private Map<Class<T>, Class<T>> delegate = Maps.newHashMap();
#Override protected Map<Class<T>, Class<T>> delegate() {
return delegate;
}
public static void main(String[] args) {
Loader<Integer> l = new Loader<Integer>();
l.put(Integer.class, Integer.class);
// error
l.put(Integer.class, String.class);
}
}

Categories