Lambda表达式使用详细讲解

目录

1.新思想

1.1函数式编程思想

1.2.函数式接口

2.通往lambda之路

2.1.什么是lambda表示式?

2.2. lambda表示式有哪些特点?

2.3.lambda表示式使用场景

2.4.lambda表示式语法

2.5.Lambda简化写法

2.6.Lambda表达式结构

3.Stream流

3.1概述

3.2.stream的操作

3.2、stream的使用

3.2.1、中间操作-filter

3.2.2、中间操作-map

4.Optional类深度解析

4.1.Optional类包含的方法

4.2.一些例子

 5.参考


1.新思想

1.1函数式编程思想

        面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操
作。

1.2.函数式接口

        函数式接口(functional interface 也叫功能性接口,其实是同一个东西)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但 最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。

Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现,下面讲到语法会讲到

2.通往lambda之路

2.1.什么是lambda表示式?

        Lambda表达式是JDK 1.8所增加的新特性,其目的主要是简化代码,而且同时也具有可读性

2.2. lambda表示式有哪些特点?

lambda表示式是一种匿名函数,具有以下特点:

  • 简洁:使用lambda可以省略函数名和return关键字,使代码更加简洁。
  • 灵活:lambda可以作为参数传递给其他函数,也可以嵌套在其他函数中使用。
  • 一次性:lambda表示式通常只用于一次性的简单函数,不适用于复杂的函数逻辑。

需要注意的是,lambda并不是Python中唯一的匿名函数实现方式,还有一些其他的方式,如使用functools模块中的partial函数

2.3.lambda表示式使用场景

lambda表示式可以用在很多场景中,例如:

  • 函数式编程:lambda可以用来定义匿名函数,这在函数式编程中非常常见。
  • 排序:lambda可以作为排序函数的参数,用来指定排序规则。
  • 过滤:lambda可以作为过滤函数的参数,用来指定过滤条件。
  • 映射:lambda可以作为映射函数的参数,用来指定映射规则。
  • GUI编程:lambda可以用来绑定事件处理程序,简化代码。

总的来说,lambda表示式是一种非常灵活的语法结构,在很多场景中都可以发挥作用。

2.4.lambda表示式语法

基本语法:
(parameters) -> expression

(parameters) ->{ statements; }

包含三个部分

  1. 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
  2. 一个箭头符号:->
  3. 方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}

- 小括内的语法与传统方法参数列表一致,没有参数就留空,有多个参数就用逗号分隔

-  【->】  是新引入的语法格式,代表指向动作

   - 大括号内的语法与传统方法体要求一致

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

看一个完整的例子,方便理解

/**
 * 测试lambda表达式
 *
 * @author benhail
 */
public class TestLambda {

    public static void runThreadUseLambda() {
        //Runnable是一个函数接口,只包含了有个无参数的,返回void的run方法;
        //所以lambda表达式左边没有参数,右边也没有return,只是单纯的打印一句话
        new Thread(() ->System.out.println("lambda实现的线程")).start(); 
    }

    public static void runThreadUseInnerClass() {
        //这种方式就不多讲了,以前旧版本比较常见的做法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类实现的线程");
            }
        }).start();
    }

    public static void main(String[] args) {
        TestLambda.runThreadUseLambda();
        TestLambda.runThreadUseInnerClass();
    }
}

可以看出,使用lambda表达式设计的代码会更加简洁,而且还可读。

2.5.Lambda简化写法

Lambda表达式使用前提:只能对函数式接口(有且仅有一个抽象方法的接口)的匿名内部类进行简化;
Lambda表达式是可推导可以省咯的,Lambda表达式可以推导出来,省略就是重写唯一的抽象方法;
Lambda表达式可以推导出来,唯一抽象方法的参数和返回值,我们可以进一步对参数和返回值进行简化;

其中参数列表写的是所重写的方法中需要传递的参数,若不需要传递参数则空着即可,箭头表示把前面的参数传递到后面的方法体中

其实,Lambda表达式的核心原则是:只要可以推导,都可以省略,即可以根据上下文推导出来的内容,都可以省略书写,可以省略的内容如下:

