分享

源码元宇宙-lambda表达式底层执行解析

 编程一生 2022-04-17

背景

3月28日那天,咱们 用户群 里,朋友让我写篇lambda表达式的底层执行解析。拖了快20天了,今天就来聊聊这个问题。

深入理解函数式编程里讲到lambda表达式本质是一个匿名的内联函数。不从Java角度,Lambda本身是计算机编程语言,Lambda 表达式是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。那篇文章讲解了匿名内部类与函数式编程怎么转换,怎么debug调试。

对面试而言,这些内容足够了。朋友提出想了解lambda表达式的底层执行解析。说明是真的对技术有追求,是把问题思考细化了,这个是很赞的。

面试官问我:你做事仔细吗?》一文中,我提出了仔细意识的重要性。大家想想,咱们上高三的时候,一般的学生是不是所有课本上讲的数学题都会做?那为什么考满分的却寥寥无几。老师告诉我们这不是马虎不马虎的事情,就是能力的差距。

能力的差距就在于把问题考虑仔细周全的能力。互联网的上半场,IT能力刚起步,就好像教育水平低的学校,能把课学明白的就不多。而互联网的下半场好比是重点高中,一个差生有可能是小学的时候的班长。学习能力都没有问题,想达到更高的深度和广度靠的就是把问题细化分解这种“额外”能力的差距。

总结来说:越是基础的知识,越需要沉下心,将问题细化。

函数式接口

Lambda是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作。在函数式编程思想中,函数是"第一等公民"。所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

Lambda表达式的入参都是函数,所以咱们先来看看函数在Java中的定义。

匿名内部类,类是使用时实现的,在Lambda表达式中,入参都是函数接口来定义的,函数接口大多都带有 @FunctionalInterface 注解。先看看这个注解的定义。

/**
* An informative annotation type used to indicate that an interface
* type declaration is intended to be a <i>functional interface</i> as
* defined by the Java Language Specification.
*
* Conceptually, a functional interface has exactly one abstract
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
*
* <p>If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
* <ul> * <li> The type is an interface type and not an annotation type, enum, or class.
* <li> The annotated type satisfies the requirements of a functional interface.
* </ul> *
* <p>However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.
*
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

这个接口的注释说明了函数接口的要点:

1)函数接口有且只有一个抽象方法

2)可以有其他default 实现的方法,这不违背有且只有一个抽象方法的原则,因为实现了就不是抽象的

3)可以有额外的 java.lang.Object 的 public 的抽象类,这不违背有且只有一个抽象方法的原则,因为 Object 或者其他的地方 已经实现了这个方法

4)函数式接口可以通过lambda表达式、方法引用或者构造方法引用来创建实例

5) 如果加上了 @FunctionalInterface 注解,编译器就会校验是否符合下面规范,不符合就会报编译错误:

规范规定此注解只能加在接口上,不能加在注解类型、枚举类型和类上,并且这个接口还要满足上面那4个条件。

6)满足上面5个条件,即使不加 @FunctionalInterface 注解,也是函数式接口

咱们来动手实践一下,写个小例子运行一下:

我们定义了一个接口的lambda表达式形式的实现,发现这个实现是Java自己的实现类,类名是Java自己生成的匿名内部类,其父类是Object,实现了YunaInterface这一个接口。

本系列的所有代码文字在 https://github.com/xiexiaojing/yuna 里可以找到。

forEach方法引用lambda表达式

先看一个简单的例子,一个list进行forEach:

跟进源码里看一看:

这里就通过lambda表达式把实现了Consumer接口的行为动作传给了forEach方法。

咱们来看一看Consumer接口:

首先还是来翻译一下接口说明:

这个函数代表了一个操作,这个操作只接受一个参数并且不返回结果。但是和大多数函数式接口不同,它会产生副作用。

这段说明很好的诠释了类名Consumer和accept方法:只accept一个参数T,并且消费掉了不会返回结果。forEach方法就是对下面匿名内部类的简写:

Alter+Enter快捷键可以对其进行化简,最简的写法是下面方法引用的形式:

大家可能注意到Consumer里有个带有默认实现的andThen方法,这个方法有需要的时候这样可以使用:

总结

本文说明了函数式接口特性和作用,并在此基础上介绍了forEach这个方法的底层实现。相信大家看了文章应该对底层执行怎么去深入了解有了思路和方法,可以自己去研究其他的函数了。

编程一生

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多