© Fu Cheng 2018

Fu Cheng, Exploring Java 9, https://doi.org/10.1007/978-1-4842-3330-6_9

9. Enhanced Method Handles

Fu Cheng

(1)Auckland, New Zealand

The class java.lang.invoke.MethodHandles is enhanced in Java 9 to include more static methods for creating different kinds of method handles.

arrayConstructor

The method MethodHandle arrayConstructor (Class<?> arrayClass) returns a method handle to construct arrays of the specified type arrayClass. The sole argument of the created method handle is an int value that specifies the size of the array, while the return value is the array. In Listing 9-1, I create a method handle to construct int[] arrays. Then I create an array of size 3 using the MethodHandle.

Listing 9-1. Example of arrayConstructor()
@Test
public void testArrayConstructor() throws Throwable {
  final MethodHandle handle = MethodHandles.arrayConstructor(int[].class);
  final int[] array = (int[]) handle.invoke(3);
  assertEquals(3, array.length);
}

arrayLength

The method MethodHandle arrayLength(Class<?> arrayClass) returns a method handle to get the length of an array of type arrayClass. The sole argument of this method handle is the array to check, while the return value is an int value. In Listing 9-2, I create a method handle to get the length of int[] arrays. Then I invoke it to get the length of the input array.

Listing 9-2. Example of arrayLength()
@Test
public void testArrayLength() throws Throwable {
  final MethodHandle handle = MethodHandles.arrayLength(int[].class);
  final int[] array = new int[]{1, 2, 3, 4, 5};
  final int length = (int) handle.invoke(array);
  assertEquals(5, length);
}

varHandleInvoker and varHandleExactInvoker

The MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) and MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) methods return a method handle to invoke a signature polymorphic access mode method on any VarHandlethat has an access mode type compatible with the given type. Both methods have two parameters of following types:

  • VarHandle.AccessMode accessMode: The access mode of VarHandle

  • MethodType type: The type of the access mode method

The difference between varHandleInvoker() and varHandleExactInvoker() is that varHandleInvoker() does necessary type conversation, but varHandleExactInvoker() doesn’t.

In Listing 9-3, the variable varHandle references the variable count in the class HandleTarget. I create the MethodHandle with the access mode VarHandle.AccessMode.GET and invoke it to get the value of the variable. The method accessModeType() of VarHandle is handy when you’re creating the MethodType for a given access mode.

Listing 9-3. Example of varHandleInvoker()
@Test
public void testVarHandleInvoker() throws Throwable {
  final VarHandle varHandle = MethodHandles
      .lookup()
      .findVarHandle(HandleTarget.class, "count", int.class);
  final VarHandle.AccessMode accessMode = VarHandle.AccessMode.GET;
  final MethodHandle methodHandle = MethodHandles.varHandleInvoker(
      accessMode,
      varHandle.accessModeType(accessMode)
  );
  final HandleTarget handleTarget = new HandleTarget();
  final int result = (int) methodHandle.invoke(varHandle, handleTarget);
  assertEquals(result, 1);
}

zero

The method MethodHandle zero(Class<?> type) returns a method handle that always returns the default value for the given type. Listing 9-4 shows how to use zero() for different types.

Listing 9-4. Example of zero()
@Test
public void testZero() throws Throwable {
  assertEquals(0, MethodHandles.zero(int.class).invoke());
  assertEquals(0L, MethodHandles.zero(long.class).invoke());
  assertEquals(0F, MethodHandles.zero(float.class).invoke());
  assertEquals(0D, MethodHandles.zero(double.class).invoke());
  assertEquals(null, MethodHandles.zero(String.class).invoke());
}

empty

The method MethodHandle empty(MethodType type) returns a method handle that always return the default value based on the return type of the given MethodType. The returned method handle of zero(type) is actually equivalent to empty(MethodType.methodType(type)). Listing 9-5 shows how to use empty() for int and String types.

Listing 9-5. Example of empty()
@Test
public void testEmpty() throws Throwable {
  assertEquals(0, MethodHandles.
      empty(MethodType.methodType(int.class)).invoke());
  assertEquals(null, MethodHandles.
      empty(MethodType.methodType(String.class)).invoke());
}

Loops

