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

the*_*kid 14 java reflection interface java-8

鉴于这个简单的"Hello World"是Java 8接口,如何通过反射调用其hello()方法?

public interface Hello {
    default String hello() {
        return "Hello";
    }
}
Run Code Online (Sandbox Code Playgroud)

Tho*_*ont 15

您可以使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的,除了它将在用于查找句柄的文件之外的文件中定义的任何接口失败.我使用了以下修改:final Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP"); field.setAccessible(真); final Lookup lookup =(Lookup)field.get(null); final Object value = lookup .unreflectSpecial(method,method.getDeclaringClass()).bindTo(t).invokeWithArguments(); (4认同)
  • 好主意,因为它给出了一个提示,“InvocationHandler”如何委托给默认方法。这在其他场景中非常有用。 (2认同)
  • 本文的评论还有其他解决方法https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/最好的可能是抓住Lookup类的构造函数,因此您可以使用PRIVATE访问创建查找,而不是像上面那样访问完全权限IMPL_LOOKUP字段. (2认同)

Luk*_*der 13

不幸的是,似乎没有一个理想的解决方案适用于所有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<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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

这种方法适用于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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Dav*_*ins 7

非常感谢卢卡斯。这是他对 Java 8 与 9+ 检查以及对非 void 返回和参数的支持的回答。一定要给他的回答点赞。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;

public class ThanksLukas implements InvocationHandler {
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

        if (method.isDefault()) {
            final float version = Float.parseFloat(System.getProperty("java.class.version"));
            if (version <= 52) {
                final Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);

                final Class<?> clazz = method.getDeclaringClass();
                return constructor.newInstance(clazz)
                        .in(clazz)
                        .unreflectSpecial(method, clazz)
                        .bindTo(proxy)
                        .invokeWithArguments(args);
            } else {
                return MethodHandles.lookup()
                        .findSpecial(
                                method.getDeclaringClass(),
                                method.getName(),
                                MethodType.methodType(method.getReturnType(), new Class[0]),
                                method.getDeclaringClass()
                        ).bindTo(proxy)
                        .invokeWithArguments(args);
            }
        }

        // your regular proxy fun here
Run Code Online (Sandbox Code Playgroud)


Roh*_*ain 5

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

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

class HelloImpl implements Hello {  }
Run Code Online (Sandbox Code Playgroud)

您可以像这样调用方法:

Class<HelloImpl> clazz = HelloImpl.class;
Method method = clazz.getMethod("hello");
System.out.println(method.invoke(new HelloImpl()));  // Prints "Hello"
Run Code Online (Sandbox Code Playgroud)

  • @thekid您无法实例化接口.你需要一个实现类,我已在答案中显示. (2认同)

the*_*kid 3

找到了一种解决方案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);
Run Code Online (Sandbox Code Playgroud)

……但这太难看了。