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

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

一、lambda表达式

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

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

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

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

(1)可选类型声明:不需要声明参数类型,编译器可以统一识别参数值;

(2)可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号;

(3)可选的大括号:如果主体包含了一个语句,就不需要使用大括号;

(4)可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

   使用lambda表达式,可以使用函数式接口,来代替内部类,赋予了Java简单但是强大的函数式编程能力,同时可以认为java支持命令式编程、声明式编程、函数式编程。

 

二、函数式接口

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

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

可以看到测试代码就会报错,因为增加一个方法之后,上面就不是函数式接口了。

 

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

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

 

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

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

 

三、lambda表达式原理分析

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

 

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

/ class version 52.0 (52)
// access flags 0x21
public class com/para/lambda/MyFunctionInterfaceTest {
  // compiled from: MyFunctionInterfaceTest.java
  // access flags 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
  // access flags 0x1

  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/para/lambda/MyFunctionInterfaceTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    LDC "   aaaa  "
    INVOKEDYNAMIC getValue()Lcom/para/lambda/MyFunctionInterface; [
      // handle kind 0x6 : 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;
      // arguments:
      (Ljava/lang/Object;)Ljava/lang/Object;,
      // handle kind 0x6 : INVOKESTATIC
      com/para/lambda/MyFunctionInterfaceTest.lambda$main$0(Ljava/lang/String;)Ljava/lang/String;,
      (Ljava/lang/String;)Ljava/lang/String;
    ]
    INVOKESTATIC com/para/lambda/MyFunctionInterfaceTest.testMethod (Ljava/lang/String;Lcom/para/lambda/MyFunctionInterface;)V
   L1
    LINENUMBER 6 L1
    LDC "   aaaa  "
    INVOKEDYNAMIC getValue()Lcom/para/lambda/MyFunctionInterface; [
      // handle kind 0x6 : 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;
      // arguments:
      (Ljava/lang/Object;)Ljava/lang/Object;,
      // handle kind 0x6 : INVOKESTATIC
      com/para/lambda/MyFunctionInterfaceTest.lambda$main$1(Ljava/lang/String;)Ljava/lang/String;,
      (Ljava/lang/String;)Ljava/lang/String;
    ]
    INVOKESTATIC com/para/lambda/MyFunctionInterfaceTest.testMethod (Ljava/lang/String;Lcom/para/lambda/MyFunctionInterface;)V
   L2
    LINENUMBER 7 L2
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x9
  // signature (Ljava/lang/String;Lcom/para/lambda/MyFunctionInterface<Ljava/lang/String;>;)V
  // declaration: void testMethod(java.lang.String, com.para.lambda.MyFunctionInterface<java.lang.String>)
  public static testMethod(Ljava/lang/String;Lcom/para/lambda/MyFunctionInterface;)V
   L0
    LINENUMBER 9 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    ALOAD 0
    INVOKEINTERFACE com/para/lambda/MyFunctionInterface.getValue (Ljava/lang/Object;)Ljava/lang/Object; (itf)
    CHECKCAST java/lang/String
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 10 L1
    RETURN
   L2
    LOCALVARIABLE str Ljava/lang/String; L0 L2 0
    LOCALVARIABLE functionInterface Lcom/para/lambda/MyFunctionInterface; L0 L2 1
    // signature Lcom/para/lambda/MyFunctionInterface<Ljava/lang/String;>;
    // declaration: functionInterface extends com.para.lambda.MyFunctionInterface<java.lang.String>
    MAXSTACK = 3
    MAXLOCALS = 2

  // 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方法的引用,以及后面的指令数组。使用jclasslib Bytecode viewer查看,显示的是调用链,如果想看具体的名称和描述符可以使用javap命令。

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;   

CallSite是引导类加载器加载的类,该类会动态代理生成一个内部类,该内部类会调用com/para/lambda/MyFunctionInterfaceTest.lambda$main$0方法。

 

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

总结

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

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页