zoukankan      html  css  js  c++  java
  • 学习Java8系列-Lambda

    Lambda演进

    小王在公司正在开发一个学生管理系统,产品经理向他提出一个需求,要筛选出年龄大于15的学生,于是小王写出了以下代码:

        public static List<Student> filterAgeStudent(List<Student> students) {
            List<Student> list = Lists.newArrayList();
            for (Student student : students) {
                if (student.getAge() > 15) {
                    list.add(student);
                }
            }
            return list;
        }

    过了几天产品经理又提出了一个需求,要筛选出体重大于50KG的学生,于是小王新增了一个方法:

        public static List<Student> filterWeightStudent(List<Student> students) {
            List<Student> list = Lists.newArrayList();
            for (Student student : students) {
                if (student.getWeight() > 50) {
                    list.add(student);
                }
            }
            return list;
        }

    过了一段时间,产品提出了要筛选出体重大于50并且年龄要大于15的学生,小王突然感觉到这不是一个简单的需求,于是小王仔细思考了一下,突然想到将每种筛选的策略抽象成为一个接口,并且将这个接口当做一个参数传入方法中,这样每次就可以只新增策略,其他代码不需要更改了,这样就满足了软件设计的六大原则的开放闭合原则,于是乎诞生以下的设计和代码:

    public interface StudentPredicate {
        boolean filter(Student student);
    }
    public class AgeStudentPredicate implements StudentPredicate {
        @Override
        public boolean filter(Student student) {
            return student.getAge() > 20 ? true : false;
        }
    }
    public static List<Student> filterStudent(List<Student> students,
                                                  StudentPredicate predicate)
     
    {
        List<Student> list = Lists.newArrayList();
        for (Student student : students) {
           if (predicate.filter(student)) {
              list.add(student);
           }
        }
        return list;
    }

    经过一段时间的学习,小王接触到匿名类,于是小王代码进行更改,以后再也不需要写策略了:

    List<Student> list = filterStudent(students, new StudentPredicate() {
         @Override
         public boolean filter(Student student) {
             return student.getAge() > 15;
         }
    });

    学习到匿名类以后,小王感觉到Java的浩瀚,然后继续学习,后来接触到Lambda,于是对待做了以下改造:

     List<Student> list = filterStudent(students, student -> student.getAge() > 15);

    Lambda知识整理

    Lambda定义

    从上面的演进过程,我们基本上可以得到Lambda表达式是一种匿名函数,简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。Java中的Lambda表达式通常使用(argument) -> (body)语法书写,常用的Lamda表达式例子有:

    (int a, int b) -> {  return a + b; }

    () -> System.out.println("Hello World");

    (String s) -> { System.out.println(s); }
    函数式接口

    函数式接口指的是是只包含一个抽象方法声明的接口。例如java.lang.Runnable就是一种函数式接口,在 Runnable接口中只声明了一个抽象方法方法void run();

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }

    对于注解@FunctionalInterface对于lamda表达式来说的话并不是必要的,@FunctionalInterface是Java8新加入的一种接口,用于指明该接口类型声明是根据Java语言规范定义的函数式接口。Java8还声明了一些Lambda表达式可以使用的函数式接口,当你注释的接口不是有效的函数式接口时,可以使用@FunctionalInterface解决编译层面的错误。

    常用函数式

    Java8中在java.util.function中引入了很多的函数式接口,这里介绍一下3个常用的函数式接口,

    1. Predicate
      Predicate接口定义一个名叫test的抽象方法,它接收泛型T对象,并返回一个boolean类型。经常使用到的地方是在流处理的过程中filter方法,满足条件的数据才会被过滤出来,例如我们上面的例子也可以改造成为Predicate函数式接口的形式。
    @FunctionalInterface
    public interface Predicate<T{
       boolean test(T t);
    }
    public static List<Student> filterStudent(List<Student> students,
                                              Predicate<Student> predicate) {
       List<Student> list = Lists.newArrayList();
       for (Student student : students) {
       if (predicate.test(student)) {
         list.add(student);
       }
      }
      return list;
    }
    1. Consumer
      Consumer定义一个名叫accept的抽象方法,他接受泛型T的对象,没有返回值。如果你需要访问泛型对象T,并其进行修改,就使用Consumer。经常使用的地方就是常用的forEach方法。
    @FunctionalInterface
    public interface Consumer<T{
      void accept(T t);
    }
    void forEachOrdered(Consumer<? super T> action);
    1. Function
      Function定义一个叫apply的方法,他接受一个泛型对象T,返回一个泛型对象R。如果你需要定义一个Lambda表达式,将输入的对象映射到输出,就使用Function,经常使用到的地方就是常用的map方法。
    @FunctionalInterface
    public interface Function<TR{
      apply(T t);
    }
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    Lambda原理窥探

    小王经过上面一系列学习,开始思考Lambda的原理是什么,因为Java8中每一个Lambda表达式必须有一个函数式接口与之对应,小王就思考经过编译器编译以后到可能实现的方式有两种,一种生成实现接口的类,另外一种是内部类,于是决定看一下反编译的以后代码,以解除心中的疑惑;

    @FunctionalInterface
    public interface Func {
        int add(int x, int y);
    }
    public class LambdaTest {
        public static void main(String[] args{
            Func func = (x, y) -> x + y;
            System.out.println(func.add(12));
        }
    }

    通过javap -p -v -c LambdaTest.class查看反编译后的代码,

    Classfile /Users/wangtongzhou/Documents/Java/learning/target/classes/com/springboot2/learning/javabasic/java8/LambdaTest.class
      Last modified 2020-7-11; size 1392 bytes
      MD5 checksum ec7d77a8b0b0a0cb5940f80a9b27b3d0
      Compiled from "LambdaTest.java"
    public class com.springboot2.learning.javabasic.java8.LambdaTest
      minor version0
      major version52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#29         // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#34         // #0:add:()Lcom/springboot2/learning/javabasic/java8/Func;
       #3 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = InterfaceMethodref #37.#38        // com/springboot2/learning/javabasic/java8/Func.add:(II)I
       #5 = Methodref          #39.#40        // java/io/PrintStream.println:(I)V
       #6 = Class              #41            // com/springboot2/learning/javabasic/java8/LambdaTest
       #7 = Class              #42            // java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               LocalVariableTable
      #13 = Utf8               this
      #14 = Utf8               Lcom/springboot2/learning/javabasic/java8/LambdaTest;
      #15 = Utf8               main
      #16 = Utf8               ([Ljava/lang/String;)V
      #17 = Utf8               args
      #18 = Utf8               [Ljava/lang/String;
      #19 = Utf8               func
      #20 = Utf8               Lcom/springboot2/learning/javabasic/java8/Func;
      #21 = Utf8               MethodParameters
      #22 = Utf8               lambda$main$0
      #23 = Utf8               (II)I
      #24 = Utf8               x
      #25 = Utf8               I
      #26 = Utf8               y
      #27 = Utf8               SourceFile
      #28 = Utf8               LambdaTest.java
      #29 = NameAndType        #8:#9          // "<init>":()V
      #30 = Utf8               BootstrapMethods
      #31 = MethodHandle       #6:#43         // 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;
      #32 = MethodType         #23            //  (II)I
      #33 = MethodHandle       #6:#44         // invokestatic com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
      #34 = NameAndType        #45:#46        // add:()Lcom/springboot2/learning/javabasic/java8/Func;
      #35 = Class              #47            // java/lang/System
      #36 = NameAndType        #48:#49        // out:Ljava/io/PrintStream;
      #37 = Class              #50            // com/springboot2/learning/javabasic/java8/Func
      #38 = NameAndType        #45:#23        // add:(II)I
      #39 = Class              #51            // java/io/PrintStream
      #40 = NameAndType        #52:#53        // println:(I)V
      #41 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest
      #42 = Utf8               java/lang/Object
      #43 = Methodref          #54.#55        // 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;
      #44 = Methodref          #6.#56         // com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
      #45 = Utf8               add
      #46 = Utf8               ()Lcom/springboot2/learning/javabasic/java8/Func;
      #47 = Utf8               java/lang/System
      #48 = Utf8               out
      #49 = Utf8               Ljava/io/PrintStream;
      #50 = Utf8               com/springboot2/learning/javabasic/java8/Func
      #51 = Utf8               java/io/PrintStream
      #52 = Utf8               println
      #53 = Utf8               (I)V
      #54 = Class              #57            // java/lang/invoke/LambdaMetafactory
      #55 = NameAndType        #58:#62        // 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;
      #56 = NameAndType        #22:#23        // lambda$main$0:(II)I
      #57 = Utf8               java/lang/invoke/LambdaMetafactory
      #58 = Utf8               metafactory
      #59 = Class              #64            // java/lang/invoke/MethodHandles$Lookup
      #60 = Utf8               Lookup
      #61 = Utf8               InnerClasses
      #62 = Utf8               (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;
      #63 = Class              #65            // java/lang/invoke/MethodHandles
      #64 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #65 = Utf8               java/lang/invoke/MethodHandles
    {
      public com.springboot2.learning.javabasic.java8.LambdaTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/springboot2/learning/javabasic/java8/LambdaTest;

      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=4, locals=2, args_size=1
             0: invokedynamic #2,  0              // InvokeDynamic #0:add:()Lcom/springboot2/learning/javabasic/java8/Func;
             5: astore_1
             6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             9: aload_1
            10: iconst_1
            11: iconst_2
            12: invokeinterface #4,  3            // InterfaceMethod com/springboot2/learning/javabasic/java8/Func.add:(II)I
            17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
            20: return
          LineNumberTable:
            line 5: 0
            line 6: 6
            line 7: 20
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      21     0  args   [Ljava/lang/String;
                6      15     1  func   Lcom/springboot2/learning/javabasic/java8/Func;
        MethodParameters:
          Name                           Flags
          args

      private static int lambda$main$0(int, int);
        descriptor: (II)I
        flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
        Code:
          stack=2, locals=2, args_size=2
             0: iload_0
             1: iload_1
             2: iadd
             3: ireturn
          LineNumberTable:
            line 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       4     0     x   I
                0       4     1     y   I
        MethodParameters:
          Name                           Flags
          x                              synthetic
          y                              synthetic
    }
    SourceFile: "LambdaTest.java"
    InnerClasses:
         public static final #60= #59 of #63; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0#31 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;
        Method arguments:
          #32 (II)I
          #33 invokestatic com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I

    反编译以后lambda表达式被编译成为一个lambda$main$0的函数,其实就是一段(x, y) -> x + y的方法,在看main方法主要分为以下8个步骤:

    1. 通过invokedynamic指令生成调用对象;
    2. 存入本地缓存;
    3. 加载java.lang.System.out静态方法;
    4. 将lambda表达式生成的对象加载入执行栈;
    5. 将int类型1加载入执行栈;
    6. 将int类型2加载入执行栈;
    7. 执行lambda表达式生成的对象的add方法;
    8. 输出执行结果;
      重点部分重点部分
      从mian方法中我们的重点就在于invokedynamic这个指令,重点要了解下是如何通过invokedynamic指令生成目标对象,invokedynamic指令通过找到BootstrapMethods中的方法,生成动态调用点,也是调用LambdaMetafactory的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();
    }

    通过源码可以看出,metafactory方法通过InnerClassLambdaMetafactory类生成对象,并提供后续调用,在InnerClassLambdaMetafactory源码中可以看到,有提供开关是否dump生成的class文件。



    接下来我们通过设置启动参数-Djdk.internal.lambda.dumpProxyClasses查看中间对象,增加这个参数以后会生成LambdaTest$$Lambda$1类,
    final class LambdaTest$$Lambda$1 implements Func {
        private LambdaTest$$Lambda$1() {
        }

        @Hidden
        public int add(int var1, int var2) {
            return LambdaTest.lambda$main$0(var1, var2);
        }
    }

    我们再看下上面这个类反编译以后的情况

    Classfile /Users/wangtongzhou/Documents/Java/learning/com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1.class
      Last modified 2020-7-11; size 437 bytes
      MD5 checksum 729979930540708c60f4e71e63b69321
    final class com.springboot2.learning.javabasic.java8.LambdaTest$$Lambda$1 implements com.springboot2.learning.javabasic.java8.Func
      minor version: 0
      major version: 52
      flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
    Constant pool:
       #1 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1
       #2 = Class              #1             // com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1
       #3 = Utf8               java/lang/Object
       #4 = Class              #3             // java/lang/Object
       #5 = Utf8               com/springboot2/learning/javabasic/java8/Func
       #6 = Class              #5             // com/springboot2/learning/javabasic/java8/Func
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = NameAndType        #7:#8          // "<init>":()V
      #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
      #11 = Utf8               add
      #12 = Utf8               (II)I
      #13 = Utf8               Ljava/lang/invoke/LambdaForm$Hidden;
      #14 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest
      #15 = Class              #14            // com/springboot2/learning/javabasic/java8/LambdaTest
      #16 = Utf8               lambda$main$0
      #17 = NameAndType        #16:#12        // lambda$main$0:(II)I
      #18 = Methodref          #15.#17        // com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
      #19 = Utf8               Code
      #20 = Utf8               RuntimeVisibleAnnotations
    {
      private com.springboot2.learning.javabasic.java8.LambdaTest$$Lambda$1();
        descriptor: ()V
        flags: ACC_PRIVATE
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #10                 // Method java/lang/Object."<init>":()V
             4return

      public int add(intint);
        descriptor: (II)I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=3
             0: iload_1
             1: iload_2
             2: invokestatic  #18                 // Method com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
             5: ireturn
        RuntimeVisibleAnnotations:
          0#13()
    }

    由此我们可以得出编译以后的代码为:

    public class LambdaTest {
        public static void main(String[] args) {
           Func func= LambdaTest$$Lambda$1();
           System.out.println(func.add(12));
        }
        private static int lambda$main$0(int x, int y) {
            return x + y;
        }

        static final class LambdaTest$$Lambda$1 implements Func {
        private LambdaTest$$Lambda$1() {
        }

        public int add(int x, inty) {
            return LambdaTest.lambda$main$0(x,y);
        }
      }
    }

    总结下,Lambda底层就是通过一个静态的内部类实现的;

    结尾

    欢迎大家点点关注,点点赞,感谢!

  • 相关阅读:
    对象直接量
    js学习类
    jquery.js与sea.js综合使用
    拥抱模块化的JavaScript
    匿名函数与闭包
    js对象如何合并?
    Web.config配置文件详解
    javascipt自定义命名空间、静态类、实例对象
    jQuery源码的基础知识
    企业架构/企业开发 [Enterprise architecture / Enterprise Development]
  • 原文地址:https://www.cnblogs.com/wtzbk/p/13296427.html
Copyright © 2011-2022 走看看