问题描述
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 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,"set",MethodType.methodType( ISetter.class ),type );
final MethodHandle factory = site.getTarget();
return (ISetter<T,I>) factory.invoke();
}
包括以下两个接口:
@FunctionalInterface
public interface IGetter<T,R>
{
@Nullable
R get( T object );
}
@FunctionalInterface
public interface ISetter<T,I>
{
void set( T object,@Nullable I value );
}
这适用于所有类类型,包括原始类型的数字包装器,例如 Integer
的 int
。但是,如果我有一个接受 int
的 setter 或返回 ìnt
的 getter,它会尝试传递/返回 Number-Wrapper,从而导致异常。
什么是装箱/拆箱而不用另一种方法的正确方法。这里的原因是保持 API 干净且易于使用。我愿意为这里的装箱/拆箱做一些小小的性能打击。
解决方法
没有内置的“漂亮”方法可以将原始类转换为包装类,因此您必须使用这样的映射:
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);
}
或者使用其他方式之一here。
一旦你这样做了,你就可以检查 fieldType
是否是一个原语。如果是,则通过在映射中查找包装器类型来更改方法类型的返回类型/参数类型。
对于吸气剂:
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeReturnType(map.get(fieldType));
}
对于二传手:
MethodType type = target.type();
if (fieldType.isPrimitive()) {
type = type.changeParameterType(1,map.get(fieldType));
}
以防万一它不清楚,调用者将传递原始 getter 和 setter 的原始类:
createSetterViaMethodname(Main.class,"setFoo",int.class)
// for a setter declared like this:
public void setFoo(int i) {
...
}
完整代码:
public class Main {
public static void main(String[] args) throws Throwable {
// this prints 1234567
createSetterViaMethodname(Main.class,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,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 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,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 );
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 );
}
,
另一种方法是使用外部库而不是 LambdaMetafactory。 Cojen/Maker 库提供对代码生成的直接控制,并自动执行装箱/拆箱转换。
即使字段类型是 int,这个例子也能正常工作。调用者必须直接提供查找类才能访问任何非公共方法,但这在原始示例中也是必要的。这意味着未使用 <T>
参数,但此示例应该足够了。
import org.cojen.maker.ClassMaker;
import org.cojen.maker.MethodMaker;
public class SetterGetterMaker {
public static <T,R> createGetterViaMethodname(MethodHandles.Lookup lookup,String methodName)
throws Throwable
{
ClassMaker cm = ClassMaker.begin(null,lookup).implement(IGetter.class);
cm.addConstructor();
MethodMaker mm = cm.addMethod(Object.class,Object.class).public_();
mm.return_(mm.param(0).cast(lookup.lookupClass()).invoke(methodName));
var newLookup = cm.finishHidden();
var newClass = newLookup.lookupClass();
var ctorHandle = newLookup.findConstructor(newClass,MethodType.methodType(void.class));
return (IGetter<T,R>) ctorHandle.invoke();
}
public static <T,I> createSetterViaMethodname(MethodHandles.Lookup lookup,String methodName,Class<I> fieldType )
throws Throwable
{
ClassMaker cm = ClassMaker.begin(null,lookup).implement(ISetter.class);
cm.addConstructor();
MethodMaker mm = cm.addMethod(void.class,Object.class,Object.class).public_();
var fieldVar = mm.param(1).cast(fieldType);
mm.param(0).cast(lookup.lookupClass()).invoke(void.class,null,fieldVar);
var newLookup = cm.finishHidden();
var newClass = newLookup.lookupClass();
var ctorHandle = newLookup.findConstructor(newClass,MethodType.methodType(void.class));
return (ISetter<T,I>) ctorHandle.invoke();
}
}