LoginSignup
7
5

More than 5 years have passed since last update.

Java8 Proxyでインターフェースデフォルトメソッドを呼び出す

Last updated at Posted at 2016-03-01

Proxyでインターフェースデフォルトメソッドを呼び出す

調べた限り、普通にはできない様子。
今回の方法は、リフレクションでどこぞのコンストラクタに無理やりアクセスするので保証はされないソースになってしまっています。

ちゃんとした(オフィシャルな)方法があるなら知りたいです。

参考:
https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/

    /**
     * デフォルトメソッド実行
     */
    private static Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        //コンストラクタにリフレクションでアクセス
        Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        constructor.setAccessible(true);

        Class<?> declaringClass = method.getDeclaringClass();
        int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;
        return constructor.newInstance(declaringClass, allModes)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
    }

実行

            String[] array = { "a", "b", "c" };
            @SuppressWarnings("unchecked")
            List<String> proxyList = (List<String>) Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { List.class },
                    (proxy, method, args) -> {
                        if (method.isDefault()) {
                            return invokeDefaultMethod(proxy, method, args);
                        } else if (method.getName().equals("iterator") && method.getParameterCount() == 0) {
                            //forEach()内部ではiterator()が必要
                            return Arrays.stream(array).iterator();
                        }

                        throw new UnsupportedOperationException(method.toString());
                    });
            //デフォルトメソッドのforEachを実行
            proxyList.forEach(System.out::print);//abc
            System.out.println();

ダメなパターン

普通にこう書きたいところ。

    private static Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> declaringClass = method.getDeclaringClass();

        return MethodHandles.lookup().in(declaringClass)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
    }

しかしこれだとエラー
残念。

Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at com.sun.proxy.$Proxy0.forEach(Unknown Source)
    at hogehoge.XXXXXX.main(XXXXXX.java:XX)
Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface java.lang.Iterable, from java.lang.Iterable/public
    at java.lang.invoke.MemberName.makeAccessException(MemberName.java:852)
    at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1568)
    at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1227)
    at hogehoge.XXXXXX.invokeDefaultMethod(XXXXXX.java:XX)
    at hogehoge.XXXXXX.lambda$1(XXXXXX.java:XX)
    ... 2 more
7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5