您的位置:首页 > 科技 > 能源 > 石家庄新钥匙网站_免费的全平台内容系统_如何创建属于自己的网站_企业推广方法

石家庄新钥匙网站_免费的全平台内容系统_如何创建属于自己的网站_企业推广方法

2024/9/24 3:20:19 来源:https://blog.csdn.net/weixin_41866717/article/details/142417646  浏览:    关键词:石家庄新钥匙网站_免费的全平台内容系统_如何创建属于自己的网站_企业推广方法
石家庄新钥匙网站_免费的全平台内容系统_如何创建属于自己的网站_企业推广方法

背景

不同于脚本语言可以直接调用函数,Java作为面向对象的语言需要提前创建类并实例化对象来调用实例方法,使用起来十分笨重。

比如我们需要构造一个如下ActionListener接口的类:

public interface ActionListener { void actionPerformed(ActionEvent e);
}

需要重新写一套class TestActionListener implements ActionListener {...} ,如果我们有N种不同的实现,就需要写N次,很麻烦,lambda出现之前,可以使用匿名类的方式省去显式class的创建:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("one")}
});
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("two")}
});

但匿名类有以下缺点:

  1. 语法笨重
  2. 变量和this关键字指向不清晰
  3. Inflexible class-loading and instance-creation semantics
  4. Inability to capture non-final local variables
  5. Inability to abstract over control flow

