Typesafe heterogeneous containers involving generics - java

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.

Related

Method to take `T extends Iterable` and return T<E>?

How do I write a method that takes a parameter of some type T which is an instance of Iterable, as well as a parameter of Class<E>, and return T<E>?
public static <...> ... checkedCast(T iterable, Class<E> clazz) {
// Check elements and throw ClassCastException if invalid
#SupressWarning("checked")
... cast = (...)iterable;
return cast;
}
I want to use it like this:
// This should compile
ArrayList<?> a = ...;
ArrayList<String> b = checkedCast(a, String.class);
// So should this
HashSet<Number> c = ...;
Set<Integer> d = checkedCast(c, Integer.class);
// This shouldn't compile
b = checkedCast(a, Integer.class);
// This shouldn't compile
b = checkedCast(c, Integer.class);
// This should throw ClassCastException
checkedCast(a, Integer.class);
I know I can do this using overrides, but this requires me to write an override for every type:
public static <T> Iterable<T> checkedCast(Iterable<?> iterable, Class<T> clazz) {...}
public static <T> List<T> checkedCast(List<?> list, Class<T> clazz) {...}
public static <T> ArrayList<T> checkedCast(ArrayList<?> list, Class<T> clazz) {...}
public static <T> Set<T> checkedCast(Set<?> set, Class<T> clazz) {...}
One of the weaknesses of the Java type system's Generics extension is that how we think about types in the singular doesn't scale to how we think of types in the plural.
In short, Collections of a generic type cannot be safely cast, ever. Build a new list, pull out each type and check it individually, and the return the new list. If you disregard this warning, I'll direct someone to do something like
List<Customer> customers = new ArrayList<>();
customers.add(new Customer(...));
List<Object> customerObjects = checkCast(customers, Object.class);
customerObjects.add(new Order(...));
You have been warned.
See if this works for you. But, people can help you better if you can describe in more detail why you need such a method.
public static
<InputElement, OutputElement extends InputElement,
InputContainer extends Iterable<InputElement>,
OutputContainer extends Iterable<OutputElement>>
OutputContainer checkedCast(InputContainer iterable,
Class<OutputElement> clazz) {
#SuppressWarnings("unchecked")
OutputContainer output = (OutputContainer) iterable;
return output;
}
This works/matches your requirements - except for throwing a ClassCastException (if you really want that behaviour, you can include it in the checkedCast method yourself):
import java.util.*;
public class CheckedCast {
public static <GenB, GenA extends GenB, CollA extends List<GenA>> List<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
return (List<GenB>)iterable;
}
public static <GenB, GenA extends GenB, CollA extends Set<GenA>> Set<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
return (Set<GenB>)iterable;
}
static class One {}
static class Two extends One {}
static class Three {}
public static void main(String[] args) {
ArrayList<Two> test1 = new ArrayList<Two>();
List<One> test2 = checkedCast(test1, One.class);
// Shouldn't compile...
ArrayList<One> aa = checkedCast(test2, One.class); // output is ArrayList
List<Two> bb = checkedCast(test2, Three.class); // Three is not superClass of Two
ArrayList cc = checkedCast(new HashSet(), Integer.class); // Set cannot become List
ArrayList<One> dd = checkedCast(new LinkedList<One>(), One.class); // ArrayList is not superClass of List
}
}
Updated to match new requirement: ArrayList xs = checkedCast(new HashSet(), Integer.class) - shouldn't compile
Update: updated to assert returned Collection generic type extends input Collection's generic type.

When inserting objects into a type-safe heterogeneous container, why do we need the class reference?

I'm checking out the heterogeneous container pattern from Bloch's Effective Java and I'm trying to determine why the class reference is needed when inserting objects into the heterogeneous container. Can't I use instance.getClass() to get this reference? Isn't JPA's entity manager an example of this?
interface BlochsHeterogeneousContainer {
<T> void put(Class<T> clazz, T instance);
<T> T get(Class<T> clazz);
}
interface AlternativeHeterogeneousContainer {
// Class<T> not needed because we can use instance.getClass()
<T> void put(T instance);
<T> T get(Class<T> clazz);
}
No you can't do that, as it won't give you class of reference type in case of inheritance, rather the class of actual object type.
Consider this example:
Number num = new Integer(4);
System.out.println(num.getClass());
this will print:
class java.lang.Integer
and not java.lang.Number.
I actually always need other approach (with .getClass()), so I suppose both implementations could be useful..
class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
public <T> void putFavorite(T instance) {
favorites.put(instance.getClass(), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
public static void main(String[] args) {
Favorites favorites = new Favorites();
Number num = new Integer(4);
favorites.putFavorite(Number.class, num); //ADDS Number -> 4
//favorites.putFavorite(Integer.class, num); //Error: no suitable method found for putFavorite(java.lang.Class<java.lang.Integer>,java.lang.Number)
favorites.putFavorite(num); //ADDS Integer -> 4
System.out.println(favorites.favorites); //{class java.lang.Integer=4, class java.lang.Number=4}
}
}

Generics: how to enforce restrictions between keys and values in a Map

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.

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

How to find object of given type in a heterogeneous List

I have a heterogeneous List that can contain any arbitrary type of object. I have a need to find an element of the List that is of a certain type. Looking through the answers of other generics related questions, I'm not finding exactly what I need.
Here's an example of what I'm trying to accomplish:
List <Object> list = new ArrayList <Object>();
...
private someMethod() {
Customer cust = findInList( Customer.class );
Invoice inv = findInList( Invoice.class );
}
So, how do I define findInList using generics? I gather that type erasure causes issues here and I don't know as much about that as I probably should, but I'd rather not define multiple "find" methods since there could be dozens of different types of objects living in the List.
You can use Typesafe Heterogeneous Container pattern described by Josh Bloch. Here is an example from Josh's presentation:
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);
}
}
You can easily extend this to support List of favorites per type instead of a single value.
You can define the method using [Class.isInstance()](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#isInstance(java.lang.Object)) and [Class.cast()](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#cast(java.lang.Object)) method. Here is a sample implementation (with extra argument for the list):
static <T> T findInList(List<?> list, Class<T> clazz) {
for (Object o : list) {
if (clazz.isInstance(o)) {
return clazz.cast(o);
}
}
return null;
}
Update: I would recommend against putting multiple types in the Collection. It's usually a sign that either need a custom datatype (e.g. Transaction) or a Tuple value.

Categories