java - Reflectively checking whether a object is a valid generic argument to a method -
how check using reflection whether given object valid parameter of method (where parameter , object generic types)?
to bit of background, i'm trying achieve:
while playing reflective method calls thought nice call methods have parameter of specific type. works raw types can call isassignablefrom(class<?> c)
on class objects. however, when start throwing in generics mix isn't easy because generics weren't part of reflection's original design , because of type erasure.
the problem larger boils down following:
ideal solution
ideally code
import java.lang.reflect.*; import java.util.*; public class reflectionabuse { public static void callmemaybe(list<integer> number) { system.out.println("you called me!"); } public static void callmeagain(list<? extends number> number) { system.out.println("you called me again!"); } public static void callmenot(list<double> number) { system.out.println("what's wrong you?"); } public static <t> void reflectivecall(list<t> number){ for(method method : reflectionabuse.class.getdeclaredmethods()) { if(method.getname().startswith("call")) { if(canbeparameterof(method, number)) { try { method.invoke(null, number); } catch (exception e) { e.printstacktrace(); } } } } } public static <t> boolean canbeparameterof(method method, list<t> number) { // fixme checks missing return true; } public static void main(string[] args) { reflectivecall(new arraylist<integer>()); } }
would print
you called me! called me again!
this should possible regardless of how t
(i.e. generic type, such list<list<integer>>
).
obviously can't work because type t
erased , unknown @ runtime.
attempt 1
the first thing working this:
import java.lang.reflect.*; import java.util.*; public class reflectionabuse { public static void callmemaybe(arraylist<integer> number) { system.out.println("you called me!"); } public static void callmeagain(arraylist<? extends number> number) { system.out.println("you called me again!"); } public static void callmenot(arraylist<double> number) { system.out.println("what's wrong you?"); } public static <t> void reflectivecall(list<t> number){ for(method method : reflectionabuse.class.getdeclaredmethods()) { if(method.getname().startswith("call")) { if(canbeparameterof(method, number)) { try { method.invoke(null, number); } catch (exception e) { e.printstacktrace(); } } } } } public static <t> boolean canbeparameterof(method method, list<t> number) { return method.getgenericparametertypes()[0].equals(number.getclass().getgenericsuperclass()); } public static void main(string[] args) { reflectivecall(new arraylist<integer>(){}); } }
which prints
you called me!
however, has additional caveats:
- it works direct instances , inheritance hierarchy not taken account
type
interface not provide needed methods. cast here , there find out (see second attempt) - the argument of
reflectivecall
needs subclass of wanted parameter type (notice{}
innew arraylist<integer>(){}
create anonymous inner class). that's less ideal: creates unnecessary class objects , error prone. way think of getting around type erasure.
attempt 2
thinking missing type in ideal solution due erasure, 1 pass type argument gets quite close ideal:
import java.lang.reflect.*; import java.util.*; public class reflectionabuse { public static void callmemaybe(list<integer> number) { system.out.println("you called me!"); } public static void callmeagain(list<? extends number> number) { system.out.println("you called me again!"); } public static void callmenot(list<double> number) { system.out.println("what's wrong you?"); } public static <t> void reflectivecall(list<t> number, class<t> clazz){ for(method method : reflectionabuse.class.getdeclaredmethods()) { if(method.getname().startswith("call")) { type n = number.getclass().getgenericsuperclass(); if(canbeparameterof(method, clazz)) { try { method.invoke(null, number); } catch (exception e) { e.printstacktrace(); } } } } } public static <t> boolean canbeparameterof(method method, class<t> clazz) { type type = ((parameterizedtype)method.getgenericparametertypes()[0]).getactualtypearguments()[0]; if (type instanceof wildcardtype) { return ((class<?>)(((wildcardtype) type).getupperbounds()[0])).isassignablefrom(clazz); } return ((class<?>)type).isassignablefrom(clazz); } public static void main(string[] args) { reflectivecall(new arraylist<integer>(), integer.class); } }
which prints correct solution. not without negative sides:
- the user of
reflectivecall
needs pass type parameter unnecessary , tedious. @ least correct call checked @ compile time. - inheritance between type parameters not taken account , there many cases left need implemented in
canbeparameterof
(such typed parameters). - and biggest problem: type parameter can't generic in itself,
list
oflist
s ofintegers
can't used argument.
the question
is there differently close possible goal? stuck either using anonymous subclasses or passing type parameter? moment, i'll settle giving parameter gives compile time safety.
is there need aware of when recursively checking parameter types?
is there possibility allow generics type parameter in solution 2?
actually, learning purposes roll own solution instead of using library although wouldn't mind taking @ inner workings of some.
just make things clear, i'm example aware of following try keep examples clean:
- potentially solved without reflection (while redesigning of requirements , using example interfaces , inner classes). learning purposes. problem i'd solve quite bit larger boils down to.
- instead of using naming pattern use annotations. that, i'd keep examples self-contained possible.
- the array returned
getgenericparametertypes()
empty, let's assume methods have argument , checked beforehand. - there non-static methods fail when
null
called. assume there aren't. - the
catch
conditions more specific. - some casts need more safe.
am stuck either using anonymous subclasses or passing type parameter?
more or less, better subclass using super type token pattern instead of subclassing value type. after all, value type might not allow subclasses. using type token pattern allows accept generic types, whereas accepting class
type parameter allows raw types (which why had take component type of list
, not type itself).
public static <t> void reflectivecall(typetoken<t> type, t value)
guava has great support creating , using typetoken
s. far simplest way create 1 creating anonymous subclass (note: if it's going reused, make constant):
reflectivecall(new typetoken<list<integer>>() {}, new arraylist<integer>());
once have this, canbeparameterof
becomes easier implement.
public static boolean canbeparameterof(method method, typetoken<?> giventype) { type[] argtypes = method.getgenericparametertypes(); return argtypes.length != 0 && typetoken.of(argtypes[0]).isassignablefrom(giventype); }
Comments
Post a Comment