当前位置: 首页 > 知识库问答 >
问题:

Java 8 Lambda上的反射类型推断

甄鹏云
2023-03-14

我在Java 8中试验新的lambda,我正在寻找一种方法,在lambda类上使用反射来获得lambda函数的返回类型。我对lambda实现通用超接口的情况特别感兴趣。在下面的代码示例中,MapFunction

虽然Java在编译后丢弃了很多泛型类型信息,但泛型超类和泛型超接口的子类(和匿名子类)确实保留了这些类型信息。通过反射,可以访问这些类型。在下面的示例(案例1)中,反射告诉my,映射函数的实现绑定了java。lang.Integer到泛型类型参数T。

即使对于本身是泛型的子类,如果已知其他一些泛型参数,也有某些方法可以找出与泛型参数绑定的内容。考虑下例中的情况2,IdentityMapper,其中F和T都绑定到同一类型。当我们知道时,如果我们知道参数类型T(在我的例子中我们知道),我们就知道类型F。

现在的问题是,我如何才能为Java8 lambda实现类似的东西?由于它们实际上不是通用超接口的常规子类,因此上述方法不起作用。具体来说,我能否弄清楚parseLambda绑定java.lang.整数T标识Lambda绑定相同到FT

PS:理论上应该可以反编译lambda代码,然后使用嵌入式编译器(如JDT)并利用其类型推断。我希望有更简单的方法来做到这一点;-)

/**
 * The superinterface.
 */
public interface MapFunction<F, T> {

    T map(F value);
}

/**
 * Case 1: A non-generic subclass.
 */
public class MyMapper implements MapFunction<String, Integer> {

    public Integer map(String value) {
        return Integer.valueOf(value);
    }
}

/**
 * A generic subclass
 */
public class IdentityMapper<E> implements MapFunction<E, E> {

    public E map(E value) {
        return value;
    }

}

/**
 * Instantiation through lambda
 */

public MapFunction<String, Integer> parseLambda = (String str) -> { return Integer.valueOf(str); }

public MapFunction<E, E> identityLambda = (value) -> { return value; }


public static void main(String[] args)
{
    // case 1
    getReturnType(MyMapper.class);    // -> returns java.lang.Integer

    // case 2
    getReturnTypeRelativeToParameter(IdentityMapper.class, String.class);    // -> returns java.lang.String
}

private static Class<?> getReturnType(Class<?> implementingClass)
{
    Type superType = implementingClass.getGenericInterfaces()[0];

    if (superType instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) superType;
        return (Class<?>) parameterizedType.getActualTypeArguments()[1];
    }
    else return null;
}

private static Class<?> getReturnTypeRelativeToParameter(Class<?> implementingClass, Class<?> parameterType)
{
    Type superType = implementingClass.getGenericInterfaces()[0];

    if (superType instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) superType;
        TypeVariable<?> inputType = (TypeVariable<?>) parameterizedType.getActualTypeArguments()[0];
        TypeVariable<?> returnType = (TypeVariable<?>) parameterizedType.getActualTypeArguments()[1];

        if (inputType.getName().equals(returnType.getName())) {
            return parameterType;
        }
        else {
            // some logic that figures out composed return types
        }
    }

    return null;
}

共有3个答案

宗政坚白
2023-03-14

我最近为TypeTools添加了对解析lambda类型参数的支持。例如:

MapFunction<String, Integer> fn = str -> Integer.valueOf(str);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());

解析的类型参数如预期的那样:

assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;

要处理传递的lambda:

public void call(Callable<?> c) {
  // Assumes c is a lambda
  Class<?> callableType = TypeResolver.resolveRawArguments(Callable.class, c.getClass());
}

注意:底层实现使用@danielbodart概述的ConstantPool方法,该方法已知可用于Oracle JDK和OpenJDK(可能还有其他)。

韩刚洁
2023-03-14

目前这是可以解决的,但只能以一种非常简单的方式解决,但让我先解释几件事:

当您编写lambda时,编译器会插入指向LambdaMetafactory的动态调用指令和带有lambda主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果lambda使用该类型或如您的示例中那样显式)。

现在在运行时调用LambdaMetaFactory,并使用ASM生成一个类,该类实现函数接口,然后方法的主体调用私有静态方法并传递任何参数。然后使用Unsafe.define匿名类将其注入原始类(参见John Rose帖子),以便它可以访问私有成员等。

