Generic class method calls - java

I apologize in advanced for my lack of knowledge in generics... I am trying to understand how generics work and I am not sure what it is but I am missing a key part that is eluding me so hopefully someone can clarify a little more and get me over this hump.
BaseDtoUtil.mapToDto(map, OcrImageDocDto.class); //OcrImageDocDto extends DtoUtils
//This method is in class BaseDtoUtil
public static <T> List<T> mapToDto(Map<String, Object> map, Class<? extends DtoUtils> klass) throws SparkSQLException{
T obj = (T)klass.newInstance();
//return a list of these
}
So I guess there are two questions I have:
First is why does it complain when I pass in OcrImageDocDto.class when the variable defined for the method call is any class? (I originally had it as (Class<?>) Doesn't that mean any class value? Obviously I am wrong but not sure what it means then.
Second question is if I send in a class value am I actually able to get that instance and return a type value back? <T> List<T>? If I am not mistaken I believe that the generic variables <T> List<T> is used for instantiations of the object. But what do they do if it is a static method?
I am a bit lost and maybe the way I understand generics is wrong... So if someone can clear these two up I think it will help a lot!

Question 1:
public static <T> List<T> mapToDto(
Map<String, Object> map,
Class<? extends DtoUtils> klass) throws SparkSQLException{
T obj = (T)klass.newInstance();
...
You don't know that klass.newInstance() can be cast to a T - all you know is that it is an instance of DtoUtils (or a subclass).
As such, you can either change to use:
DtoUtils obj = klass.newInstance();
or constrain T to extend DtoUtils:
public static <T extends DtoUtils> List<T> mapToDto(
Map<String, Object> map,
Class<? extends T> klass) throws SparkSQLException{
T obj = klass.newInstance();
...
Question 2:
Yes, because you have an actual instance of the class. You would not be able to create an instance without that (or some other object which can provide instances of T), because of type erasure.
This would not work:
public static <T extends DtoUtils> List<T> mapToDto(
Map<String, Object> map) throws SparkSQLException{
T obj = new T(); // Compiler error.
Something like the following works just fine:
T obj = klass.newInstance();
List<T> list = new ArrayList<>();
list.add(obj);
return list;

Related

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.

Generic method triggers type safety error - why?

Whilst playing around with solutions for this question, I came up with the following code, which has some compiler warnings. One warning is:
Type safety: The expression of type Test.EntityCollection needs unchecked conversion to conform to Test.EntityCollection<Test.Entity>
I don't entirely understand why this warning appears. By passing in a Class<M> type and declaring the method returns EntityCollection<M>, why am I not doing enough to convince the (Java 7) compiler that the correct type is being returned?
static class Entity {
}
static class EntityCollection<E extends Entity> {
private EntityCollection(HashMap<?, E> map) {
}
public static <T extends HashMap<?, M>, M extends Entity> EntityCollection<M> getInstance(
Class<T> mapType, Class<M> entityType)
throws ReflectiveOperationException {
T map = mapType.getConstructor().newInstance();
return new EntityCollection<M>(map);
}
}
public static void main(String[] args) throws Exception {
// both compiler warnings are on the line below:
EntityCollection<Entity> collection = EntityCollection.getInstance(
LinkedHashMap.class, Entity.class);
}
Bonus points if anyone can improve the code to avoid warnings entirely. I've been staring at it for a while and haven't dreamt up any ways to lessen the warnings.
The problem is that getInstance is a generic method but you don't pass generic type parameters to it. You can get around it by passing them like this:
public static void main(String[] args) throws Exception {
EntityCollection<Entity> collection = EntityCollection.<LinkedHashMap, Entity>getInstance(
LinkedHashMap.class, Entity.class);
}
You will still have to deal a rawtypes warning because LinkedHashMap is a generic type. This is problematic in your case since there is a wildcard in the key type.
You face several problems here:
You can't pass parameterized class objects like this: LinkedHashMap<Object, Entity>.class so you pretty much stuck with the rawtypes warning.
The problem there is T. You are adding a constraint to your method saying that T should extends HashMap<?, M>. However, the way you are later referencing to T is like a generic parameter of the type Class (Class<T>). LinkedHashMap.class is of type Class<LinkedHashMap> not Class<LinkedHashmap<?, Entity>> (which is what you needed)
A Class object always references a non-parameterized type, and that makes sense. Because the generic binding exists in compile-time, and you are going to use that Class to dynamically reflect the state and behaviour of an instance in runtime. Long story short, you can use a Class<HashMap> to build a new instance, not bounded to any type.
So, I guess what you need to do to your code to modify that constraint in the way it looks like:
public static <T extends HashMap, M extends Entity> EntityCollection<M> getInstance(
Class<T> mapType, Class<M> entityType)
throws ReflectiveOperationException {
T map = mapType.getConstructor().newInstance();
return new EntityCollection<M>(map);
}

Java anonymous generic inheritance

Take a look at the next code:
ArrayList<? extends Object> list = new ArrayList<Object>();
list.add(new Object());
It does not compile. The question is why?
ArrayList<? extends Object>
is the same as just
ArrayList<?>
and you can assign any ArrayList to a variable of this type. For example,
ArrayList<? extends Object> list = new ArrayList<String>();
is legal. Clearly, the language semantics will not let you add an Object to such a list. In fact, they won't let you add anything at all to it, except null, or something which you directly retrieved from the list.
As noted by Lukas in the comment, it is far from trivial to even add the list's own item back to it: you need a helper method to capture the wildcard into a named type.
public static void main(String[] args) {
ArrayList<? extends Object> list = new ArrayList<String>();
addOwn(list);
}
static <T> void addOwn(List<T> l) { l.add(l.get(0)); }
The problem is that Foo extends Object does not mean Collection<Foo> can be treated as a subtype of Collection<Object>. This is simply because the former class does not permit you to do everything the latter does; for instance, you cannot add an Object to a Collection<Foo>. Using generics instead of some concrete class Foo doesn't change this.
I think the reason is because Generic types are not polymorphic. When you use wildcards ? with extends you cant add anything in the collection except null.
Here is an example to what will happen if that is allowed:
Class Car{
}
class A extends Car {
}
class B extends Car{
}
Now you have List<? extends Car>
public void someMethod(List<? extends Car> list){
list.add(new A()); //this is not valid
}
Also you may invoke the method like this:
List<B> someList = new Array:list<B>();
somemethod(someList);

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.

return generic type from generic function

we have a method more or less like the following.
however we currently return List
which in function bla() would return List<Bar> at runtime.
I'm looking for a way to make both
List<Interface> = troubleFuction(foo, bar.getCLass());;
and
List<Bar> = troubleFuction(foo, bar.getCLass());;
possible.
basicaly i want it to return List which would be compatible with interface
however this gives the following error
*Type mismatch: cannot convert from List<capture#3-of ? extends Bar> to List<Interface>*
is there any way to make this return type possible or does runtime erasure make this impossible
public <T1 extends Interface, T2 extends Interface> List<"problem"> troubleFunction( T1 in, Class<T2> clazz) {
return in.doStuffWhichGeneratesAlistOF(clazz)
}
public void bla() {
Foo foo = new Foo(); // implements interface
Bar bar = new Bar(); // implements interface
List<Interface> ifaces = toubleFuction(foo, bar.getCLass());
List<Bar> mustAlsoWork = toubleFuction(foo, bar.getCLass());
}
edit:
in a lot of the existing code base the method is called like
List<Bar> result = troubleFunction(List<Interface> list, Bar.class);
thus this return type must stay compatible (rewrite/re-factor is not an option)
essentially i want the method to return List<? super Bar> if called as
troublefunction(foo, Bar.class);
and
List<? super Foo> when called as
troublefunction(foo, Bar.class);
Generally speaking in situations like this, you need to explicitly pass a Class object in (generically parameterised) which is used for the return value.
However it looks like you've done this already in your case, so would it not work for troubleFunction to be declared to return List<T2>? Alternatively, if you want to keep it general then have it return List<? extends Interface>.
You're not giving us enough information to really tell what you need to do. For example, you didn't give us the type signature of doStuffWhichGeneratesAlistOF() or tell us what it does. And you didn't tell us what the type of the "in" argument has to do with all of this.
Sure, it's possible to have the return type of a method be generic. For example,
public <T extends Interface> List<T> troubleFunction(Interface in, Class<? extends T> clazz) {
List<T> result = new ArrayList<T>();
result.add(clazz.newInstance());
return result;
}
And then you could call the method directly like this and it would work (you don't need to specify the type parameter explicitly because it's inferred from the assignment):
List<Interface> iface = this.troubleFunction(foo, bar.getCLass());
But seeing as how in your code above you return the result of in.doStuffWhichGeneratesAlistOF(clazz), you would probably have to make the return type of that method generic also. But I can't really help you on that because we don't have any information on that method.
As I understand it, the argument types are looked at before the target type to infer the generic arguments. So, I guess you need to explicitly specify the generic arguments, which I think goes something like this:
List<Interface> iface = this.<Interface>troubleFunction(foo, bar.getCLass());
where
public <T extends Interface> List<T> troubleFunction(
T in, Class<? extends T> clazz
) {
i've looked at this again and the problem was that i wanted to use a 'super' return type
the signature i was looking for was more or less:
public <T1 extends interface, T2 super T1> List<T2> getAList(Class<T1> clazz);
which is not possible

Categories

Resources