lambdametafactory装箱/取消装箱参数和返回类型

r1zk6ea1  于 2021-08-20  发布在  Java
关注(0)|答案(2)|浏览(429)

我有以下两种方法:

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

这适用于所有类类型,包括基本类型的数字 Package 器,例如 Integer 对于 int . 然而,如果我有一个二传手 int 或者返回 ìnt ,它尝试传递/返回数字 Package ,导致异常。
在不必使用其他方法的情况下,装箱/取消装箱的正确方法是什么。这里的原因是为了保持api干净且易于使用。我愿意在这里为拳击/拆箱表演一次小小的打击。

5t7ly7z5

5t7ly7z51#

没有内置的“漂亮”方法将基本类转换为 Package 类,因此必须使用如下Map:

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 是原始的。如果是,请通过在Map中查找 Package 器类型来更改方法类型的返回类型/参数类型。
对于getter:

MethodType type = target.type();
if (fieldType.isPrimitive()) {
  type = type.changeReturnType(map.get(fieldType));
}

对于setter:

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

dauxcl2d2#

另一种方法是使用外部库而不是lambdametafactory。cojen/maker库提供对代码生成的直接控制,并自动执行装箱/拆箱转换。
即使字段类型为int,此示例也可以正常工作。调用方必须直接提供lookup类才能访问任何非公共方法,但在原始示例中这也是必需的。这意味着 <T> 未使用param,但此示例应该足够了。

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

相关问题