MethodHandles has new static methods to create method handles representing different kinds of loops, including for loops, while loops, and do-while loops.

loop

The method MethodHandle loop(MethodHandle[]... clauses) represents for loops. It’s quite complicated to create for loops using this method. A loop is defined by one or many clauses. A clause can specify up to four actions using method handles.

  • init: Initializes an iteration variable before the loop executes.

  • step: Updates the iteration variable when a clause executes. The return value of this action is used as the updated value of the iteration variable.

  • pred: Executes a predicate to test for loop exit when a clause executes.

  • fini: Computes the loop’s return value if the loop exits.

Each clause can define its own iteration variable. Each iteration of the loop executes each clause in order. The actions step, pred, and fini receive all iteration variables as leading parameters. All these actions can also receive extra loop parameters. These loop parameters are passed as arguments when invoking the created MethodHandle and are available for all actions in all iterations. For the actions step, pred, and fini, loop parameters come after the iteration variables. The init action can only accept loop parameters.

In Listing 9-6, init(), step(), pred(), and fini() are methods used as actions in a clause. I only have one clause with MethodHandles for these methods when I’m using the method loop(). In the step action, the variable sum is updated to add the value of iteration variable i. The return value of step is used as the updated value of the iteration variable i. In the pred action, the variable k is the loop parameter with a value of 11, which is provided when the created method handle loop is invoked. I check the values of i and k for loop exit. In the fini action, the value of sum is returned as the result of invoking the method handle loop. Because each clause is represented as an array of MethodHandles, I use the helper method getMethodHandle() to get the MethodHandles of those static methods in the current class.

Listing 9-6. Example of loop()
public class ForLoopTest {

  static int sum = 0;

  static int init() {
    return 1;
  }


  static int step(final int i) {
    sum += i;
    return i + 1;
  }


  static boolean pred(final int i, final int k) {
    return i < k;
  }


  static int fini(final int i, final int k) {
    return sum;
  }


  @Test
  public void testLoop() throws Throwable {
    final MethodHandle init = getMethodHandle("init",
        MethodType.methodType(int.class));
    final MethodHandle step = getMethodHandle("step",
        MethodType.methodType(int.class, int.class));
    final MethodHandle pred = getMethodHandle("pred",
        MethodType.methodType(boolean.class, int.class, int.class));
    final MethodHandle fini = getMethodHandle("fini",
        MethodType.methodType(int.class, int.class, int.class));
    final MethodHandle[] sumClause =
        new MethodHandle[]{init, step, pred, fini};
    final MethodHandle loop = MethodHandles.loop(sumClause);
    assertEquals(55, loop.invoke(11));
  }


  private MethodHandle getMethodHandle(final String name,
      final MethodType methodType)
      throws NoSuchMethodException, IllegalAccessException {
    return MethodHandles
        .lookup()
        .findStatic(ForLoopTest.class, name, methodType);
  }
}

In Listing 9-6, I use a static variable sum to maintain the internal state. I cannot use loop parameters here because they are not supposed to be updated. What I can do is to use another iteration variable from a different clause. That way, I can use the iteration variable in one clause to control the loop exit and use the other iteration variable to store the internal state.

countedLoop

The method MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) creates a method handle representing a loop that runs for a given number of iterations. It’s much easier to use than loop() for simple loops like for (int i = 0; i < end; ++i). The parameters of countedLoop() are different MethodHandles.

  • iterations: Determines the number of iterations. The result type of this method handle must be int.

  • init: Initializes the optional loop variable. This variable can be updated in each iteration.

  • body: Executes the method body.

The method handle created by countedLoop() has an implicit loop variable that starts at 0 and increases by 1 in each iteration. The method handle init can create a second loop variable that’s passed to body as the first parameter and uses the return value of body as the updated value. The implicit loop variable is passed as the second parameter to body. Method handles iterations, init, and body all accept extra loop parameters that come after the second loop variable in the parameters list. The return value of invoking the method handle created by countedLoop() is the final value of the second loop variable.

The method MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) is similar to countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body), but it allows customization of the start and end values of the implicit loop variable. The method handles start and end must return values of type int, which determine the start (inclusive) and end (exclusive) values of the loop variable, respectively.

