Lambda表达式初体验

Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。

  1. public interface UserService {
  2. void show();
  3. }
public class LambdaTest {
    public static void main(String[] args) {
        toShow(new UserService() {
            @Override
            public void show() {
                System.out.println("使用匿名内部类");
            }
        });
        toShow(() -> {
            System.out.println("使用Lambda表达式");
        });
    }

    private static void toShow(UserService userService) {
        userService.show();
    }    
}

运行结果:
image.png
可以看到使用匿名内部类和Lambda表达式都可以实现只有一个抽象方法一个接口,并且可以达到一样的目的,但是Lambda表达式明显是语法简单,只关注了抽象方法的实现内容。但是使用Lambda表达式是有要求的:Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。@FunctionalInterface 是一个标志注解,被修饰的接口中的抽象方法只能有一个,这个注解往往会和 lambda 表达式一起出现。比如:
image.png
定义了两个抽象方法,就会编译不通过,因此@FunctionalInterface 注解是规定接口只能有一个抽象方法的好帮手。

Lambda表达式的演变过程

Lambda表达式是jdk8的新特性,其出现的目的是为了避免过多的内部类,去掉一些没有意义的代码,只留下核心的代码,其实质属于函数式编程的概念,而Lambda表达式在多线程这一块使用的很多,因此在这里介绍。Lambda表达式只适用于函数式接口的使用,也就是只有一个方法的接口,这样可以简化大量代码。

public class Test {
    static class Love2 implements LoveInterface {

        @Override
        public void love(String a, String b) {
            System.out.println("I love you too --> " + a + b);
        }
    }

    public static void main(String[] args) {
        LoveInterface loveInterface = null;

        //成员内部类
        loveInterface = new Love1();
        loveInterface.love("test01  ", "成员内部类");


        //静态内部类
        loveInterface = new Love2();
        loveInterface.love("test02  ", "静态内部类");


        //局部内部类
        class Love3 implements LoveInterface {

            @Override
            public void love(String a, String b) {
                System.out.println("I love you --> " + a + b);
            }
        }
        loveInterface = new Love3();
        loveInterface.love("test03 ", "局部内部类");


        //匿名内部类
        loveInterface = new LoveInterface() {
            @Override
            public void love(String a, String b) {
                System.out.println("I love you --> " + a + b);
            }
        };
        loveInterface.love("test04 ", "匿名内部类");


        //Lambda表达式1.0
        loveInterface = (String a, String b) -> {
            System.out.println("I love you --> " + a + b);
        };
        loveInterface.love("test05 ", "Lambda表达式1.0");

        //Lambda表达式2.0(如果参数只有一个括号也可以省略)
        loveInterface = (a, b) -> {
            System.out.println("I love you --> " + a + b);
        };
        loveInterface.love("test06 ", "Lambda表达式2.0");

        //Lambda表达式3.0(如果方法体不止一行则不能去掉大括号)
        loveInterface = (a, b) -> System.out.println("I love you --> " + a + b);
        loveInterface.love("test07 ", "Lambda表达式3.0");

    }
}

interface LoveInterface {
    void love(String a, String b);
}

class Love1 implements LoveInterface {

    @Override
    public void love(String a, String b) {
        System.out.println("I love you --> " + a + b);
    }
}

通过上述代码可以看到,函数式接口的编写从“成员内部类”—>“静态内部类”—>“局部内部类”—>“匿名内部类”—>“Lambda表达式”的不断简化,通过Lambda表达式,对于函数式接口,无需它的实现类、方法名、参数类型等等,大量简化代码。

Lambda表达式语法

Lambda表达式由组成三部分:

  (参数类 参数名 ...)  -> { //方法体 }
// () 为参数列表    ->  为运算符(读作goes to)        { } 为方法体

由此可见lambda表达式简化了方法的修饰符,返回值,方法名
image.png

注意:

  1. 小括号内的所有参数都可以不
  2. 写参数类型
  3. 只有一个参数时可省略(),方法体只有一条语句时可以省略{ }
  4. 如果方法体只有一行return语句,那么可以一并省略“return”和“;”

Lambda表达式的原理

public class LambdaTest {

    public static void main(String[] args) {
        toShow(() -> {
            System.out.println("使用Lambda表达式");
        });

    }

    private static void toShow(UserService userService) {
        userService.show();
    }
}

首先,使用“javap -p LambdaTest.class”查看 LambdaTest 类的 class 文件中所有方法,发现有一个不是我们自己定义的方法,那就是“private static void lambda$main$0()”image.png
然后使用“javap -c -p LambdaTest.class”,查看 lambda$main$0() 方法:
image.png
可以看到方法体的内容就是lambda表达式的方法体内容。然后查看jdk的 LambdaMetafactory
类处理lambda的方法:

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        // 通过new一个InnerClassLambdaMetafactory并调用buildCallSite
        // 为Lambda表达式生成了一个内部类
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

可见其实是再程序运行过程中动态生成一个匿名内部类,关于内部类,在运行时加上vm参数 -Djdk.internal.lambda.dumpProxyClasses,可以发现生成了:
image.png
由此可以知道Lambda表达式,编译器在类中生成一个静态函数,运行时调以内部类形式调用该静态函数,就好比这样的代码:

public class LambdaTest {

    public static void main(String[] args) {
        toShow(() -> {
            lambda$main$0();
        });
    }

    private static void toShow(UserService userService) {
        userService.show();
    }

    private static void lambda$main$0(){
        System.out.println("使用Lambda表达式");
    }
}

Lamba和匿名内部类的对比

所需类型

  • 匿名内部类的类型可以是类、抽象类、接口
  • Lambda表达式需要的类型必须是接口

    抽象方法的数量

  • 匿名内部类所需的接口中的抽象方法是没有限制的,只有全部实现即可

  • Lambda表达式所需的接口中只能有一个抽象方法

    实现原理

  • 匿名内部类是在编译后生成一个Class

  • Lambda表达式是在程序运行时动态生成Class

    Lambda表达式的主要用途

    Lambda表达式创建线程

    以前都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。

    Thread t = new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        System.out.println(2 + ":" + i);
      }
    });
    t.start();
    

    给集合排序

    再集合排序中,如果要自定义排序规则,就需要实现Comparable接口,而它刚好只有一个comperTo()抽象方法,这里就可以使用到lambda表达式。

    public static void main(String[] args) {
          List<Person> list=new ArrayList<>();
          Person person1=new Person("周杰伦",40);
          Person person2=new Person("林俊杰",38);
          Person person3=new Person("薛之谦",36);
          Person person4=new Person("易烊千玺",20);
          list.add(person1);
          list.add(person2);
          list.add(person3);
          list.add(person4);
    
          list.sort((p1, p2) -> {
              return p1.age - p2.age;
          });
    
          list.forEach(System.out::println);
      }
    

    遍历集合

    public static void main(String[] args) {
          ArrayList<Integer> list = new ArrayList<>();
          Collections.addAll(list, 1,2,3,4,5);
          //lambda表达式 方法引用
          list.forEach(System.out::println);
          System.out.println("----------------");
          list.forEach(element -> {
              if (element % 2 == 0) {
                  System.out.println(element);
              }
          });
      }