Given this simple "Hello World"ish Java 8 interface, how do I invoke its hello() method via reflection?
public interface Hello {
default String hello() {
return "Hello";
}
}
Given this simple "Hello World"ish Java 8 interface, how do I invoke its hello() method via reflection?
public interface Hello {
default String hello() {
return "Hello";
}
}
Unfortunately, there doesn't seem to be an ideal solution that works on all of JDK 8, 9, 10, which behave differently. I've run into issues when fixing an issue in jOOR. I've also blogged about the correct solution here in detail.
In Java 8, the ideal approach uses a hack that accesses a package-private constructor from 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();
}
}
This is the only approach that works with both private-accessible and private-inaccessible interfaces. However, the above approach does illegal reflective access to JDK internals, which will no longer work in a future JDK version, or if --illegal-access=deny
is specified on the JVM.
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();
}
}
Simply implement both of the above solutions and check if your code is running on JDK 8 or on a later JDK and you'll be fine. Until you're not :)
You could use MethodHandles for that:
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
}
}
Huge thanks to Lukas. Here is his answer with the Java 8 vs 9+ check and support for non-void returns and arguments. Be sure to give his answer an upvote.
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
You can't call it directly, as you need an instance of an implementing class. And for that, you need an implementing class. default
method is not a static
method, and neither you can create an instance of an interface.
So, suppose you've an implementing class:
class HelloImpl implements Hello { }
You can invoke the method like this:
Class<HelloImpl> clazz = HelloImpl.class;
Method method = clazz.getMethod("hello");
System.out.println(method.invoke(new HelloImpl())); // Prints "Hello"
I've found a solution to creating instances from interfaces like the above reflectively using code from sun.misc.ProxyGenerator
which defines a class HelloImpl
by assembling bytecode. Now I'm able to write:
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);
...but that's pretty ugly.