深圳专业建站公司技术好,百度大数据中心,办公室装修设计连锁,wordpress新浪图什么是Lambda#xff1f;
Lambda是一个匿名函数#xff0c;我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递#xff0c;称为行为参数化)。Lambda允许把函数作为一个方法的参数#xff08;函数作为参数传递进方法中#xff09;#xff0c;要…什么是Lambda
Lambda是一个匿名函数我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递称为行为参数化)。Lambda允许把函数作为一个方法的参数函数作为参数传递进方法中要做到这一点就需要了解什么是函数式接口这里先不做介绍等下一篇在讲解。 首先先看一下lambda长什么样 正常写法
new Thread(new Runnable() {Overridepublic void run() {System.out.println(hello lambda);}
}).start();lambda写法
new Thread(() - System.out.println(hello lambda)
).start();怎么样是不是感觉很简洁没错这就是lambda的魅力他可以让你写出来的代码更简单、更灵活。
Lambda怎么写
大家看一些上面的这个图这就是lambda的语法一个lambda分为三部分参数列表、操作符、lambda体。以下是lambda表达式的重要特征
可选类型声明 不需要声明参数类型编译器可以统一识别参数值。也就说(s) - System.out.println(s)和 (String s) - System.out.println(s)是一样的编译器会进行类型推断所以不需要添加参数类型。可选的参数圆括号 一个参数无需定义圆括号但多个参数需要定义圆括号。例如s - System.out.println(s) 一个参数不需要添加圆括号。 (x, y) - Integer.compare(y, x) 两个参数添加了圆括号否则编译器报错。可选的大括号 如果主体包含了一个语句就不需要使用大括号。
不需要大括号. s - System.out.println(s) 需要大括号
(s) - {if (s.equals(s)) {System.out.println(s);}}; 可选的返回关键字 如果主体只有一个表达式返回值则编译器会自动返回值大括号需要指定明表达式返回了一个数值。 Lambda体不加{ }就不用写return: Comparator com (x, y) - Integer.compare(y, x); 复制代码Lambda体加上{ }就需要添加return: ComparatorInteger com (x, y) - {int compare Integer.compare(y, x);return compare;}; 类型推断
上面我们看到了一个lambda表达式应该怎么写但lambda中有一个重要特征是可选参数类型声明就是说不用写参数的类型那么为什么不用写呢它是怎么知道的参数类型呢这就涉及到类型推断了。 java8的泛型类型推断改进
支持通过方法上下文推断泛型目标类型 支持在方法调用链路中泛型类型推断传递到最后一个方法
ListPerson ps ...
StreamString names ps.stream().map(p - p.getName());在上面的代码中ps的类型是List所以ps.stream()的返回类型是Stream。map()方法接收一个类型为FunctionT, R的函数式接口这里T的类型即是Stream元素的类型也就是Person而R的类型未知。由于在重载解析之后lambda表达式的目标类型仍然未知我们就需要推导R的类型通过对lambda表达式lambda进行类型检查我们发现lambda体返回String因此R的类型是String因而map()返回Stream。绝大多数情况下编译器都能解析出正确的类型但如果碰到无法解析的情况我们则需要
使用显式lambda表达式为参数p提供显式类型以提供额外的类型信息 把lambda表达式转型为FunctionPerson, String 为泛型参数R提供一个实际类型。 map(p - p.getName())
方法引用
方法引用是用来直接访问类或者实例已经存在的方法或构造方法提供了一种引用而不执行方法的方式。是一种更简洁更易懂的Lambda表达式当Lambda表达式中只是执行一个方法调用时直接使用方法引用的形式可读性更高一些。 方法引用使用 “ :: ” 操作符来表示左边是类名或实例名右边是方法名。 注意方法引用::右边的方法名是不需要加的例User::getName 方法引用的几种形式
类 :: 静态方法 类 :: 实例方法 对象 :: 实例方法
例如
ConsumerString consumer (s) - System.out.println(s);等同于
ConsumerString consumer System.out::println;例如 FunctionString, Integer stringToInteger (String s) - Integer.parseInt(s);等同于 FunctionString, Integer stringToInteger Integer::parseInt;例如
BiPredicateListString, String contains (list, element) - list.contains(element);等同于 BiPredicateListString, String contains List::contains;注意:
Lambda体中调用方法的参数列表与返回值类型要与函数式接口中抽象方法的函数列表和返回值类型保存一致 若Lambda参数列表中的第一个参数是实例方法的调用者而第二个参数是实例方法的参数时可以使用ClassName::method
构造器引用 语法格式类名::new 例如 Supplier supplier ()-new User();
等同于 Supplier supplier User::new; 复制代码注意: 需要调用的构造器方法与函数式接口中抽象方法的参数列表保持一致。 Lambda是怎么实现的 研究了半天Lambda怎么写可是它的原理是什么我们简单看个例子看看真相到底是什么
public class StreamTest {public static void main(String[] args) {printString(hello lambda, (String s) - System.out.println(s));}public static void printString(String s, PrintString print) {print.print(s);}
}FunctionalInterface
interface PrintT {public void print(T t);
}上面的代码自定义了一个函数式接口定义一个静态方法然后用这个函数式接口来接收参数。编写完这个类以后我们到终端界面javac进行编译然后用javapjavap是jdk自带的反解析工具。它的作用就是根据class字节码文件反解析出当前类对应的code区汇编指令、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。进行解析如下图
执行javap -p 命令 ( -p -private 显示所有类和成员)
看上图发现在编译Lambda表达式生成了一个lambda$main$0静态方法这个静态方法实现了Lambda表达式的逻辑现在我们知道原来Lambda表达式被编译成了一个静态方法那么这个静态方式是怎么调用的呢我们继续进行 执行javap -v -p 命令 ( -v -verbose 输出附加信息)
public com.lxs.stream.StreamTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack1, locals1, args_size1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.)V 4: return LineNumberTable: line 7: 0
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack2, locals1, args_size1 0: ldc #2 // String hello lambda 2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print; 7: invokestatic #4 // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V 10: return LineNumberTable: line 10: 0 line 12: 10
public static void printString(java.lang.String, com.lxs.stream.Printjava.lang.String); descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack2, locals2, args_size2 0: aload_1 1: aload_0 2: invokeinterface #5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V 7: return LineNumberTable: line 15: 0 line 16: 7 Signature: #19 // (Ljava/lang/String;Lcom/lxs/stream/PrintLjava/lang/String;;)V
private static void lambda$mainKaTeX parse error: Expected EOF, got # at position 183: … getstatic #̲6 …Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandlesKaTeX parse error: Expected EOF, got # at position 201: …guments: #̲28 (Ljava/lang/…mainKaTeX parse error: Expected EOF, got # at position 31: …tring;)V #̲30 (Ljava/lang/…Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandlesKaTeX parse error: Expected EOF, got # at position 201: …guments: #̲28 (Ljava/lang/…mainKaTeX parse error: Expected EOF, got # at position 31: …tring;)V #̲30 (Ljava/lang/…main$0作为参数传了进去我们来看metafactory 的方法里的实现代码 public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; mf new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); return mf.buildCallSite(); } 复制代码在buildCallSite的函数中,是函数spinInnerClass 构建了这个内部类。也就是生成了一个StreamTest?Lambda$1.class这样的内部类,这个类是在运行的时候构建的并不会保存在磁盘中。 Override CallSite buildCallSite() throws LambdaConversionException { final Class? innerClass spinInnerClass(); 以下省略。。。 } 复制代码如果想看到这个构建的类可以通过设置环境参数 System.setProperty(“jdk.internal.lambda.dumpProxyClasses”, . ); 会在你指定的路径 . 当前运行路径上生成这个内部类。我们看下一下生成的类长什么样
从图中可以看出动态生成的内部类实现了我自定义的函数式接口并且重写了函数式接口中的方法。 我们在javap -v -p StreamTest?LambdaKaTeX parse error: Expected }, got EOF at end of input: …ream.StreamTest$Lambda$1(); descriptor: ()V flags: ACC_PRIVATE Code: stack1, locals1, args_size1 0: aload_0 1: invokespecial #10 // Method java/lang/Object.)V 4: return
public void print(java.lang.Object); descriptor: (Ljava/lang/Object;)V flags: ACC_PUBLIC Code: stack1, locals2, args_size2 0: aload_1 1: checkcast #15 // class java/lang/String 4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$mainKaTeX parse error: Expected EOF, got # at position 84: …ions: 0: #̲13() } 复制代码发现在重…main0方法。总结这样实现了Lambda表达式使用invokedynamic指令运行时调用LambdaMetafactory.metafactory动态的生成内部类实现了函数式接口并在重写函数式接口中的方法在方法内调用lambda0方法。 总结 这样实现了Lambda表达式使用invokedynamic指令运行时调用LambdaMetafactory.metafactory动态的生成内部类实现了函数式接口并在重写函数式接口中的方法在方法内调用lambda0方法。总结这样实现了Lambda表达式使用invokedynamic指令运行时调用LambdaMetafactory.metafactory动态的生成内部类实现了函数式接口并在重写函数式接口中的方法在方法内调用lambdamain$0内部类里的调用方法块并不是动态生成的只是在原class里已经编译生成了一个静态的方法内部类只需要调用该静态方法。
作者俩右 链接https://juejin.im/post/6844903890274484231 来源掘金 著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。