简化:
1.(参数列表):参数列表中的数据类型可以省略不写
(int a)==>(a)(int a,double d)==>(a,d)
2,(参数列表):参数列表中只有一个参数,那么小括号()也可以省咯不写,但是如果没有参数,小括号必须写
(inta)==>(a)==>a    ()==>()
3.{重写抽像方法的方法体}:无论方法是否有返回值,如果方法体只有一行代码,省略return和{}和这行代码结束的分号(若要省略必须三个一起省略);

2.5.1.省略规则

  • 参数类型可以省略
  • ·方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
  • ·方法只有一个参数时小括号可以省略

2.5.2.格式说明:

                - 小括内的语法与传统方法参数列表一致,没有参数就留空,有多个参数就用逗号分隔

                -  【->】  是新引入的语法格式,代表指向动作

                - 大括号内的语法与传统方法体要求一致
 

2.6.Lambda表达式结构

  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
  • Lambda 表达式的主体可包含零条或多条语句
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

3.Stream流

        stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。

3.1概述

Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们
对集合或数组操作。

3.2.stream的操作

每个Stream都有两种模式: 顺序执行和并行执行。

顺序流:

List  people = list.getStream.collect(Collectors.toList());

并行流:

List  people = list.getStream.parallel().collect(Collectors.toList());

顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

Stream中常用方法如下:

1)Stream filter​(Predicate predicate):用于对流中的数据进行过滤

    Predicate接口中的方法 boolean test​(T t):对给定的参数进行判断,返回一个布尔值

2)Stream limit​(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据

3)Stream skip​(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流

4)static Stream concat​(Stream a, Stream b):合并a和b两个流为一个流

5)Stream distinct​():返回由该流的不同元素(根据Object.equals(Object) )组成的流

6)Stream sorted​():返回由此流的元素组成的流,根据自然顺序排序

7)Stream sorted​(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序

8) Stream map​(Function mapper):返回由给定函数应用于此流的元素的结果组成的流Function接口中的方法 R apply​(T t)

