Lambda 表达式介绍和底层实现分析

如果你的需求需要匿名类来实现,例如是一个只有一个方法的接口,那么匿名类的语法可能看起来比较笨拙和不清晰,尽管匿名类比命名类更简洁,但对于只有一个方法的类来说,即使是匿名类也显得有些麻烦。还有在一些情况下,需要将功能作为参数传递给另一个方法,例如当有人单击页面上按钮时应该采取什么操作,javascript 可以通过闭包实现。在 java 语言中,lambda 表达式能够将功能视为方法参数,或将代码视为数据,而且 lambda 表达式可以更紧凑地表达单方法类的实例,在 Swing 编程和集合(Collections)编程中优势很明显。

lambda 表达式

lambda 表达式,也被称为闭包,它是推动 Java 8 发布的最重要新特性。lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

lambda 表达式的语法格式如下:

(parameters) -> expression或 (parameters) ->{ statements; }

以下是 lambda 表达式的重要特征:

Lambda 表达式介绍和底层实现分析

例如下面是一些 lambda 表达式

(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }

函数式接口

只有一个抽象方法的接口,称为函数式接口。例如下面的接口,

public interface MyFunctionInterface<T> {
    public T getValue(T t);
}

测试类如下

public class MyFunctionInterfaceTest {
    public static void main(String[] args) {
        testMethod("   aaaa  ", s -> s.trim());
        testMethod("   aaaa  ", s -> s.trim().toUpperCase());
    }

    public static void testMethod(String str,     MyFunctionInterface<String> functionInterface) {
        System.out.println(functionInterface.getValue(str));
    }
}

输出结果如下:

aaaa
AAAA

修改一下上面的接口,增加一个方法。

public interface MyFunctionInterface<T> {
    public T getValue(T t);
    public T returnValue(T t);
}

增加一个方法之后,上面就不是函数式接口了,可以看到 lambda 表达式就会报错。

Lambda 表达式介绍和底层实现分析

虽然不能在函数式接口中定义多个方法,但可以定义默认方法、静态方法以及 java.lang.Object 里的 public 方法。如下面的代码

@FunctionalInterface
public interface MyFunctionInterface<T> {

    public T getValue(T t);

    default void defaultMethod() {
        System.out.println("this is default method");
    }

    static void staticMethod() {
        System.out.println("this is static method");
    }


    public boolean equals(Object obj);
}

我们可以在接口上使用 @FunctionalInterface 注解,如果使用 Intellij IDEA 可以在编码的时候就看见报错了,这样做可以检查它是否是一个函数式接口。

Lambda 表达式介绍和底层实现分析

通过反编译,可以看到函数式接口其实就是一个普通的 java 接口类,如下图

Lambda 表达式介绍和底层实现分析

函数式接口可以作为方法参数传递 lambda 表达式,但是为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。但是我们没必要为每一个 lambda 表达式创建接口,在 jdk 的 java.util.function 包下面已经为我们创建了常用的函数式接口,其中比较核心的是消费型接口(Consumer ),供给型接口(Supplier ),断言型接口(Predicate ),函数型接口(Function<T, R>)四个接口,能够满足大部分应用场景。

lambda 表达式原理分析

继续使用上面的测试代码,可以在 IDEA 中使用 jclasslib Bytecode viewer 插件查看 MyFunctionInterface.class 源文件。

安装完 jclasslib Bytecode viewer,会在 view 菜单中出现如下两个选项

Lambda 表达式介绍和底层实现分析

选择需要反编译的 class 文件,点击 Show Bytecode with Jclasslib 选项会出现如下界面

Lambda 表达式介绍和底层实现分析

可以看到里面编译器多生成了 lambda$main$0 和 lambda$main$1 两个私有静态方法,属性当中多了 InnerClasses。我们可以通过 Show Bytecode 查看一下测试类字节码更详细的反编译结果,找到这两个静态方法。

// access flags 0x100A
private static synthetic lambda$main$1(Ljava/lang/String;)Ljava/lang/String;
 L0
  LINENUMBER 6 L0
  ALOAD 0
  INVOKEVIRTUAL java/lang/String.trim ()Ljava/lang/String;
  INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;
  ARETURN
 L1
  LOCALVARIABLE s Ljava/lang/String; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1

// access flags 0x100A
private static synthetic lambda$main$0(Ljava/lang/String;)Ljava/lang/String;
 L0
  LINENUMBER 5 L0
  ALOAD 0
  INVOKEVIRTUAL java/lang/String.trim ()Ljava/lang/String;
  ARETURN
 L1
  LOCALVARIABLE s Ljava/lang/String; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1
}

可以看到两个私有的静态方法干的就是 Lambda 表达式里面的内容,那么又是如何调用的生成的私有静态方法呢?如下图,通过分析 main 方法的 L0,首先通过 INVOKEDYNAMIC 指令调用是 MyFunctionInterface 的 getValue 方法的引用,以及后面的 BootstrapMethods #0。使用 jclasslib Bytecode viewer 查看。

Lambda 表达式介绍和底层实现分析

点击 #3,进入下面界面

Lambda 表达式介绍和底层实现分析

点击 BootstrapMethods #0,进入如下界面

Lambda 表达式介绍和底层实现分析

继续点击相应的方法描述符,我们可以看到最后

Lambda 表达式介绍和底层实现分析

cp_info #74 内容如下:

(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;

可以看到 INVOKEDYNAMIC 后面的一系列指令,最后使用 INVOKESTATIC 调用

java/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;

查看 LambdaMetafactory.metafactory 的方法,里面通过 InnerClassLambdaMetafactory 生成了 CallSite 的子类 ConstantCallSite,当通过指令调用 CallSite 会返回函数式接口的实例,而生成接口实例的方式是通过内部类的方式,由于方法比较深,就不继续贴代码了。

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();
}

Lambda 表达式介绍和底层实现分析

CallSite 持有 com/para/lambda/MyFunctionInterfaceTest.lambda$main$0 方法的句柄,这个句柄会调用该方法。

Lambda 表达式介绍和底层实现分析

所以使用 lambda 表达式的地方,会在类编译的时候在本类中生成对应的私有静态方法和一个 INNERCLASS 的访问标识(具体是什么东西没找到资料,注释显示是一个访问标识),该访问标识会调用引导类加载器动态生成内部类,该内部类实现了函数式接口,在实现接口的方法中,会调用编译器生成静态方法,在使用 lambda 表达式的地方,通过传递内部类实例,来调用函数式接口方法。

总结

本文从 lambda 表达式、函数式接口的介绍和对 lambda 表达式底层原理的分析来认识 java 中的函数式编程。函数式接口本身就是一个普通的接口,而 lambda 表达式本质上和匿名内部类是一样的,只不过条件更加苛刻。使用 lamda 表达式可以以一种更优雅的方式来编程。

本文转载自:360 技术(ID:qihoo_tech) 点这里:2020Python高薪实战学习大合集**

[拿走不谢!Python 3.9 官方中文文档,限时领!] (http://dwz.date/dE6v)

[限时!速领!14张高清Python速查表,效率提升必备!] (http://dwz.date/dE6w)

[GitHub标星3W+,80个Python案例,带你轻松玩转Python学习!] (http://dwz.date/dE64)

上一篇:反编译解析数组使用foreach


下一篇:字符串拼接【面试题】