Lambda简介

  • Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构
  • JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
  • Lambda表达式是Java SE 8中一个重要的新特性。Lambda表达式允许你通过表达式来代替功能接口。Lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
  • Lambda表达式还增强了集合库。Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说Lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。

Lambda定义

Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多
  • 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模板代码。

对接口的要求

虽然使用Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用Lambda 表达式来实现。Lambda规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法

jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

函数式接口

什么是函数式接口

(1)只包含一个抽象方法的接口,称为函数式接口。

(2)你可以通过 Lambda 表达式来创建该接口的对象。(若Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)。

(3)我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc也会包 含一条声明,说明这个接口是一个函数式接口。

@FunctionalInterface

修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。

Lambda表达式语法

基本语法

    1. Java8中引入了一个新的操作符,”->”,该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;

      左侧: Lambda表达式的参数列表,对应的是接口中抽象方法的参数列表;
      右侧: Lambda表达式中所需要执行的功能(Lambda体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法))

    1. Lambda表达式的实质是 对接口的实现;
  1. (parameters) ->{ statements; }
  2. //示例代码
  3. Comparator<Apple> byWeight2 = (Apple o1, Apple o2)-> o1.getWeight().compareTo(o2.getWeight());

解析:

  • 参数列表——这里它采用了Comparatorcompare 方法的参数,两个Apple
  • 箭头——箭头 -> 把参数列表与Lambda主体分隔开
  • Lambda主体——比较两个Apple 的重量。表达式就是Lambda的返回值了

Lambda语法示例

下面是Java Lambda表达式的简单例子:

  1. // 1. 不需要参数,返回值为 5
  2. () -> 5
  3. // 2. 接收一个参数(数字类型),返回其2倍的值
  4. x -> 2 * x
  5. // 3. 接受2个参数(数字),并返回他们的差值
  6. (x, y) -> x y
  7. // 4. 接收2个int型整数,返回他们的和
  8. (int x, int y) -> x + y
  9. // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
  10. (String s) -> System.out.print(s)

Lambda五大语法格式

语法格式一:接口中的抽象方法 : 无参数,无返回值;

  1. @Test
  2. public void test1(){
  3. /**
  4. *语法格式一、
  5. * 接口中的抽象方法 : 无参数,无返回值;
  6. */
  7. /*final */int num = 2; //jdk1.7之前必须定义为final的下面的匿名内部类中才能访问
  8. Runnable r = new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("Hello world!" + num); //本质还是不能对num操作(只是jdk自己为我们设置成了final的)
  12. }
  13. };
  14. r.run();
  15. System.out.println("----------使用Lambda输出-----------");
  16. Runnable r1 = () -> System.out.println("Hello world!" + num);//省去乐:大括号,分号
  17. r1.run();
  18. }

语法格式二:接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)

  1. @Test
  2. public void test2(){
  3. /**
  4. *语法格式二、
  5. * 接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)
  6. */
  7. Consumer<String> con = x -> System.out.println(x);
  8. con.accept("Lambda牛逼!");
  9. }

语法格式三:两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return

  1. @Test
  2. public void test3(){
  3. /**
  4. *语法格式三、
  5. * 两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return
  6. */
  7. Comparator<Integer> com = (x,y) -> {
  8. System.out.println("函数式接口,");
  9. return Integer.compare(y,x); //降序
  10. };
  11. Integer[] nums = {4,2,8,1,5};
  12. Arrays.sort(nums,com);
  13. System.out.println(Arrays.toString(nums));
  14. }

语法格式四:两个参数,有返回值,但是只有一条语句: 大括号省略,return省略

  1. @Test
  2. public void test4(){
  3. /**
  4. *语法格式四、
  5. * 两个参数,有返回值,但是只有一条语句: 大括号省略,return省略
  6. */
  7. Comparator<Integer> com = (x,y) -> Integer.compare(x,y);//升序
  8. Integer[] nums = {4,2,8,1,5};
  9. Arrays.sort(nums,com);
  10. System.out.println(Arrays.toString(nums));
  11. }

语法格式五:表达式的参数列表的数据类型 可以省略不写,因为JVM编译器通过上下文推断出数据类型,即”类型推断”,

  1. (Integer x,Integer y ) -> Integer.compare(x,y)
  2. //可以简写成
  3. (x,y) -> Integer.compare(x,y);

Lambda 表达式常用示例


Lambda创建线程


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

通过Thread创建线程示例代码:

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

通过Runnable创建线程示例代码:

  1. /*
  2. * Lambda实现Runnable接口
  3. * Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句
  4. */
  5. @Test
  6. public void test6() {
  7. // 1.1原来方式使用匿名内部类
  8. new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("Hello world !使用匿名内部类,开线程");
  12. }
  13. }).start();
  14. // 1.2使用 lambda expression
  15. new Thread(() -> System.out.println("使用 lambda expression,开线程")).start();
  16. // 2.1使用匿名内部类
  17. Runnable race1 = new Runnable() {
  18. @Override
  19. public void run() {
  20. System.out.println("使用匿名内部类,不开线程");
  21. }
  22. };
  23. // 2.2使用 lambda expression
  24. Runnable race2 = () -> System.out.println("使用 lambda expression,不开线程");
  25. // 直接调用 run 方法(没开新线程哦!)
  26. race1.run();
  27. race2.run();
  28. }

Lambda遍历集合


