首页 > 解决方案 > 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 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 );
}

这适用于所有类类型,包括用于原始类型的数字包装器,例如Integerfor int。但是,如果我有一个接受 an 的 setterint或一个返回 an 的 getter ìnt,它会尝试传递/返回 Number-Wrapper,从而导致异常。

无需使用其他方法即可将其装箱/拆箱的正确方法是什么。这里的原因是保持 API 干净且易于使用。我愿意为这里的拳击/拆箱带来小小的性能损失。

标签: javalambdareflectionmethodhandlelambda-metafactory

解决方案


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

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

或者在这里使用其他方法之一。

一旦你这样做了,你就可以检查是否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 );
}

推荐阅读