java lambda之方法句柄&invokedynamic指令

Java7 MethodHandle

参考文档:https://www.javacodegeeks.com/2011/03/glimpse-at-java-7-methodhandle-and-its.html

Due to Java’s Reflection API we have been able to inspect and alter program execution at runtime. In particular, we can observe interfaces/classes/methods and fields at runtime without knowing their names at compile time.

JDK 7 introduces a new player to this dynamic/runtime inspection, the method handle (i.e. a subclass of the abstract class java.dyn.MethodHandle). Method handles gives us unrestricted capabilities for calling non-public methods, e.g. it can be formed on a non-public method by a class that can access it. Compared to using the Reflection API, access checking is performed when the method handle is created as opposed to every time the method is called.

MethodHandle和反射一样也能够实现在运行期间访问私有方法,且更加高效。可以看成是:持有某个具体方法的指针,然后就能直接调用该句柄所引用的底层方法。与使用Reflection API相比,在创建方法句柄时(而不是在每次调用该方法时)执行访问检查。

例子代码:


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


class MethodAccessExampleWithArgs{
    
    
    private final int i;

    public MethodAccessExampleWithArgs(int i_) {
    
    
        i = i_;
    }

    private void bar(int j, String msg) {
    
    
        System.out.println("Private Method \'bar\' successfully accessed : "
                + i + ", " + j + " : " + msg + "!");
    }

    // Using Reflection,使用反射
    public static Method makeMethod() {
    
    
        Method meth = null;
        try {
    
    
            Class[] argTypes = new Class[] {
    
     int.class, String.class };
            meth = MethodAccessExampleWithArgs.class.getDeclaredMethod("bar",
                    argTypes);
            meth.setAccessible(true);
        } catch (IllegalArgumentException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (SecurityException e) {
    
    
            e.printStackTrace();
        }
        return meth;
    }

    // Using method handles, 使用 MethodHandle
    public static MethodHandle makeMethodhandle() {
    
    
        MethodHandle mh = null;
        MethodType desc = MethodType.methodType(void.class, int.class,
                String.class);
        try {
    
    
            mh = MethodHandles.lookup().findVirtual(
                    MethodAccessExampleWithArgs.class, "bar", desc);
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("mh=" + mh);

        return mh;
    }
}

/**
 * @author dingqi on 2022/10/29
 * @since 1.0.0
 */
public class MethodHandleExample {
    
    

    private static void withReflectionArgs() {
    
    
        Method meth = MethodAccessExampleWithArgs.makeMethod();

        MethodAccessExampleWithArgs mh0 = new MethodAccessExampleWithArgs(0);
        MethodAccessExampleWithArgs mh1 = new MethodAccessExampleWithArgs(1);

        try {
    
    
            System.out.println("Invocation using Reflection");
            meth.invoke(mh0, 5, "Jabba the Hutt");
            meth.invoke(mh1, 7, "Boba Fett");
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
    }

    private static void withMethodHandleArgs() {
    
    
        MethodHandle mh = MethodAccessExampleWithArgs.makeMethodhandle();

        MethodAccessExampleWithArgs mh0 = new MethodAccessExampleWithArgs(0);
        MethodAccessExampleWithArgs mh1 = new MethodAccessExampleWithArgs(1);

        try {
    
    
            System.out.println("Invocation using MethodHandle");
            mh.invokeExact(mh0, 42, "R2D2");
            mh.invokeExact(mh1, 43, "C3PO");
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        withReflectionArgs();
        withMethodHandleArgs();
    }
}

lambda实现说明

参考:Translation of Lambda Expressions

There are a number of ways we might represent a lambda expression in bytecode, such as inner classes, method handles, dynamic proxies, and others. Each of these approaches has pros and cons. In selecting a strategy, there are two competing goals: maximizing flexibility for future optimization by not committing to a specific strategy, vs providing stability in the classfile representation. We can achieve both of these goals by using the invokedynamic feature from JSR 292 to separate the binary representation of lambda creation in the bytecode from the mechanics of evaluating the lambda expression at runtime. Instead of generating bytecode to create the object that implements the lambda expression (such as calling a constructor for an inner class), we describe a recipe for constructing the lambda, and delegate the actual construction to the language runtime. That recipe is encoded in the static and dynamic argument lists of an invokedynamic instruction.

invokedynamic指令

The use of invokedynamic lets us defer the selection of a translation strategy until run time. The runtime implementation is free to select a strategy dynamically to evaluate the lambda expression. The runtime implementation choice is hidden behind a standardized (i.e., part of the platform specification) API for lambda construction, so that the static compiler can emit calls to this API, and JRE implementations can choose their preferred implementation strategy. The invokedynamic mechanics allow this to be done without the performance costs that this late binding approach might otherwise impose.

执行逻辑

  1. 将调用点(CallSite)抽象成Java类
  • 第一次执行invokedynamic指令时,JVM会调用该指令所对应的启动方法(BootStrapMethod)
  • 启动方法会生成调用点,并且绑定至该invokedynamic指令中
  1. 并且将方法调用和方法链接暴露给应用程序。(这些工作原本应该由JVM控制)
  2. 运行过程中,每一条invokedynamic指令将捆绑一个调用点,并且会调用调用点所链接的方法句柄。

例子代码

import java.util.function.Predicate;

/**
 * @author dingqi on 2022/10/29
 * @since 1.0.0
 */
public class LambdaTest {
    
    
    public static void main(String[] args) {
    
    
        Predicate<Character> isDigit = c -> c >= '0' && c <= '9';
        boolean b = isDigit.test('0');
        System.out.println(b);
        b = isDigit.test('a');
        System.out.println(b);
        String s;
        char c = '1';
        int a = c-'0';
    }
}

查看字节码:

javac LambdaTest.java
javap -verbose LambdaTest.class

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_26437925/article/details/127584655
今日推荐