通过调用集合的 public void forEach(Consumer<? super E> action) 方法,通过lambda 表达式的方式遍历集合中的元素。以下是Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。

  1. //jdk提供的函数式接口
  2. @FunctionalInterface//这个注解是用判断是否是函数式接口
  3. public interface Consumer<T> {
  4. void accept(T t);
  5. //....
  6. }
  7. String[] atp = {"Rafael Nadal", "Novak Djokovic",
  8. "Stanislas Wawrinka",
  9. "David Ferrer","Roger Federer",
  10. "Andy Murray","Tomas Berdych",
  11. "Juan Martin Del Potro"};
  12. List<String> players = Arrays.asList(atp);
  13. // 以前的循环方式
  14. for (String player : players) {
  15. System.out.print(player + "; ");
  16. }
  17. // 使用 lambda 表达式以及函数操作(functional operation)
  18. players.forEach((player) -> System.out.print(player + "; "));
  19. // 在 Java 8 中使用双冒号操作符(double colon operator)
  20. players.forEach(System.out::println);
  21. }

删除集合中的某个元素


通过removeIf()方法来删除集合中的某个元素,**Predicate** 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。

  1. List<Person> javaProgrammers = new ArrayList<Person>() {
  2. {
  3. add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
  4. add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
  5. add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
  6. add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
  7. add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
  8. add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
  9. add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
  10. add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
  11. add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
  12. add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
  13. }
  14. };
  15. //removeIf()删除集合中符合条件的值
  16. javaProgrammers.removeIf(ele -> ele.getSalary() == 2000);
  17. //通过 foreach 遍历,查看是否已经删除
  18. javaProgrammers.forEach(System.out::println);

集合内元素的排序


在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。

  1. /*
  2. *Lambdas排序集合
  3. * 在Java中,Comparator 类被用来排序集合。
  4. */
  5. String[] players = {"Rafael Nadal", "Novak Djokovic",
  6. "Stanislas Wawrinka", "David Ferrer",
  7. "Roger Federer", "Andy Murray",
  8. "Tomas Berdych", "Juan Martin Del Potro",
  9. "Richard Gasquet", "John Isner"};
  10. // 1.1 使用匿名内部类根据 name 排序 players
  11. Arrays.sort(players, new Comparator<String>() {
  12. @Override
  13. public int compare(String s1, String s2) {
  14. return (s1.compareTo(s2));
  15. }
  16. });
  17. System.out.println("使用静态内部类排序结果:"+Arrays.toString(players));
  18. System.out.println("-----------------------分割线-------------------------");
  19. String[] players2 = {"Rafael Nadal", "Novak Djokovic",
  20. "Stanislas Wawrinka", "David Ferrer",
  21. "Roger Federer", "Andy Murray",
  22. "Tomas Berdych", "Juan Martin Del Potro",
  23. "Richard Gasquet", "John Isner"};
  24. // 1.2 使用 lambda expression 排序 players
  25. Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
  26. Arrays.sort(players2, sortByName);
  27. System.out.println("使用lambda排序结果(方式一):"+Arrays.toString(players2));
  28. // 1.3 也可以采用如下形式:
  29. Arrays.sort(players2, (String s1, String s2) -> (s1.compareTo(s2)));
  30. System.out.println("使用lambda排序结果(方式二):"+Arrays.toString(players2));

Lambda 表达式引用方法


有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。
语法:
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
示例代码:

  1. package com.zy.pagehelper.Interface;
  2. @FunctionalInterface
  3. public interface ReturnOneParam {
  4. /**
  5. *函数式接口
  6. * 一个参数有返回值
  7. */
  8. int method(int a);
  9. }
  10. public class LambdaTest {
  11. public static void main(String[] args) {
  12. //这里返回函数式接口ReturnOneParam一个参数
  13. ReturnOneParam lambda1 = a -> doubleNum(a);
  14. System.out.println(lambda1.method(3));
  15. //lambda2 引用了已经实现的 doubleNum 方法
  16. ReturnOneParam lambda2 = LambdaTest::doubleNum;
  17. System.out.println(lambda2.method(3));
  18. LambdaTest exe = new LambdaTest();
  19. //lambda4 引用了已经实现的 addTwo 方法
  20. ReturnOneParam lambda4 = exe::addTwo;
  21. System.out.println(lambda4.method(2));
  22. }
  23. /**
  24. * 要求
  25. * 1.参数数量和类型要与接口中定义的一致
  26. * 2.返回值类型要与接口中定义的一致
  27. */
  28. public static int doubleNum(int a) {
  29. return a * 2;
  30. }
  31. public int addTwo(int a) {
  32. return a + 2;
  33. }
  34. }

构造方法的引用


一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。

示例代码:

  1. public interface PersonCreatorBlankConstruct {
  2. /**接口作为对象的生成器
  3. *无参构造器
  4. */
  5. Person getPerson();
  6. }
  7. public interface PersonCreatorParamContruct {
  8. /*
  9. *有参构造器
  10. */
  11. Person getPerson(String firstName, String lastName, String job,
  12. String gender, int age, int salary);
  13. }
  14. /**
  15. *构造方法的引用
  16. * 一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
  17. * 该接口作为对象的生成器---->创建一个无参构造器,
  18. * 该接口作为对象的生成器---->创建一个有参构造器,
  19. */
  20. @Test
  21. public void test9() {
  22. /**
  23. *1.lambda表达式创建对象,返回无参函数接口,生参无参对象
  24. */
  25. PersonCreatorBlankConstruct creator = () -> new Person();
  26. Person person = creator.getPerson();
  27. PersonCreatorBlankConstruct creator2 = Person::new;
  28. Person person1 = creator2.getPerson();
  29. PersonCreatorParamContruct creator3 = Person::new;
  30. Person person2 = creator3.getPerson("名称", "修改名称","职位","男",23,2000);
  31. }

完毕!,搞定搞定lambda表达式的基本知识点,接下来我们才可以更深入的认识JDK8的新特性


参考/推荐博客:
Lambda表达式详解
Java8新特性入门一(Lambda表达式一)
Java Lambda表达式入门