java – 为什么我不能.invokeExact()在这里,即使MethodType是OK?

前端之家收集整理的这篇文章主要介绍了java – 为什么我不能.invokeExact()在这里,即使MethodType是OK?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
对于我的一个项目,我必须对构造函数进行动态调用.但由于这是 Java 7,而是使用“经典”反射API,我使用java.lang.invoke.

码:

@ParametersAreNonnullByDefault
public class PathMatcherProvider
{
    private static final MethodHandles.Lookup LOOKUP
        = MethodHandles.publicLookup();
    private static final MethodType CONSTRUCTOR_TYPE
        = MethodType.methodType(void.class,String.class);

    private final Map<String,Class<? extends PathMatcher>> classMap
        = new HashMap<>();
    private final Map<Class<? extends PathMatcher>,MethodHandle> handleMap
        = new HashMap<>();

    public PathMatcherProvider()
    {
        registerPathMatcher("glob",GlobPathMatcher.class);
        registerPathMatcher("regex",RegexPathMatcher.class);
    }

    public final PathMatcher getPathMatcher(final String name,final String arg)
    {
        Objects.requireNonNull(name);
        Objects.requireNonNull(arg);

        final Class<? extends PathMatcher> c = classMap.get(name);
        if (c == null)
            throw new UnsupportedOperationException();

        try {
            return c.cast(handleMap.get(c).invoke(arg));
        } catch (Throwable throwable) {
            throw new RuntimeException("Unhandled exception",throwable);
        }
    }

    protected final void registerPathMatcher(@Nonnull final String name,@Nonnull final Class<? extends PathMatcher> matcherClass)
    {
        Objects.requireNonNull(name);
        Objects.requireNonNull(matcherClass);
        try {
            classMap.put(name,matcherClass);
            handleMap.put(matcherClass,findConstructor(matcherClass));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            throw new RuntimeException("cannot find constructor",e);
        }
    }

    private static <T extends PathMatcher> MethodHandle findConstructor(
        final Class<T> matcherClass)
        throws NoSuchMethodException,IllegalAccessException
    {
        Objects.requireNonNull(matcherClass);
        return LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE);
    }

    public static void main(final String... args)
    {
        new PathMatcherProvider().getPathMatcher("regex","^a");
    }
}

好的,这个工作.

我遇到的问题是这一行:

return c.cast(handleMap.get(c).invoke(arg));

如果我用invokeExact替换invoke,我得到这个堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException: Unhandled exception
    at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:62)
    at com.github.fge.filesystem.path.matchers.PathMatcherProvider.main(PathMatcherProvider.java:89)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.invoke.WrongMethodTypeException: expected (String)RegexPathMatcher but found (String)Object
    at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:350)
    at java.lang.invoke.Invokers.checkExactType(Invokers.java:361)
    at com.github.fge.filesystem.path.matchers.PathMatcherProvider.getPathMatcher(PathMatcherProvider.java:60)

我不太明白GlobPathMatcher和RegexPathMatcher都使用一个构造函数,其中一个String作为参数,因此,两者的MethodType都是在CONSTRUCTOR_TYPE中定义的.如果不是,我无法“抓住”MethodHandles.

但我得到一个WrongMethodTypeException.为什么?

编辑:这是我看完答案后的代码现在我不需要中间地图:我只需要有一个地图,将一个String映射到一个MethodHandle:

@ParametersAreNonnullByDefault
public class PathMatcherProvider
{
    private static final MethodHandles.Lookup LOOKUP
        = MethodHandles.publicLookup();
    private static final MethodType CONSTRUCTOR_TYPE
        = MethodType.methodType(void.class,final String arg)
    {
        Objects.requireNonNull(name);
        Objects.requireNonNull(arg);

        final MethodHandle handle = handleMap.get(name);
        if (handle == null)
            throw new UnsupportedOperationException();

        try {
            return (PathMatcher) handle.invokeExact(arg);
        } catch (Throwable throwable) {
            throw new RuntimeException("Unhandled exception",@Nonnull final Class<? extends PathMatcher> matcherClass)
    {
        Objects.requireNonNull(name);
        Objects.requireNonNull(matcherClass);

        final MethodHandle handle;
        final MethodType type;

        try {
            handle = LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE);
            type = handle.type().changeReturnType(PathMatcher.class);
            handleMap.put(name,handle.asType(type));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            throw new RuntimeException("cannot find constructor",e);
        }
    }
}

解决方法

当编译器发出invokeExact调用时,它会将Object记录为预期的返回类型.从MethodHandle javadoc(强调我的):

As is usual with virtual methods,source-level calls to invokeExact and invoke compile to an invokevirtual instruction. More unusually,the compiler must record the actual argument types,and may not perform method invocation conversions on the arguments. Instead,it must push them on the stack according to their own unconverted types. The method handle object itself is pushed on the stack before the arguments. The compiler then calls the method handle with a symbolic type descriptor which describes the argument and return types.

To issue a complete symbolic type descriptor,the compiler must also determine the return type. This is based on a cast on the method invocation expression,if there is one,or else Object if the invocation is an expression or else void if the invocation is a statement. The cast may be to a primitive type (but not void).

在运行时,方法句柄实际上返回RegexPathMatcher,所以invokeExact失败了WrongMethodTypeException.

您需要使用(编译时)转换显式指定返回类型:

return (RegexPathMatcher)handleMap.get(c).invokeExact(arg);

除了需要通过不同的PathMatcher实现之外,您需要转换方法句柄以使用asType返回PathMatcher,然后使用PathMatcher作为预期的返回类型.

//in findConstructor
MethodHandle h = LOOKUP.findConstructor(matcherClass,CONSTRUCTOR_TYPE);
return h.asType(h.type().changeReturnType(PathMatcher.class));

//in getPathMatcher
return (PathMatcher)handleMap.get(c).invokeExact(arg);

猜你在找的Java相关文章