9)IntStream mapToInt​(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果

        IntStream:表示原始 int 流

        ToIntFunction接口中的方法 int applyAsInt​(T value)


3.2、stream的使用

    Stream流接口中定义了许多对于集合的操作方法,总的来说可以分为两大类:中间操作和终端操作:
  1. 中间操作:会返回一个流,通过这种方式可以将多个中间操作连接起来,形成一个调用链,从而转换为另外 一个流。除非调用链后存在一个终端操作,否则中间操作对流不会进行任何结果处理。
  2. 终端操作:会返回一个具体的结果,如boolean、list、integer等。

数据准备如下:

public class Employee {
    //姓名
    private String name;
    //年龄
    private Integer age;
    //性别
    private String sex;
    //薪资
    private Double salary;
    //住址
    private String address;
    //工作地
    private String baseAddress;
    //职位
    private String position;
    //主管
    private Employee employee;
    public Employee(String name, Integer age, String sex, Double salary, String address, String baseAddress, String position, Employee employee) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
        this.address = address;
        this.baseAddress = baseAddress;
        this.position = position;
        this.employee = employee;
    }
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
//创建原始数据       
List list = new ArrayList<>();
Employee ceo = new Employee("张三", 56, "男", 50000.42D, "浙江杭州", "浙江杭州", "ceo", null);
Employee manager1 = new Employee("李四", 47, "女", 20000.7D, "浙江宁波", "浙江宁波", "经理", ceo);
Employee manager2 = new Employee("王五", 45, "男", 24000.5D, "浙江金华", "浙江金华", "经理", ceo);
Employee employee1 = new Employee("麻六", 27, "女", 7000.6D, "浙江宁波", "广东广州", "售前", manager1);
Employee employee2 = new Employee("孙七", 28, "男", 8000.8D, "浙江宁波", "广东深圳", "售后", manager1);
Employee employee3 = new Employee("赵八", 27, "女", 9500.2D, "浙江杭州", "云南昆明", "售前", manager2);
Employee employee4 = new Employee("钱九", 26, "男", 9000.0D, "浙江杭州", "云南玉溪", "售后", manager2);
list.add(ceo);
list.add(manager1);
list.add(manager2);
list.add(employee1);
list.add(employee2);
list.add(employee3);
list.add(employee4);

3.2.1、中间操作-filter

public class StreamTest {
    public static void main(String[] args) {
        //查找名字为张三的员工信息,并打印出来
        list.stream().filter(employee -> StringUtils.equals("张三", employee.getName())).forEach(employee -> System.out.println(employee.getName()));
    }
}

结果:

张三

filter返回一个Boolean类型的值,true为保留,false为舍弃。

3.2.2、中间操作-map

//收集公司里所有员工的名字,并打印出来
list.stream().map(employee -> {
return employee.getName(); 
}).forEach(name-> System.out.println(name));

结果:

张三
李四
王五
麻六
孙七
赵八
钱九

4.Optional类深度解析

        调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法。我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。这正是一些类似Guava的外部API试图解决的问题。一些JVM编程语言比如Scala、Ceylon等已经将对在核心API中解决了这个问题。在我的前一篇文章中,介绍了Scala是如何解决了这个问题。

这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

4.1.Optional类包含的方法

4.1.1 of

为非null的值创建一个Optional。

of方法通过工厂方法创建Optional类。需要注意的是,创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException 。

//调用工厂方法创建Optional实例
Optional name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional someNull = Optional.of(null);

4.1.2.ofNullable

为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。示例如下:

//下面创建了一个不包含任何值的Optional实例
//例如,值为'null'
Optional empty = Optional.ofNullable(null);

4.1.3.isPresent

非常容易理解

如果值存在返回true,否则返回false。

类似下面的代码:

//isPresent方法用来检查Optional实例中是否包含值
if (name.isPresent()) {
  //在Optional实例内调用get()返回已存在的值
  System.out.println(name.get());//输出Sanaulla
}

4.1.4.get

如果Optional有值则将其返回,否则抛出NoSuchElementException。

上面的示例中,get方法用来得到Optional实例中的值。下面我们看一个抛出NoSuchElementException的例子:

//执行下面的代码会输出: No value present 
try {
  //在空的Optional实例上调用get(),抛出NoSuchElementException
  System.out.println(empty.get());
} catch (NoSuchElementException ex) {
  System.out.println(ex.getMessage());
}

4.1.5.ifPresent

如果Optional实例有值则为其调用consumer,否则不做处理

要理解ifPresent方法,首先需要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接通过lambda表达式传入参数。

如果Optional实例有值,调用ifPresent()可以接受接口段或lambda表达式。类似下面的代码:

//ifPresent方法接受lambda表达式作为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
  System.out.println("The length of the value is: " + value.length());
});

4.1.6.orElse

如果有值则将其返回,否则返回指定的其它值。

如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。示例如下:

//如果值不为null,orElse方法返回Optional实例的值。
//如果为null,返回传入的消息。
//输出: There is no value present!
System.out.println(empty.orElse("There is no value present!"));
//输出: Sanaulla
System.out.println(name.orElse("There is some value!"));

4.1.7.orElseGet

orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。示例如下:

//orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
//orElseGet可以接受一个lambda表达式生成默认值。
//输出: Default Value
System.out.println(empty.orElseGet(() -> "Default Value"));
//输出: Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));

4.1.8.orElseThrow

如果有值则将其返回,否则抛出supplier接口创建的异常。

在orElseGet方法中,我们传入一个Supplier接口。然而,在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。示例如下:

try {
  //orElseThrow与orElse方法类似。与返回默认值不同,
  //orElseThrow会抛出lambda表达式或方法生成的异常 

  empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
  //输出: No value present in the Optional instance
  System.out.println(ex.getMessage());
}

ValueAbsentException定义如下:

class ValueAbsentException extends Throwable {

  public ValueAbsentException() {
    super();
  }

  public ValueAbsentException(String msg) {
    super(msg);
  }

  @Override
  public String getMessage() {
    return "No value present in the Optional instance";
  }
}

4.1.9.map

map方法文档说明如下:

如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。