Listing 9-7 shows the usage of two countedLoop() methods to represent the same loop. In the method body, the first parameter sum is the loop variable I created to keep the summarized value. The second parameter i is the implicit loop variable.

Listing 9-7. Example of countedLoop()
public class CountedLoopTest {

  static int body(final int sum, final int i) {
    return sum + i + 1;
  }


  @Test
  public void testCountedLoop() throws Throwable {
    final MethodHandle iterations = MethodHandles.constant(int.class, 10);
    final MethodHandle init = MethodHandles.zero(int.class);
    final MethodHandle body = MethodHandles
        .lookup()
        .findStatic(CountedLoopTest.class, "body",
            MethodType.methodType(int.class, int.class, int.class));
    final MethodHandle countedLoop = MethodHandles
        .countedLoop(iterations, init, body);
    assertEquals(55, countedLoop.invoke());
  }


  @Test
  public void testCountedLoopStartEnd() throws Throwable {
    final MethodHandle start = MethodHandles.zero(int.class);
    final MethodHandle end = MethodHandles.constant(int.class, 10);
    final MethodHandle init = MethodHandles.zero(int.class);
    final MethodHandle body = MethodHandles
        .lookup()
        .findStatic(CountedLoopTest.class, "body",
            MethodType.methodType(int.class, int.class, int.class));
    final MethodHandle countedLoop = MethodHandles
        .countedLoop(start, end, init, body);
    assertEquals(55, countedLoop.invoke());
  }
}

iteratedLoop

The method MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) creates a method handle representing a loop that iterates over the values produced by an Iterator<T>. The method ha ndle iterator must return a value of type Iterator<T>. The usage of iteratedLoop() is similar with countedLoop(), except that the implicit loop variable is a value of type T instead of int. You can also use a second loop variable in iteratedLoop(). In Listing 9-8, I use iteratedLoop() to iterate a Iterator<String> from the list of Strings to calculate the sum of the length of these Strings.

Listing 9-8. Example of iteratedLoop()
public class IteratedLoopTest {

  static int body(final int sum, final String value) {
    return sum + value.length();
  }


  @Test
  public void testIteratedLoop() throws Throwable {
    final MethodHandle iterator = MethodHandles.constant(
        Iterator.class,
        List.of("a", "bc", "def").iterator());
    final MethodHandle init = MethodHandles.zero(int.class);
    final MethodHandle body = MethodHandles
        .lookup()
        .findStatic(
            IteratedLoopTest.class,
            "body",
            MethodType.methodType(
                int.class,
                int.class,
                String.class));
    final MethodHandle iteratedLoop = MethodHandles
        .iteratedLoop(iterator, init, body);
    assertEquals(6, iteratedLoop.invoke());
  }
}

whileLoop and doWhileLoop

The method MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) creates a method handle representing a while loop. A while loop is defined by three method handles.

  • init: Initializ es an optional loop variable. In each iteration, the loop variable is passed to the body and updated with the return value of the body. The result of the loop execution is the final value of the loop variable.

  • pred: Executes a predicate to test for loop exit.

  • body: Executes the method body.

The method MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) creates a method handle representing a do-while loop. It has the same parameters as whileLoop, but with a different order. In a doWhileLoop, the parameter body comes before pred. This is because the do-while loop executes the body before pred in each iteration. All three of these parameters have the same meaning as in whileLoop().

In Listing 9-9, the loop variable is an int[] array with two elements; the first element is the iteration variable that controls the loop exit, while the second elements contains the sum. k is the extra parameter that determines the number of iterations. The return value of method init() is the initial value of the loop variable. The return value of method body() is passed as the input of the next iteration. The return value of the created MethodHandle is the final value of the loop variable.

Listing 9-9. Example of whileLoop() and doWhileLoop()
public class WhileLoopTest {
    static int[] init(final int k) {
        return new int[]{1, 0};
    }


    static boolean pred(final int[] local, final int k) {
        return local[0] < k;
    }


    static int[] body(final int[] local, final int k) {
        return new int[]{local[0] + 1, local[1] + local[0]};
    }


