I've got the following two methods:
public static <T, R> IGetter<T, R> createGetterViaMethodname( final Class<T> clazz, final String methodName,
final Class<R> fieldType )
throws Throwable
{
final MethodHandles.Lookup caller = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( fieldType );
final MethodHandle target = caller.findVirtual( clazz, methodName, methodType );
final MethodType type = target.type();
final CallSite site = LambdaMetafactory.metafactory(
caller,
"get",
MethodType.methodType( IGetter.class ),
type.erase(),
target,
type );
final MethodHandle factory = site.getTarget();
return (IGetter<T, R>) factory.invoke();
}
public static <T, I> ISetter<T, I> createSetterViaMethodname( final Class<T> clazz, final String methodName,
final Class<I> fieldType )
throws Throwable
{
final MethodHandles.Lookup caller = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( void.class, fieldType );
final MethodHandle target = caller.findVirtual( clazz, methodName, methodType );
final MethodType type = target.type();
final CallSite site = LambdaMetafactory.metafactory(
caller,
"set",
MethodType.methodType( ISetter.class ),
type.erase(),
target,
type );
final MethodHandle factory = site.getTarget();
return (ISetter<T, I>) factory.invoke();
}
including the following two interfaces:
@FunctionalInterface
public interface IGetter<T, R>
{
@Nullable
R get( T object );
}
@FunctionalInterface
public interface ISetter<T, I>
{
void set( T object, @Nullable I value );
}
This works great for all Class-Types, including the Number-Wrappers for primitive types such as Integer
for int
. However, if I have a setter that takes an int
or a getter that returns an ìnt
, it tries passing / returning the Number-Wrapper, resulting in an exception.
What's the correct way to box / unbox this without having to make another method. The reason here is to keep the API clean and simple to use. I am willing to take a small performance hit for the boxing / unboxing here.
There is no built-in "pretty" way to convert a primitive class to a wrapper class, so you gotta use a map like this:
private final static Map<Class<?>, Class<?>> map = new HashMap<>();
static {
map.put(boolean.class, Boolean.class);
map.put(byte.class, Byte.class);
map.put(short.class, Short.class);
map.put(char.class, Character.class);
map.put(int.class, Integer.class);
map.put(long.class, Long.class);
map.put(float.class, Float.class);
map.put(double.class, Double.class);
}
Or use one of the other ways here.
Once you do that, you can just check if fieldType
is a primitive. If it is, change the return type/parameter type of the method type by looking up the wrapper type in the map.
For the getter:
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeReturnType(map.get(fieldType));
}
For the setter:
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeParameterType(1, map.get(fieldType));
}
Just in case it wasn't clear, the caller would pass the primitive class for primitive getters and setters:
createSetterViaMethodname(Main.class, "setFoo", int.class)
// for a setter declared like this:
public void setFoo(int i) {
...
}
Full code:
public class Main {
public static void main(String[] args) throws Throwable {
// this prints 1234567
createSetterViaMethodname(Main.class, "setFoo", int.class).set(new Main(), 1234567);
}
public void setFoo(int i) {
System.out.println(i);
}
public final static Map<Class<?>, Class<?>> map = new HashMap<>();
static {
map.put(boolean.class, Boolean.class);
map.put(byte.class, Byte.class);
map.put(short.class, Short.class);
map.put(char.class, Character.class);
map.put(int.class, Integer.class);
map.put(long.class, Long.class);
map.put(float.class, Float.class);
map.put(double.class, Double.class);
}
public static <T, R> IGetter<T, R> createGetterViaMethodname( final Class<T> clazz, final String methodName,
final Class<R> fieldType )
throws Throwable
{
final MethodHandles.Lookup caller = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( fieldType );
final MethodHandle target = caller.findVirtual( clazz, methodName, methodType );
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeReturnType(map.get(fieldType));
}
final CallSite site = LambdaMetafactory.metafactory(
caller,
"get",
MethodType.methodType( IGetter.class ),
type.erase(),
target,
type);
final MethodHandle factory = site.getTarget();
return (IGetter<T, R>) factory.invoke();
}
public static <T, I> ISetter<T, I> createSetterViaMethodname( final Class<T> clazz, final String methodName,
final Class<I> fieldType )
throws Throwable
{
final MethodHandles.Lookup caller = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( void.class, fieldType );
final MethodHandle target = caller.findVirtual( clazz, methodName, methodType );
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeParameterType(1, map.get(fieldType));
}
final CallSite site = LambdaMetafactory.metafactory(
caller,
"set",
MethodType.methodType( ISetter.class ),
type.erase(),
target,
type );
final MethodHandle factory = site.getTarget();
return (ISetter<T, I>) factory.invoke();
}
}
@FunctionalInterface
interface IGetter<T, R>
{
@Nullable
R get( T object );
}
@FunctionalInterface
interface ISetter<T, I>
{
void set( T object, @Nullable I value );
}