map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。如果你不熟悉Function接口,可以参考我的这篇博客。map方法示例如下:

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值创建新的Optional实例作为map方法的返回值。
Optional upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

4.1.10.flatMap

如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。

参照map函数,使用flatMap重写的示例如下:

//flatMap与map(Function)非常类似,区别在于传入方法的lambda表达式的返回类型。
//map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 
//但flatMap方法中的lambda表达式返回值必须是Optionl实例。 
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//输出SANAULLA

4.1.11.filter

filter个方法通过传入限定条件对Optional实例的值进行过滤。文档描述如下:

如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。

读到这里,可能你已经知道如何为filter方法传入一段代码。是的,这里可以传入一个lambda表达式。对于filter函数我们应该传入实现了Predicate接口的lambda表达式。如果你不熟悉Predicate接口,可以参考这篇文章。

现在我来看看filter的各种用法,下面的示例介绍了满足限定条件和不满足两种情况:

//filter方法检查给定的Option值是否满足某些条件。
//如果满足则返回同一个Option实例,否则返回空Optional。
Optional longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla

//另一个例子是Optional值不满足filter指定的条件。
Optional anotherName = Optional.of("Sana");
Optional shortName = anotherName.filter((value) -> value.length() > 6);
//输出: name长度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

4.2.一些例子

public class OptionalDemo {

  public static void main(String[] args) {
    //创建Optional实例,也可以通过方法返回值得到。
    Optional name = Optional.of("Sanaulla");

    //创建没有值的Optional实例,例如值为'null'
    Optional empty = Optional.ofNullable(null);

    //isPresent方法用来检查Optional实例是否有值。
    if (name.isPresent()) {
      //调用get()返回Optional值。
      System.out.println(name.get());
    }

    try {
      //在Optional实例上调用get()抛出NoSuchElementException。
      System.out.println(empty.get());
    } catch (NoSuchElementException ex) {
      System.out.println(ex.getMessage());
    }

    //ifPresent方法接受lambda表达式参数。
    //如果Optional值不为空,lambda表达式会处理并在其上执行操作。
    name.ifPresent((value) -> {
      System.out.println("The length of the value is: " + value.length());
    });

    //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
    System.out.println(empty.orElse("There is no value present!"));
    System.out.println(name.orElse("There is some value!"));

    //orElseGet与orElse类似,区别在于传入的默认值。
    //orElseGet接受lambda表达式生成默认值。
    System.out.println(empty.orElseGet(() -> "Default Value"));
    System.out.println(name.orElseGet(() -> "Default Value"));

    try {
      //orElseThrow与orElse方法类似,区别在于返回值。
      //orElseThrow抛出由传入的lambda表达式/方法生成异常。
      empty.orElseThrow(ValueAbsentException::new);
    } catch (Throwable ex) {
      System.out.println(ex.getMessage());
    }

    //map方法通过传入的lambda表达式修改Optonal实例默认值。 
    //lambda表达式返回值会包装为Optional实例。
    Optional upperName = name.map((value) -> value.toUpperCase());
    System.out.println(upperName.orElse("No value found"));

    //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
    //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
    //但是flatMap方法的lambda返回值总是Optional类型。
    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
    System.out.println(upperName.orElse("No value found"));

    //filter方法检查Optiona值是否满足给定条件。
    //如果满足返回Optional实例值,否则返回空Optional。
    Optional longName = name.filter((value) -> value.length() > 6);
    System.out.println(longName.orElse("The name is less than 6 characters"));

    //另一个示例,Optional值不满足给定条件。
    Optional anotherName = Optional.of("Sana");
    Optional shortName = anotherName.filter((value) -> value.length() > 6);
    System.out.println(shortName.orElse("The name is less than 6 characters"));

    // 如果为空返回 false;不为空返回 true
    boolean isNull = Optional.ofNullable(对象).isPresent();
  }
}

 5.参考

​[1] https://wizardforcel.gitbooks.io/java8-new-features/content/1.html

[2] https://blog.csdn.net/ss810540895/article/details/126172285

[3] https://pdai.tech/md/java/java8/java8-optional.html ​

你可能感兴趣的:(JAVA,SE,Lambda,jdk8,java)