不幸的是,生成的类不存储泛型签名(它可以),因此您不能使用常见的反射方法来避免擦除

对于普通类,您可以使用Class.get资源(ClassName". class")检查字节码,但对于使用Unsecurity定义的匿名类,您运气不佳。但是,您可以使用JVM参数将LambdaMetaFactory转储它们:

java -Djdk.internal.lambda.dumpProxyClasses=/some/folder

通过查看转储的类文件(使用javap-p-s-v),可以看到它确实调用了静态方法。但是问题仍然是如何从Java本身中获取字节码。

不幸的是,这就是它被攻击的地方:

使用反射,我们可以调用类。getConstantPool,然后访问MethodRefInfo以获取类型描述符。然后我们可以使用ASM来解析它并返回参数类型。总而言之:

Method getConstantPool = Class.class.getDeclaredMethod("getConstantPool");
getConstantPool.setAccessible(true);
ConstantPool constantPool = (ConstantPool) getConstantPool.invoke(lambda.getClass());
String[] methodRefInfo = constantPool.getMemberRefInfoAt(constantPool.size() - 2);

int argumentIndex = 0;
String argumentType = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodRef[2])[argumentIndex].getClassName();
Class<?> type = (Class<?>) Class.forName(argumentType);

更新了Jonathan的建议

现在理想情况下,LambdaMetaFactory生成的类应该存储泛型类型签名(我可能会看看我是否可以向OpenJDK提交补丁),但目前这是我们能做的最好的了。上面的代码有以下问题:

  • 它使用未记录的方法和类
  • 它极易受到JDK中代码更改的影响
  • 它不保留泛型类型,所以如果您传递列表
齐乐逸
2023-03-14

如何将lambda代码映射到接口实现的确切决定权留给实际的运行时环境。原则上,实现相同原始接口的所有lambda可以共享一个运行时类,就像MethodHandleProxies一样。为特定的lambda使用不同的类是由实际的LambdaMetafactory实现执行的优化,而不是旨在帮助调试或反射的功能。

因此,即使您在lambda接口实现的实际运行时类中找到更详细的信息,它也将是当前使用的运行时环境的工件,在不同的实现甚至当前环境的其他版本中可能不可用。

如果lambda是可序列化的,则可以使用序列化表单包含实例化接口类型的方法签名这一事实来将实际的类型变量值拼凑在一起。

 类似资料:
  • 问题内容: 我对反射库有问题。我试图动态加载实现特定接口的所有类。只要我不在这些类中使用lambda表达式(Java 8),一切就可以正常工作(所有类都已加载)。我尝试升级lib版本,但效果是相同的(java.io.IOException:无效的常量类型:18)。 依赖关系并在pom.xml中构建 没有排除是一样的效果。 码: 如何使用lambda表达式加载类? PS对不起,英语:) 问题答案:

  • 本文向大家介绍java反射之获取类的信息方法(推荐),包括了java反射之获取类的信息方法(推荐)的使用技巧和注意事项,需要的朋友参考一下 本文接上文“老生常谈反射之Class类的使用(必看篇)”,以编写一个用来获取类的信息(成员函数、成员变量、构造函数)的工具类来讲解"反射之获取类的信息" 1、获取成员函数信息 2、获取成员变量信息 3、获取构造函数信息 4、工具类代码 以上这篇java反射之获

  • 主要内容:理解反射的类型(Type)与种类(Kind)在 Go语言中通过调用 reflect.TypeOf 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。当然,我们也可以将一个接口值传递给一个 reflect.TypeOf 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。 实际上,r

  • 问题内容: 我有以下签名的方法: 它读取json字符串,并将其转换为适当类型的对象的列表,例如。整数,字符串等 是否有一种可靠的方法可以通过从参数中推断出参数来将其从方法中删除?例如。有没有办法可以在不丢失功能的情况下将方法签名更改为以下内容? 似乎该解决方案将需要对ParameterizedType进行一些精美的操作。我看了以下内容,但是我使用的方法不正确,或者它们没有达到我的期望: 来自htt

  • 石英创建方法

  • 如果我有两个具有相同签名的方法,仅根据vararg类型变化,Java如何知道在我不使用参数或传递null时调用哪个方法? 例如,如果我有以下两种方法: 我打电话给: 输出是“字符串args”。为什么Java决定在这里调用第二个方法而不是第一个方法?我很难找到描述这一点的规范的相关部分。