如何反射性地调用 Java 8 默认方法

2022-09-02 23:30:22

给定这个简单的“Hello World”式Java 8接口,我如何通过反射调用它的hello()方法?

public interface Hello {
    default String hello() {
        return "Hello";
    }
}

答案 1

您可以使用方法处理程序

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ReflectiveDefaultMethodCallExample {

    static interface Hello {
        default String hello() {
            return "Hello";
        }
    }

    public static void main(String[] args) throws Throwable{

        Hello target =
                //new Hello(){};
                (Hello)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{Hello.class}, (Object proxy, Method method, Object[] arguments) -> null);
        Method method = Hello.class.getMethod("hello");

        Object result = MethodHandles.lookup()
                                     .in(method.getDeclaringClass())
                                     .unreflectSpecial(method,method.getDeclaringClass())
                                     .bindTo(target)
                                     .invokeWithArguments();
        System.out.println(result); //Hello
    }
}

答案 2

不幸的是,似乎没有一个理想的解决方案可以适用于所有JDK 8,9,10,它们的行为不同。我在jOOR中修复问题时遇到了问题我还在这里详细记录了正确的解决方案

此方法适用于 Java 8

在Java 8中,理想的方法使用一种从以下位置访问包私有构造函数的hack:Lookup

import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                Constructor<Lookup> constructor = Lookup.class
                    .getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);
                constructor.newInstance(Duck.class)
                    .in(Duck.class)
                    .unreflectSpecial(method, Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}

这是同时适用于私有可访问和私有无法访问接口的唯一方法。但是,上述方法确实非法地对 JDK 内部进行反射访问,这些访问在将来的 JDK 版本中将不再有效,或者在 JVM 上指定。--illegal-access=deny

此方法适用于 Java 9 和 10,但不适用于 Java 8

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Proxy;

interface Duck {
    default void quack() {
        System.out.println("Quack");
    }
}

public class ProxyDemo {
    public static void main(String[] a) {
        Duck duck = (Duck) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] { Duck.class },
            (proxy, method, args) -> {
                MethodHandles.lookup()
                    .findSpecial( 
                         Duck.class, 
                         "quack",  
                         MethodType.methodType(void.class, new Class[0]),  
                         Duck.class)
                    .bindTo(proxy)
                    .invokeWithArguments();
                return null;
            }
        );

        duck.quack();
    }
}

溶液

只需实现上述两个解决方案,并检查您的代码是在JDK 8上运行还是在以后的JDK上运行,就可以了。直到你不:)