【问题标题】:LambdaMetaFactory boxing / unboxing parameters and return typesLambdaMetaFactory 装箱/拆箱参数和返回类型
【发布时间】:2021-09-18 22:22:46
【问题描述】:

我有以下两种方法:

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

包括以下两个接口:

@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 干净且易于使用。我愿意为这里的装箱/拆箱带来小小的性能损失。

【问题讨论】:

  • “导致异常”什么异常

标签: java lambda reflection methodhandle lambda-metafactory


【解决方案1】:

没有将原始类转换为包装类的内置“漂亮”方法,因此您必须使用这样的映射:

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

【讨论】:

  • 谢谢,这对我来说已经足够了。由于它不使用库,因此非常完美。
  • 其实你可以简单的使用if(fieldType.isPrimitive()) type = type.wrap();作为getter。对于 setter,您必须链接 .changeReturnType(void.class) 以抵消将 void 更改为 Void 的影响,但它仍然比维护自己的 Map 更简单。
【解决方案2】:

另一种方法是使用外部库而不是 LambdaMetafactory。 Cojen/Maker 库提供对代码生成的直接控制,并自动执行装箱/拆箱转换。

即使字段类型是 int,此示例也能正常工作。调用者必须直接提供查找类才能访问任何非公共方法,但这在原始示例中也是必需的。这意味着没有使用&lt;T&gt; 参数,但这个例子就足够了。

import org.cojen.maker.ClassMaker;
import org.cojen.maker.MethodMaker;

public class SetterGetterMaker {
    public static <T, R> IGetter<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, "get", 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> ISetter<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, "set", Object.class, Object.class).public_();
        var fieldVar = mm.param(1).cast(fieldType);
        mm.param(0).cast(lookup.lookupClass()).invoke(void.class, methodName, 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();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 2011-07-03
    • 2011-02-28
    • 1970-01-01
    相关资源
    最近更新 更多