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

鉴于这个简单的“Hello World”ish Java 8接口,如何通过reflection调用其hello()方法?

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

您可以使用MethodHandles :

 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 } } 

您不能直接调用它,因为您需要实现类的实例。 为此,您需要一个实现类。 default方法不是static方法,也不能创建接口实例。

所以,假设你有一个实现类:

 class HelloImpl implements Hello { } 

您可以像这样调用方法:

 Class clazz = HelloImpl.class; Method method = clazz.getMethod("hello"); System.out.println(method.invoke(new HelloImpl())); // Prints "Hello" 

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

这种方法适用于Java 8

在Java 8中,理想的方法是使用从Lookup访问包私有构造函数的hack:

 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 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内部进行非法reflection访问,这将在将来的JDK版本中不再起作用,或者在JVM上指定了--illegal-access=deny

这种方法适用于Java 9和10,但不适用于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上运行,您就可以了。 直到你不是:)

我已经找到了一个解决方案来从上面的接口创建实例, sun.misc.ProxyGenerator使用来自sun.misc.ProxyGenerator代码,该代码通过汇编字节码来定义类HelloImpl 。 现在我可以写:

 Class clazz = Class.forName("Hello"); Object instance; if (clazz.isInterface()) { instance = new InterfaceInstance(clazz).defineClass().newInstance(); } else { instance = clazz.newInstance(); } return clazz.getMethod("hello").invoke(instance); 

……但那很难看。