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);
}
}
Related
I have a class Saver<T> with one generic type argument <T>. In my Saver<T> class, I'm attempting to define the following static map:
public abstract class Saver<T> {
private static final Map<Class<E>, Class<? extends Saver<E>>> DEFAULT_SAVERS = new HashMap<>();
}
Of course, this gives me an error because the compiler doesn't know what E is. How can I create this map? The map should have Classes for keys, and Savers with that class as its generic type as its values. Is this possible? How can I do this?
There's no typesafe way to declare it. You'll have to use wildcards and control how it's accessed to prevent heap pollution:
private static Map<Class<?>, Saver<?>> DEFAULT_SAVERS = new HashMap<>();
public static <T> void put(Class<T> clazz, Saver<T> saver) {
DEFAULT_SAVERS.put(clazz, saver);
}
public static <T> Saver<T> get(Class<T> clazz) {
return (Saver<T>)DEFAULT_SAVERS.get(clazz);
}
I need to create a container that provides a way for me to store elements of generic type, kind of like this effective java pattern but storing generic things
Is it possible to create a typesafe heterogeneous container where generic typed things are involved?
<T> void put(T ele, SomeTypedThing<T> genEle);
<T> SomeTypedThing<T> get(T ele);
I am fine to add the Class<T> as method param. example:
public static class Container {
Map<Class<?>, Set<?>> store = new HashMap<>();
public <T> void put(Set<T> ele, Class<T> type) {
store.put(type, ele);
}
public <T> Set<T> get(Class<T> type) {
return store.get(type);
}
}
would it be possible to achieve this?
Set<?> raw = store.get(type);
Set<T> typed = // some magic;
how, or why not? is it something that java doesn't do or is it something fundamental (so no language can do, or just doesn't make sense to do)
The problem is with the wildcard parameter on the Set. Instead of using a Set<?>, make it a Set<Object>, and everything works:
public static class Container {
Map<Class<?>, Set<Object>> store = new HashMap<>();
public <T> void put(T ele, Class<T> type) {
store.putIfAbsent(type, new HashSet<>());
store.get(type).add(ele);
}
}
The difference between Set<?> and Set<Object> is this:
A Set<?> could be a Set of any type - it could be a Set<String> or a Set<Integer>. And the java compiler wants to make sure that you are not trying to add a String object to a Set<Integer>.
On the other hand, a Set<Object> is just a Set that can contain instances of the Object class. And since String and Integer are both subclasses of Object, you can easily store strings and Integers into such a set.
Adding the method
public <T> Set<T> get(Class<T> type) {
return (Set<T>) store.get(type);
}
to the class gives a compiler warning about an unchecked cast. This warning can be safely ignored here, because you know that you added only elements of type T to that Set.
I stumbled upon this problem a few weeks ago as I needed a typesafe heterogenous container (THC) for literally any object (including generic interface implementations) AND any number of them (like two keys that provide a String for example).
Although this question is rather old, I'd like to provide another approach.
THCs are all about parameterizing the keys. So you can use a key object that wraps the class type instead of using the class type as key.
For example:
Key-Class:
static class Type<T> {
private final Class<T> object_type;
public Type(String name, Class<T> object_type){
this.object_type = object_type;
}
public Class<T> getObjectType() {
return object_type;
}
}
Container-Class:
static class Container{
private final Map<Type<?>, Object> properties = new HashMap<>();
public <T> T get(Type<T> type){
return type.getObjectType().cast(properties.get(type)); //no compiler complaints
}
public <T> void put(Type<T> type, T value){
properties.put(type, type.getObjectType().cast(value)); //no compielr complaints
}
}
Since we can't provide Set<Foo>.class as object type we have like two possibilites to deal with the generic type. Either we use inheritence or we use composition.
Inheritence:
static class IntHashSet extends HashSet<Integer>{}
Composition:
static class IntSetComposition{
private final Set<Integer> set;
public IntSetComposition(Set<Integer> set){
this.set=set;
}
public Set<Integer> getSet(){
return this.set;
}
}
How to use all this:
public static void main(String[] args) {
Type<String> string_type = new Type<>("string_type_1", String.class);
Type<Integer> int_type = new Type<>("int_type_1", Integer.class);
Type<IntHashSet> int_set = new Type<>("int_hashset", IntHashSet.class);
Type<IntSetComposition> int_set_comp = new Type<>("int_set_comp", IntSetComposition.class);
Container container = new Container();
String s = container.get(string_type); //no compiler complaints
int i = container.get(int_type); //no compiler complaints
IntHashSet set = container.get(int_set); //no compiler complaints
Set<Integer> set2 = container.get(int_set_comp).getSet(); //no compiler complaints
String s2 = container.get(int_type); //the compiler does not like this!
}
Note: NotNull checks should be implemented as well as hashcode() and equals() overrides
Thomas Klägers solution is an answer that also came to my head. Creating an fully functioning heterogeneous container which works like this:
Set<?> raw = store.get(type);
Set<T> typed = // some magic;
But as far as I know, It is not possible to use container You mentioned with that 'smooth' code I quoted above. However it is usable and You can get sets of Your stored by class sets. Here goes the code:
public class Container {
Map<Class<?>, Set<Object>> container = new HashMap<>();
public <T> void put(T e, Class<?> type) {
container.putIfAbsent(type, new HashSet<>());
container.get(type).add(e);
}
public <T> Set<T> get(Class<T> type) {
#SuppressWarnings("unchecked") //It is fine to ignore warnings here
Set<T> res = (Set<T>) container.get(type);
return res;
}
}
And working example of storing ang retreiving Containers elements:
public class Run {
public static void main(String[] args) {
Foo foo = new Foo();
Bar bar = new Bar();
Container con = new Container();
con.put(foo, foo.getClass());
con.put(bar, bar.getClass());
Set<? extends Foo> foos = con.get(foo.getClass());
Set<? extends Bar> bars = con.get(bar.getClass());
//here You can use Your sets as ususal
}
If approach <? extends Foo> and usage is fine for You, it's working solution. Above that if You work in Java 10+, there's possibility for that 'dirty' declaration omission. Just declare it as var and poof, its hidden.
public class MyClass<T> {
private Map<Class<?>, Object> member;
public <E> void putEnumSet(Class<E> enumSetType, E enumSet) {
this.member.put(enumSetType, enumSetType.cast(enumSet));
}
public <E> E getEnumSet(Class<E> enumType) {
return enumType.cast(this.member.get(enumType));
}
};
public enum Category {
// ...
};
The member in MyClass is used to store several kinds of EnumSet with different Enum type. While implementing relative methods, I meet some problems: when I try to call the method like this:
public static void main(String[] args) {
EnumSet<Category> set = EnumSet.noneOf(Category.class);
MyClass<Word> newClass = new MyClass<Word>();
newClass.putEnumSet(set.getClass(), set);
}
Here comes the error:
The method putEnumSet(Class<E>, E) in the type MyClass<Word> is not applicable for the arguments (Class<capture#1-of ? extends EnumSet>, EnumSet<Category>)
How to deal with this problem? I think it may come from raw type or type erasure, but I do not know the main reason. Thanks.
How to deal with this problem?
E extends EnumSet<E>
This is very confusing as it says you have to have a element E which must extend EnumSet<E> i.e. you need an element type which is itself an EnumSet of E
You have a problem that all EnumSet classes are the same at runtime. I.e. there is only one EnumSet.class. You are better off recording the class of elements.
public class MyClass {
private Map<Class, Set> member;
public <E> void putEnumSet(Class<E> elementType, Set<E> enumSet) {
this.member.put(elementType, enumSet);
}
public <E> Set<E> getEnumSet(Class<E> elementType) {
return (Set<E>) this.member.get(elementType));
}
};
Class objects can be difficult to use. As you have noticed, they're not easy to use with generic types because due to type erasure EnumSet<Category>.class is not legal code. It would be impossible to use your approach to store EnumSets for different Enums because there is only one Class object for all EnumSets, namely EnumSet.class.
One solution I have found to this is to replace Class<?> with my own key object. Here is a complete program demonstrating this approach.
public class Main {
public enum Shape { SQUARE, CIRCLE, TRIANGLE }
// Here you would instantiate all the keys you will need.
public static final ClassKey<String> STRING_KEY = new ClassKey<String>();
public static final ClassKey<EnumSet<Shape>> SHAPE_SET_KEY = new ClassKey<EnumSet<Shape>>();
public static final class ClassKey<T> { private ClassKey() {} }
private Map<ClassKey<?>, Object> member = new HashMap<ClassKey<?>, Object>();
public <E extends Enum<E>> void putEnumSet(ClassKey<EnumSet<E>> enumSetType, EnumSet<E> enumSet) {
this.member.put(enumSetType, enumSet);
}
public <E extends Enum<E>> EnumSet<E> getEnumSet(ClassKey<EnumSet<E>> enumType) {
return (EnumSet<E>) member.get(enumType);
}
public static void main(String[] args) {
Main main = new Main();
EnumSet<Shape> enumSet = EnumSet.allOf(Shape.class);
main.putEnumSet(SHAPE_SET_KEY, enumSet);
EnumSet<Shape> shapes = main.getEnumSet(SHAPE_SET_KEY);
System.out.println(shapes);
}
}
One major drawback to this approach is that you have to have a fixed bank of ClassKey objects. It would not work to create these objects on the fly because if you usednew ClassKey<EnumSet<Shape>> to put an EnumSet into member and then tried to use new ClassKey<EnumSet<Shape>> to retrieve the EnumSet, you would find it would't work because the keys would not be equal. There is no way to write an equals() method for ClassKey that works because, due to type erasure, it would be impossible to tell a ClassKey<EnumSet<Shape>> from a ClassKey<EnumSet<Category>>.
What I would like to obtain is something that can safely return the instance of an input class. In my case is used as a service provider. A code sample:
public class MyClass {
private Map<String, Object> mServices;
public MyClass() {
mServices = new HashMap<String, Object>();
mServices.put(XmlService.class.getName(), new XmlService());
}
public <E extends Object> E getService(Class<?> service) {
return (E) mServices.get(service.getName());
}
}
I admit that I am not that skilled with Parameterized Types and I need some help here. Am I not getting an Object from the Map? Why do I need to cast to E, loosing the type safety?
Is there a way to avoid either casting and/or suppress warnings?
No, unfortunately the is no way to avoid this extra downcast. Notice that the type of your map is Map<String, Object>. Object - that is all the Java compiler knows about values. You would have to somehow declare the map to have different type of every key. Impossible.
But your code can be simplified a bit:
public <E> E getService(Class<E> service) {
return (E) mServices.get(service.getName());
}
Another slight improvement involves using Class as key:
private Map<Class<?>, Object> mServices;
public <E> E getService(Class<E> service) {
return (E) mServices.get(service);
}
This should work without the downcast :
public class ServiceLoader<T extends AbstractService> {
private Map<String, T> services = new HashMap<String, T>();
T getService(Class<T> componentType) {
return services.get(componentType.getName());
}
The problem: I've a Function Object interface defined in a class:
public static interface FunctionObject<T> {
void process(T object);
}
I need it generic because I'd like to use T methods in the process implementations.
Then, in other generic class, I've a Map where I have classes as keys and function objects as values:
Map<Class<T>, FunctionObject<T>> map;
But I also want the map to accept subtype classes and function objects of supertypes OF THE KEY TYPE, so I did this:
Map<Class<? extends T>, FunctionObject<? super T>> map; //not what I need
The basic idea is to be able to use the map as follows:
//if T were Number, this should be legal
map.put(Class<Integer>, new FunctionObject<Integer>(){...});
map.put(Class<Float>, new FunctionObject<Number>(){...});
map.put(Class<Double>, new FunctionObject<Object>(){...});
As I want to enforce the FunctionObject has the type of the class key or a supertype, what I really would like to define is this:
Map<Class<E extends T>, FunctionObject<? super E>>> map;
How can I achieve the desired effect? Is a typesafe heterogenous container the only option? What would the Map generic types look like to allow populating it from a reference?
Parametrized container, seems to work just fine:
public class MyMap<T>
{
interface FunctionObject<X> {}
private Map<Class<? extends T>, FunctionObject<Object>> map = new HashMap<>();
#SuppressWarnings("unchecked")
public <E extends T> void put(Class<E> c, FunctionObject<? super E> f)
{
map.put(c, (FunctionObject<Object>) f);
}
public <E extends T> FunctionObject<Object> get(Class<E> c)
{
return map.get(c);
}
public static void Main(String[] args)
{
MyMap<Number> map = new MyMap<>();
map.put(Integer.class, new FunctionObject<Integer>() {});
map.put(Float.class, new FunctionObject<Number>() {});
map.put(Double.class, new FunctionObject<Object>() {});
}
}
Edited to comply to the question. Sadly there is no way to avoid the downcasting to object.
Edit added get().
You can do this with encapsulation, assuming you only use the map through the method which check this on a per entry basis.
The following add method avoids the need to double up on the type as well.
public class Main {
interface FunctionObject<T> { }
private final Map<Class, FunctionObject> map = new LinkedHashMap<Class, FunctionObject>();
public <T> void add(FunctionObject<T> functionObject) {
Class<T> tClass = null;
for (Type iType : functionObject.getClass().getGenericInterfaces()) {
ParameterizedType pt = (ParameterizedType) iType;
if (!pt.getRawType().equals(FunctionObject.class)) continue;
Type t = pt.getActualTypeArguments()[0];
tClass = (Class<T>) t;
break;
}
map.put(tClass, functionObject);
}
public <T> void put(Class<T> tClass, FunctionObject<T> functionObject) {
map.put(tClass, functionObject);
}
public <T> FunctionObject<T> get(Class<T> tClass) {
return map.get(tClass);
}
public static void main(String... args) throws IOException {
Main m = new Main();
m.add(new FunctionObject<Integer>() {
});
FunctionObject<Integer> foi = m.get(Integer.class);
System.out.println(foi.getClass().getGenericInterfaces()[0]);
}
}
prints
Main.Main$FunctionObject<java.lang.Integer>
You can use #SuppressWarnings("unchecked") if you want to disable the warning.
The point is; there is no way to describe the constraint you have in the field declaration, you can achieve the same result if you use accessor methods which do the check on a per entry basis. You can add runtime checks as well if you need to ensure raw types are correct.