fge*_*fge 11 java java-7 methodhandle
对于我的一个项目,我必须对构造函数进行动态调用.但由于这是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");
}
}
Run Code Online (Sandbox Code Playgroud)
好的,这很有效.
我遇到的问题是这一行:
return c.cast(handleMap.get(c).invoke(arg));
Run Code Online (Sandbox Code Playgroud)
如果我更换invoke有invokeExact,我得到这个堆栈跟踪:
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)
Run Code Online (Sandbox Code Playgroud)
我不太清楚.两者GlobPathMatcher并RegexPathMatcher用一个构造函数具有String作为参数,并MethodType为双方因此,什么是定义CONSTRUCTOR_TYPE.如果不是,我无论如何都不能"抓住" MethodHandle.
然而我得到了一个WrongMethodTypeException.为什么?
编辑:这是我读完答案后的代码; 现在我不需要中间地图:我只需要有一个地图,将a映射String到MethodHandle:
@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, 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 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", throwable);
}
}
protected final void registerPathMatcher(@Nonnull final String name,
@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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jef*_*oom 13
当编译器发出invokeExact调用时,它会将Object记录为预期的返回类型.从MethodHandle javadoc(强调我的):
与通常的虚方法一样,源级调用invokeExact并调用compile到invokevirtual指令.更不寻常的是,编译器必须记录实际的参数类型,并且可能不会对参数执行方法调用转换.相反,它必须根据自己未转换的类型将它们推入堆栈.方法句柄对象本身在参数之前被压入堆栈.然后,编译器使用符号类型描述符调用方法句柄,该描述符描述参数和返回类型.
要发出完整的符号类型描述符,编译器还必须确定返回类型.这是基于对方法调用表达式的强制转换(如果有),或者如果调用是表达式则为Object,否则如果调用是语句则为void.演员表可以是原始类型(但不是空白).
在运行时,方法句柄实际上返回RegexPathMatcher,因此invokeExact失败并出现WrongMethodTypeException.
您需要使用(编译时)强制转换显式指定返回类型:
return (RegexPathMatcher)handleMap.get(c).invokeExact(arg);
Run Code Online (Sandbox Code Playgroud)
除非您需要在不同的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);
Run Code Online (Sandbox Code Playgroud)
经过 3 年的发帖,我来读这个,虽然答案确实是正确的,但很难掌握所有内容。因此,恕我直言,我将发布一种稍微不同的方法(以防像我这样的人不得不挠头两次才能真正理解)。
这里的主要问题是两个不同的调用:invoke和invokeExact。但是首先,源代码中的这两个方法都被注释了
@多态签名
也称为compiler overloads。java编译器对这些方法进行了非常特殊的处理——没有其他方法以相同的方式处理。
为了理解,让我们提供一个例子。这是一个具有单一方法的简单类:
static class Calle {
public Object go(Object left, Object right) {
// do something with left and right
return new Object();
}
}
Run Code Online (Sandbox Code Playgroud)
编译它并查看生成的字节码是什么样子的 ( javap -c Calle.class)。在一些行中会有这个方法:
public java.lang.Object go(java.lang.Object, java.lang.Object);
它的签名是:two arguments of type java.lang.Object and a return of type java.lang.Object。到现在为止还挺好。
所以这样做是完全合法的:
Calle c = new Calle();
int left = 3;
int right = 4;
c.go(left, right);
Run Code Online (Sandbox Code Playgroud)
其字节码将如下所示:
invokevirtual #5 // Method CompilerOverloads$Calle.go:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
该方法需要两个对象,两个整数作为参数传递是完全合法的。
现在想想方法的定义:
MethodHandle#invoke
Run Code Online (Sandbox Code Playgroud)
它的签名是 java.lang.Object var arg 并返回一个 java.lang.Object。
那么这段代码将如何编译?
Lookup l = MethodHandles.lookup();
MethodType type = MethodType.methodType(Object.class, Object.class, Object.class);
MethodHandle handle = l.findVirtual(Calle.class, "go", type);
Object result = handle.invoke(c, left, right); // what is generated here?
Run Code Online (Sandbox Code Playgroud)
有趣的是,它的编译与我们的非常不同 Calle::go
Method java/lang/invoke/MethodHandle.invoke:(LCalle;II)Ljava/lang/Object;
Run Code Online (Sandbox Code Playgroud)
它的输入参数是:Integer, Integer,返回类型是java.lang.Object。这就像编译器信任编译时方法声明并从中生成方法签名。
如果我们想将返回类型更改int为例如,我们需要在编译时将其指定为强制转换:
int result = (int) handle.invoke(c, left, right);
Run Code Online (Sandbox Code Playgroud)
然后它在字节码级别签名更改(重点是我的):
方法 java/lang/invoke/MethodHandle.invoke:(LCalle; II)I
据我所知,这在 jdk 世界的其他任何地方都不会发生。
现在invokevs的问题invokeExact变得有点明显(一个是精确的签名,另一个是更松散的)。