关于变量和this关键字指向不清晰,可以看以下案例:

   void caseOne() {String a = "a";Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(a); // "a"}};runnable.run();}---void caseTwo() {String a = "a";Runnable runnable = new Runnable() {String a = "b";@Overridepublic void run() {System.out.println(this.a); // "b" this始终表示当前匿名类对象System.out.println(a); // "b" 匿名类中有变量a时,匿名类外同名变量失效}};runnable.run();}

lambda表达式的出现消除了第1和第2点,其中lambda中使用this关键字始终指向外部对象,这点和匿名类不一样。lambda避开了第3点的繁琐的类创建和实例化,第4点lambda增加了外部字段的final or effectively final检测,第4点和第5点问题并未被lambda表达式解决。

基本原理

我们创建一个lambda表达式并反编译看下bytecode:

import java.util.function.Function;public class TestLambda {public static void sayHelloWorld() {String world = "World";Function<String,String> func = (hello) -> hello + world;func.apply("hello");}
}

bytecode:

// class version 52.0 (52)
// access flags 0x21
public class TestLambda {// compiled from: TestLambda.java// access flags 0x19public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this LTestLambda; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x9public static sayHelloWorld()VL0LINENUMBER 6 L0LDC "World"ASTORE 0L1LINENUMBER 7 L1ALOAD 0INVOKEDYNAMIC apply(Ljava/lang/String;)Ljava/util/function/Function; [// handle kind 0x6 : INVOKESTATICjava/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;// arguments:(Ljava/lang/Object;)Ljava/lang/Object;, // handle kind 0x6 : INVOKESTATICTestLambda.lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;, (Ljava/lang/String;)Ljava/lang/String;]ASTORE 1L2LINENUMBER 8 L2ALOAD 1LDC "hello"INVOKEINTERFACE java/util/function/Function.apply (Ljava/lang/Object;)Ljava/lang/Object; (itf)POPL3LINENUMBER 9 L3RETURNL4LOCALVARIABLE world Ljava/lang/String; L1 L4 0LOCALVARIABLE func Ljava/util/function/Function; L2 L4 1// signature Ljava/util/function/Function<Ljava/lang/String;Ljava/lang/String;>;// declaration: func extends java.util.function.Function<java.lang.String, java.lang.String>MAXSTACK = 2MAXLOCALS = 2// lambda中用到的外部变量作为方法参数传入,返回值类型为实现的接口方法的返回值类型// access flags 0x100Aprivate static synthetic lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;L0LINENUMBER 7 L0NEW java/lang/StringBuilderDUPINVOKESPECIAL java/lang/StringBuilder.<init> ()VALOAD 1INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;ALOAD 0INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;ARETURNL1LOCALVARIABLE world Ljava/lang/String; L0 L1 0LOCALVARIABLE hello Ljava/lang/String; L0 L1 1MAXSTACK = 2MAXLOCALS = 2
}

可以看到lambda表达式的产生了一个 invokedynamic + 一个静态方法 + 一个静态字段的字节码。

invokedynamic

在 Java7 之前,JVM 提供了如下 4 种【方法调用】指令:

  • invokestatic: 调用静态方法,不会传递当前对象的引用,不会进行动态绑定
  • invokevirtual: 调用实例方法,传递当前对象的引用(this),使用vtable进行动态绑定
  • invokeinterface: 调用接口的方法,使用itable进行动态绑定
  • invokespecial: 调用特殊方法,如构造方法、私有方法、不会进行动态绑定

invokedynamic: 用于处理新型的方法分派,允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,

比如,用户编写lambda表达式:() -> a.test(); b.test(); Jvm在执行该lambda表达式时,
会根据用户在lambda中编写的内容(即调用a和b的test方法)来执行。

注意,lambda表达式并不是invokedynamic调用执行的,invokedynamic调用只是生成了lambda方法对应的CallSite对象,具体执行还得靠invokeinterfaceinvokevirtual。关于为什么lambda使用dynamic可参考:Why are Java 8 lambdas invoked using invokedynamic?

实现上,invokedynamic使用java.lang.invoke.LambdaMetafactory#metafactory方法(也叫引导方法,Bootstrap method)返回java.lang.invoke.CallSite对象,CallSite对象返回java.lang.invoke.MethodHandle方法句柄并执行由lambda生成的静态方法。

以上都是Jvm的内部实现,用户代码其实只有一个lambda,我们使用Java代码来模拟下上面的调用方式,模拟之前先来了解一个名词duck typing:

If it looks like a duck and quacks like a duck, it’s a duck

同理,Supplier接口接收空参数,返回String类型,如果我们实现了一个类接收空参数,返回String类型,那它就实现了Supplier接口,当然在Java这种强类型语言肯定有一个类型转换过程。

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Collections;
import java.util.function.Supplier;public class LambdaExpTest {public static String innerLambdaCode() {return "i'm lambda code";}// 等同于 Supplier<String> supplier = () -> "i'm lambda code";public static void mockLambdaInJvm() throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();// lambda实现的是Supplier接口,该方法需要接收lambda使用到的外部参数并返回一个Supplier,类似一个转换方法// The parameter types represent the types of capture variables; 入参是lambda使用到的外部参数类型// the return type is the interface to implement 出参是lambda实现的接口类型MethodType convertMethodType = MethodType.methodType(Supplier.class, Collections.emptyList());// Supplier的方法入参出参类型MethodType supplierMethodType = MethodType.methodType(Object.class, Collections.emptyList());CallSite callSite = LambdaMetafactory.metafactory(lookup,"get",  // 调用的Supplier接口的方法名convertMethodType,supplierMethodType,// lambda内部逻辑委托给静态方法LambdaExpTest#innerLambdaCode执行lookup.findStatic(LambdaExpTest.class, "innerLambdaCode", MethodType.methodType(String.class)),supplierMethodType);MethodHandle factory = callSite.getTarget();Supplier<String> r = (Supplier<String>)factory.invoke();System.out.println(r.get());}public static void main(String[] args) throws Throwable {// 打印 i'm lambda codemockLambdaInJvm();}
}

参考

  • why-are-java-8-lambdas-invoked-using-invokedynamic
  • understanding-java-method-invocation-with-invokedynamic
  • why-use-reflection-to-access-class-members-when-methodhandle-is-faster
  • what-is-a-bootstrap-method
  • whats-invokedynamic-and-how-do-i-use-it
  • what-is-duck-typing
  • VirtualCalls
  • State of the Lambda
  • An Introduction to Invoke Dynamic in the JVM
  • 浅析 JVM invokedynamic 指令和 Java Lambda 语法|得物技术

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com