How to declare a map with variable generics? - java

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.

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>();

Enforcing generics on a map

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);
}

Is it possible to declare two wildcard types to be of same type?

I want to create a mapping from (a) class type to (b) long (the identifier of the object of the defined class type) to (c) the object itself.
I have the following:
protected HashMap<Class<?>, HashMap<Long, ?>> obj = new HashMap<Class<?>, HashMap<Long, ?>>();
Is it possible to somehow denote that the first ? must be of the same type than the second ?? I would expect something like this, but this is ofcourse not possible:
protected <T> HashMap<Class<T>, HashMap<Long, T>> obj = new HashMap<Class<T>, HashMap<Long, T>>();
As an alternative, you could use a small amount of not-type-safe code encapsulated in a way that enforces your constraint:
class Cache {
private Map<Class<?>, Map<Long, ?>> items = new HashMap<Class<?>, Map<Long, ?>>();
private <T> Map<Long, T> getItems(Class<T> type) {
#SuppressWarnings("unchecked")
Map<Long, T> result = (Map<Long, T>) items.get(type);
if (result == null) {
result = new HashMap<Long, T>();
items.put(type, result);
}
return (Map<Long, T>) result;
}
public <T> void addItem(Class<T> type, Long id, T item) {
getItems(type).put(id, item);
}
public <T> T getItem(Class<T> type, Long id) {
return type.cast(getItems(type).get(id));
}
}
The type.cast() in getItem() isn't necessary for the compiler to not complain, but it would help catch an object of the wrong type getting into the cache early.
Each occurence of a wildcard corresponds to a different type, and the only appropriate scope for a type parameter representing the type is the entry in the outer HashMap. Unfortunately, HashMap does not allow constraining the entry type in its type parameter like:
class Entry<K,V> {
// fields omitted
}
class Map<E extends Entry<?,?> {
}
class EntityCacheEntry<E> extends Entry<Class<E>, Map<Entry<Long, E>>> { }
class EntityCache extends Map<EntityCacheEntry<?>> { }
Even if it did, there is no way to implement Map.get without using unchecked casts, because we'd have to constrain its type parameter to a particular member of the type family represented by E - and you can't constrain a type parameter of a type parameter in Java.
Therefore, your only recourse is writing a facade whose api enforces the type invariant, but internally uses casts:
class EntityCache {
Map<Class<?>, Map<Long, Object>> map = new HashMap<>();
public <E> void put(Class<E> clazz, long id, E entity) {
map.get(clazz).put(id, entity);
// TODO create map if it doesn't exist yet
}
public <E> E get(Class<E> clazz, long id) {
return clazz.cast(map.get(clazz).get(id));
// TODO what if not found?
}
}
You could extend the HashMap class with your specific generic definitions and make a generic class that takes the <T> as argument, something like this:
public class MyHashMap<T> extends HashMap<Class<T>, HashMap<Long, T>> { ...
What you probably want is: HashMap<Class<?>, HashMap<Long, Object>>. Because you will be putting objects of different types in it, Object is the type parameter that will allow you to add any type of object.
Don't get confused with the wildcard (?). It has the opposite meaning -- it means that the parameter is some type (thus all the objects must be of that type) but we don't know what it is, thus we can't put anything in it at all. That is not what you want.

how can I relate values of generic type parameters in java Map interface?

I don't think the title of the question will be clear, but the idea is simple.
Suppose I have a Map type variable.
Map<K,V> myMap;
but I want to establish a relation between K and V. for example, I'd like to say that
this Map relates Sets of some class to objets of that class. Something like:
Map<Set<T>, T> myMap;
but not for a specific type T. I'd like this Map to accept entries like
(Set<String>, String),
(Set<Integer>, Integer)
...
Is there a possible declaration for myMap that allows me to have this behavior? Please let me know if I'm explaining myself wrongly or if I have a previous conceptual error.
Sadly this is not possible with Java generics. If Java allowed higher order type parameters, then one could have defined Map something like:
public interface Map<V<>> { // here V<> is my hypothetical syntax for a
// type parameter which is itself generic...
<K>
V<K> put(K key, V<K> value);
...
}
instead of the actual java.util.Map:
public interface Map<K, V> {
V put(K key, V value);
...
}
You can see that the problem is that K is declared once for the whole class and not for each call to .put().
Enough fantasizing, so what can you do? I think the best is to create a Map<Set<?>, Object> and wrap it as a private member. Then you are free to create your own put() and get() which take into account the intended "relation" between types:
class SpecialMap {
private Map<Set<?>, Object> map = ...;
public <T>
T put(Set<T> key, T value) {
return (T) map.put(key, value);
}
public <T>
T get(Set<T> key) {
return (T) map.get(key);
}
}
What you are trying to do doesn't seem like a good diea, because each Set<T> is always not equal to another Set<T> even if of the same type - using Sets as keys is more or less useless.
That said, you don't need to define a new class - you can require a method to accept such a map:
public static <T> void process(Map<Set<T>, T> map) {
for (Map.Entry<Set<T>, T> entry : map) {
Set<T> key = entry.getKey();
T value = entry.getValue();
// do something
}
}
I don't think it's possible to achieve compile-time checking with Java generics. However it's quite simple at runtime. Just right a short decorator:
public class FancyTypeMapDecorator implements Map<Set<? extends Object>, Object> {
final Map<Set<? extends Object>, Object> target;
public FancyTypeMapDecorator(Map<Set<? extends Object>, Object> target) {
this.target = target;
}
#Override
public Object put(Set<? extends Object> key, Object value) {
final Class<?> keyElementType = key.iterator().next().getClass();
final Class<?> valueType = value.getClass();
if (keyElementType != valueType) {
throw new IllegalArgumentException(
"Key element type " + keyElementType + " does not match " + valueType);
}
return target.put(key, value);
}
#Override
public void putAll(Map<? extends Set<? extends Object>, ? extends Object> m) {
for (Entry<? extends Set<? extends Object>, ? extends Object> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
//remaining methods are simply delegating to target
}
Here's how it works:
final Map<Set<? extends Object>, Object> map =
new FancyTypeMapDecorator(new HashMap<Set<? extends Object>, Object>());
Set<? extends Object> keyA = Collections.singleton(7);
map.put(keyA, 42);
Set<? extends Object> keyB = Collections.singleton("bogus");
map.put(keyB, 43);
Second put throws an exception.
However both the implementation (and I don't even mean that it will fail for empty Set as a key) and usage/API triggers an alarm bell... Do you really want to deal with such a structure? Maybe you need to rethink your problem? What are you actually trying to achieve?
There is no way with generics to have the compiler verify a different T for each put() call. In other words, you can't have the same map and do:
myMap.put(new HashSet<String>(), "foo");
myMap.put(new HashSet<Integer>(), 1);
If you need this then you may have to store <Object> and do the verification yourself using instanceof or some other hack.
Now, you can definitely do something like:
public class MyMap<T> extends HashMap<Set<T>, T> {
...
Then you can do:
MyMap<String> myMap = new MyMap<String>();
Set<String> set = new HashSet<String>();
myMap.put(set, "foo");
Remember that the key has to have a valid hashCode() and equals() methods which might be expensive with a Set.

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