19

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";
    }
}
earcam
  • 6,662
  • 4
  • 37
  • 57
thekid
  • 246
  • 2
  • 8
  • related: http://stackoverflow.com/questions/37812393/how-to-explicitly-invoke-default-method-from-a-dynamic-proxy as noted as dup. by @Rudziankoŭ – earcam Nov 08 '16 at 23:50

5 Answers5

17

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.

This approach works in Java 8

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.

This approach works on Java 9 and 10, but not 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();
    }
}

Solution

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 :)

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • I don't think this solution is complete, for the general case, where the class reference (`Duck.class`) is passed in as a parameter. The class may be a concrete class that inherited the default method from an interface that it implements -- or from a superclass -- or from an interface that inherited the default method from a superinterface -- or from a superclass that inherited the method from a superinterface. You have to iterate through all superclasses, collecting unique implemented interfaces, then find the transitive closure of superinterfaces, then try each one. – Luke Hutchison Jun 13 '18 at 19:29
  • @LukeHutchison. The use-case is to pass the interface that holds the default method itself. That's what the OP wanted. I'm not sure what you're looking for. – Lukas Eder Jun 14 '18 at 09:04
16

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
    }
}
Didier L
  • 18,905
  • 10
  • 61
  • 103
Thomas Darimont
  • 1,356
  • 11
  • 14
  • 2
    Nice idea as it gives a hint how an `InvocationHandler` can delegate to a default method. That can be really useful in other scenarios. – Holger Aug 25 '14 at 12:17
  • 4
    This is correct, except that it will fail for any interface defined in a file outside the one used to lookup the handles. I used the following modification: final Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP"); field.setAccessible(true); final Lookup lookup = (Lookup) field.get(null); final Object value = lookup .unreflectSpecial(method, method.getDeclaringClass()) .bindTo(t) .invokeWithArguments(); – Ajax Jan 30 '15 at 12:01
  • 2
    The comments of this article also have other workarounds https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/ the best probably being to grab the constructor of the Lookup class so you can create a lookup with PRIVATE access, instead of accessing the full-permission IMPL_LOOKUP field as I did above. – Ajax Jan 30 '15 at 12:02
  • Just to clarify... MethodHandles have reflection protections about who can access what methods, so you need to get the universal Lookup object from the Lookup class instead of using the one provided by MethodHandles.lookup – Ajax Jul 18 '16 at 06:49
7

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
David Blevins
  • 19,178
  • 3
  • 53
  • 68
4

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"
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • I should've been more precise in my question: How do I *instantiate it* and invoke ist hello() method - *both* via reflection? – thekid Mar 24 '14 at 20:40
  • 2
    @thekid You can't instantiate an interface. You need an implementing class, which I've shown in the answer. – Rohit Jain Mar 24 '14 at 20:43
3

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.

thekid
  • 246
  • 2
  • 8
  • that has to be ugly, otherwise someone would want to use it! – Michal Gruca Jun 02 '14 at 21:17
  • Definitely more work than is necessary. The access can be done via MethodHandles that will not require large dependencies or excessive runtime work. – Ajax Jan 30 '15 at 12:04