    @Test
    public void testWhileLoop() throws Throwable {
        final MethodHandle init = getMethodHandle("init",
                MethodType.methodType(int[].class, int.class));
        final MethodHandle pred = getMethodHandle("pred",
                MethodType.methodType(boolean.class, int[].class, int.class));
        final MethodHandle body = getMethodHandle("body",
                MethodType.methodType(int[].class, int[].class, int.class));
        final MethodHandle whileLoop = MethodHandles.whileLoop(init, pred, body);
        assertEquals(55, ((int[]) whileLoop.invoke(11))[1]);
    }


    @Test
    public void testDoWhileLoop() throws Throwable {
        final MethodHandle init = getMethodHandle("init",
                MethodType.methodType(int[].class, int.class));
        final MethodHandle pred = getMethodHandle("pred",
                MethodType.methodType(boolean.class, int[].class, int.class));
        final MethodHandle body = getMethodHandle("body",
                MethodType.methodType(int[].class, int[].class, int.class));
        final MethodHandle doWhileLoop = MethodHandles
                .doWhileLoop(init, body, pred);
        assertEquals(55, ((int[]) doWhileLoop.invoke(11))[1]);
    }


    private MethodHandle getMethodHandle(final String name,
                                         final MethodType methodType)
            throws NoSuchMethodException, IllegalAccessException {
        return MethodHandles
                .lookup()
                .findStatic(WhileLoopTest.class, name, methodType);
    }
}
Note

The method handle body in countedLoop(), iteratedLoop(), whileLoop(), and doWhileLoop() can have an optional loop variable. If the return type of body is void, then there will be no loop variable as the leading parameter of body. In this case, the return type of init is also void. When there is no loop variable, the first parameter of body will be the implicit loop variable.

Try-finally

The method MethodHandle tryFinally(MethodHandle target, MethodHandle cleanup) creates a method handle that wraps the target method handle in a try-finally block. The parameter target is the MethodHandle to wrap, while cleanup is the MethodHandle to invoke in the finally block. Any exception thrown in the execution of target is passed to cleanup. The exception will be rethrown unless cleanup throws an exception first. The return value of the method handle created by tryFinally() is the return value of cleanup. cleanup accepts two optional leading parameters:

  • A Throwable represents the exception thrown by invoking the method handle target.

  • Return value of the method handle target.

Except those two leading parameters, target and cleanup should have the same corresponding arguments and return types. In Listing 9-10, I have two different target method handles, success and failure. The method handle tryFinallySuccess that wraps the method success() returns the value 1, but the method handle tryFinallyFailure that wraps the method failure() throws the IllegalArgumentException. The method cleanup is invoked in both invocations and outputs different information to the console.

Listing 9-10. Example of tryFinally
public class TryFinallyTest {

  static int success() {
    return 1;
  }


  static int failure() {
    throw new IllegalArgumentException("");
  }


  static int cleanup(final Throwable throwable, final int result) {
    if (throwable != null) {
      throwable.printStackTrace();
    } else {
      System.out.println("Success: " + result);
    }
    return result;
  }


  @Test(expected = IllegalArgumentException.class)
  public void testTryFinally() throws Throwable {
    final MethodHandle targetSuccess = getMethodHandle("success",
        MethodType.methodType(int.class));
    final MethodHandle targetFailure = getMethodHandle("failure",
        MethodType.methodType(int.class));
    final MethodHandle cleanup = getMethodHandle("cleanup",
        MethodType.methodType(int.class, Throwable.class, int.class));
    final MethodHandle tryFinallySuccess = MethodHandles
        .tryFinally(targetSuccess, cleanup);
    assertEquals(1, tryFinallySuccess.invoke());
    final MethodHandle tryFinallyFailure = MethodHandles
        .tryFinally(targetFailure, cleanup);
    tryFinallyFailure.invoke();
  }


  private MethodHandle getMethodHandle(final String name,
      final MethodType methodType)
      throws NoSuchMethodException, IllegalAccessException {
    return MethodHandles
        .lookup()
        .findStatic(TryFinallyTest.class, name, methodType);
  }
}

Summary

In this chapter, we discussed the enhancements to method handles in Java 9. You can now create method handles for array constructors, variable handles, loops, and try-finally blocks. In the next chapter, we’ll discuss changes in concurrency.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.151.32