【问题标题】:How to get type arguments of function objects with reflection?如何通过反射获取函数对象的类型参数?
【发布时间】:2018-06-13 20:14:26
【问题描述】:

我有一个用例,使用反射来获取Function对象的参数和返回值类型会很有用1。

但我发现 lambda 表达式导致的函数对象的令人惊讶的行为:该对象没有具有特定类型的 apply 方法。

示例代码:

import java.lang.reflect.Type;
import java.lang.reflect.Method;
import java.util.function.Function;

public class Trying {
    public static void main(String[] args) {
        Function<Integer, Double> lambda = i -> i.doubleValue();

        Function<Integer, Double> methodRef = Integer::doubleValue;

        Function<Integer, Double> anon = new Function<Integer, Double>() {
            @Override
            public Double apply(Integer i) {
                return i.doubleValue();
            }
        };

        printType("lambda", lambda);
        printType("methodRef", methodRef);
        printType("anon", anon);
    }
    private static void printType(String name, Function<?, ?> f) {
        System.out.println("### " + name);
        System.out.println("super: " + f.getClass().getSuperclass().getSimpleName());

        for (Type i : f.getClass().getGenericInterfaces()) {
            System.out.println("interface: " + i);
        }

        for (Method m : f.getClass().getDeclaredMethods()) {
            System.out.println("name: " + m.getName() + ", bridge: " + m.isBridge()
                + ", arg type: " + m.getParameterTypes()[0].getSimpleName()
                + ", return type: " + m.getReturnType().getSimpleName());
        }

        System.out.println();
    }
}

此代码打印以下内容:

### methodRef
super: Object
interface: interface java.util.function.Function
name: apply, bridge: false, arg type: Object, return type: Object

### lambda
super: Object
interface: interface java.util.function.Function
name: apply, bridge: false, arg type: Object, return type: Object

### anon
super: Object
interface: java.util.function.Function<java.lang.Integer, java.lang.Double>
name: apply, bridge: false, arg type: Integer, return type: Double
name: apply, bridge: true, arg type: Object, return type: Object
  1. 有没有办法解决这个问题并使用反射来获取函数对象的具体类型?
  2. 是语言指定的行为,还是依赖于实现?
  3. 为什么 lambda 和方法引用对象会以这种方式运行?

1:我想使用此信息为数据绑定 API 创建默认转换器。

【问题讨论】:

  • Related Q&A。我很确定这是故意的,而且没有解决办法。
  • 这与函数或 lambda 无关。这是 java 泛型的泛型行为。了解类型擦除。
  • @gagansingh:请注意,示例代码中的匿名类具有可用的特定类型,可以使用反射进行访问。一些通用信息,例如关于具有应用类型参数的超类,存储在类文件中。例如阅读bridge methods。
  • @Lii 感谢您的链接。
  • 为什么要“故意”这样做?似乎更像是一种疏忽。如果你能找到匿名类的泛型类型参数,为什么不能找到 lambdas 呢?我对这个设计决定感到失望。

标签: java reflection lambda


【解决方案1】:

首先,我要感谢您发布一个非常完整的代码 sn-p,只需要进行一些修改即可编译和运行。它让我们的事情变得更容易。

我的简短回答是不要这样做。编写依赖于这种反射工作的代码将是有问题的......可能有一些技巧可以让它像使用上面显示的内部类一样工作,但它最终会咬你,所以我的建议是避免这种做法。

相反,您可以创建一个“类型化”函数子类,使用这样的有效 Java 习惯用法来解决问题:

abstract class TypedFunction<T, U> implements Function<T, U> {
  final Class<T> inClass;
  final Class<U> outClass;

  TypedFunction(final Class<T> inClass, final Class<U> outClass) {
    this.inClass = inClass;
    this.outClass = outClass;
  }

  static <TT, UU> TypedFunction<TT, UU> make(
          final Class<TT> inClass, 
          final Class<UU> outClass,
          final Function<? super TT, ? extends UU> func) {
    return new TypedFunction<TT, UU>(inClass, outClass) {
            @Override
            UU apply(final TT in) {
              return func.apply(in);
            }
    };
  }
}

然后使用代码实例化这些函数的方式如下:

TypedFunction<Integer, Double> lambda = TypedFunction.make(Integer.class, Double.class, i -> i.doubleValue());

TypedFunction<Integer, Double> methodRef = TypedFunction.make(Integer.class, Double.class, Integer::doubleValue);

TypedFunction<Integer, Double> anon = TypedFunction.make(Integer.class, Double.class,  new Function<Integer, Double>() {
        @Override
        public Double apply(Integer i) {
            return i.doubleValue();
        }
};

TypedFunction<Integer, Double> anonSimple = 
        new TypedFunction<Integer, Double>(Integer.class, Double.class) { 
   @Override
   public Double apply(Integer i) {
      return i.doubleValue();
   }
};

反过来,您解析输入和输出类的反射代码只需访问传递的TypedFunction&lt;?, ?&gt; 上的inClass 和outClass 字段。当然,如果这些恰好是泛型类型,它会返回给您“原始”类型而不是泛型形式,但我认为我们应该离开。

如果包含Integer.class 和Double.class 看起来很乏味,您可以创建一个二阶函数,其构造函数采用T 和U 类引用,并且它本身就是一个从底层Function&lt;T, U&gt; 到其@987654331 的函数@包装的版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多