2

So i have been doing some things that involve Java MethodHandlers. And the reflection api has been around for a long time so there is a lot of documentation about it.

MethodHandlers on the other hand hasn't been around as long so there is a lot fewer examples. What im trying to do is to invoke a static void function that mutates a state using the MethodHandlers Api.

Here is a runnable example that demonstrates my problem:

class Main {
    public static void main(String[] args) throws Throwable {

        System.out.println("Times before: " + Foobar.getTimes());
        final Class<?> aClass = MethodHandles.lookup().findClass(Foobar.class.getName());

        incReflectionApi(aClass);
        incMethodHandler(aClass);

    }

    private static void incReflectionApi(Class<?> aClass) throws Throwable {
        final Method init = aClass.getMethod("increment", (Class<?>[]) null);
        init.invoke(null, (Object[]) null);

        System.out.println("Reflection api: " + Foobar.getTimes());
    }

    private static void incMethodHandler(Class<?> aClass) throws Throwable {

        // Here is where we throw
        final MethodHandle handle = MethodHandles.lookup().findStatic(aClass, "increment", MethodType.methodType(Void.class));
        handle.invoke();

        System.out.println("MethodHandler api: " + Foobar.getTimes());
    }

    public static class Foobar {

        private static int times;

        public static void increment() {
            times++;
        }

        public static int getTimes() {
            return times;
        }
    }
}

So basically, access the increment function and invoke it. Doing so with reflective api getMethod works fine. But when i try to use MethodHandler.lookup#findStatic i get a NoSuchMethodException.

Here is the output of the example:

Times before: 0
Reflection api: 1
Exception in thread "main" java.lang.NoSuchMethodException: no such method: Main$Foobar.increment()Void/invokeStatic
    at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:963)
    at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1101)
    at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:2030)
    at java.base/java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:1102)
    at Main.incMethodHandler(scratch_5.java:26)
    at Main.main(scratch_5.java:14)
Caused by: java.lang.NoSuchMethodError: 'java.lang.Void Main$Foobar.increment()'
    at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
    at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1070)
    at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1098)
    ... 4 more

Am i missing something obvious?

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54

1 Answers1

4

You specified Void.class for the expected return type which represents the wrapper type Void rather than the primitive void.

Here’s a tweaked example:

class Main {
    public static void main(String[] args) throws Throwable {
        System.out.println("Times before: " + Foobar.getTimes());
        final Class<?> aClass = Foobar.class;

        incReflectionApi(aClass);
        incUnreflect(aClass);
        incMethodHandle(aClass);
    }

    private static void incReflectionApi(Class<?> aClass) throws Throwable {
        final Method init = aClass.getMethod("increment");
        init.invoke(null);

        System.out.println("Reflection api: " + Foobar.getTimes());
    }

    private static void incUnreflect(Class<?> aClass) throws Throwable {
        final Method init = aClass.getMethod("increment");
        final MethodHandle handle = MethodHandles.lookup().unreflect(init);
        handle.invokeExact();

        System.out.println("Reflection to MH: " + Foobar.getTimes());
    }

    private static void incMethodHandle(Class<?> aClass) throws Throwable {
        final MethodHandle handle = MethodHandles.lookup()
            .findStatic(aClass, "increment", MethodType.methodType(void.class));
        handle.invokeExact();

        System.out.println("MethodHandle api: " + Foobar.getTimes());
    }

    public static class Foobar {
        private static int times;

        public static void increment() {
            times++;
        }

        public static int getTimes() {
            return times;
        }
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • i got it working 10 mins after the question, i have never seen `void.class` before, but you are completely correct. The error message was not helpfull and i didn't even know there was a `void.class` primitive It feels im dealing with dark java magic here – Toerktumlare Aug 24 '22 at 13:51
  • 2
    You can write class literals for all primitive types, e.g. `int.class`, which would be necessary with old Reflection as well, when specifying parameter types. A literal like `int.class` is actually syntactic sugar for reading the static field [`Integer.TYPE`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Integer.html#TYPE). Likewise, `void.class` just reads [`Void.TYPE`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Void.html#TYPE). But it’s much simpler to always use class literals for known types, whether primitive or reference type. – Holger Aug 24 '22 at 13:56
  • I am aware that all other primitives have `TYPE`, but i was suprised to find out that `void` is considered a primitive, that was new to me, and that `Void.TYPE` existed. Thanks for the help @Holger I learnt something new today. As said before, this is black java magic for me. – Toerktumlare Aug 24 '22 at 14:06
  • 2
    Well, even when the old Reflection doesn’t use the return type when looking up a method, it needed a value for `Method#getReturnType()`. In fact, `Void` was introduced only for holding the `TYPE` field… – Holger Aug 24 '22 at 14:34