LambdaMetaFactory 装箱/拆箱参数和返回类型

问题描述

我有以下两种方法

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

这适用于所有类类型,包括原始类型的数字包装器,例如 Integerint。但是,如果我有一个接受 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();
    }
}