15

私のプロジェクトの 1 つで、コンストラクターを動的に呼び出す必要があります。しかし、これは 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));

に置き換えるinvokeinvokeExact、次のスタック トレースが得られます。

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)

よくわかりません。と の両方が引数としてa を持つ単一のコンストラクターを使用するGlobPathMatcherため、 for both は で定義されているものです。そうでなければ、とにかく sを「つかむ」ことはできませんでした。RegexPathMatcherStringMethodTypeCONSTRUCTOR_TYPEMethodHandle

それでも私はWrongMethodTypeException. なんで?


編集:答えを読んだ後のコードは次のとおりです。今は中間マップは必要ありません: aStringを aにマッピングする 1 つのマップが必要です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);
        }
    }
}
4

2 に答える 2

16

コンパイラは、invokeExact 呼び出しを発行すると、予期される戻り値の型として Object を記録します。MethodHandle javadoc から (強調鉱山):

通常の仮想メソッドと同様に、invokeExact および invoke へのソースレベルの呼び出しは、invokevirtual 命令にコンパイルされます。さらに珍しいことに、コンパイラは実際の引数の型を記録する必要があり、引数に対してメソッド呼び出しの変換を実行しない場合があります。代わりに、変換されていない独自の型に従ってスタックにプッシュする必要があります。メソッド ハンドル オブジェクト自体は、引数の前にスタックにプッシュされます。次に、コンパイラは、引数と戻り値の型を記述するシンボリック型記述子を使用してメソッド ハンドルを呼び出します。

完全なシンボリック型記述子を発行するには、コンパイラは戻り値の型も決定する必要があります。これは、メソッド呼び出し式 (存在する場合) のキャストに基づいています。呼び出しが式の場合は Object であり、呼び出しがステートメントの場合は void です。キャストはプリミティブ型にすることができます (ただし、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);
于 2014-12-03T18:43:00.267 に答える