Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I invoke Java 8 default methods reflectively

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";
    }
}
like image 592
thekid Avatar asked Mar 24 '14 16:03

thekid


People also ask

Can we call default method in Java 8?

Interfaces can have default methods with implementation in Java 8 on later. Interfaces can have static methods as well, similar to static methods in classes. Default methods were introduced to provide backward compatibility for old interfaces so that they can have new methods without affecting existing code.

How do you call a default method?

Default methods have an implemented function body. To call a method from the super class you can use the keyword super , but if you want to make this with a super interface it's required to name it explicitly.


2 Answers

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
    }
}
like image 77
Thomas Darimont Avatar answered Sep 19 '22 12:09

Thomas Darimont


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

like image 28
Lukas Eder Avatar answered Sep 21 '22 12:09

Lukas Eder