第02篇 Lambda表达式

一、Lambda表达式引入

这里以对人进行筛选的需求一步步变化来引入lambda

1. 数据准备

定义数据结构

package com.firewolf.java8.s001.lambda.leadin;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 作者:刘兴 时间:2019-10-28
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {

    /**
     * 颜色
     */
    private String color;
    /**
     * 年龄
     */
    private Integer age;
}

准备初始数据集合

private List<Person> personList = null;

@Before
public void init() {
    personList = Arrays.asList(
            new Person("yellow", 30),
            new Person("black", 50),
            new Person("black", 35),
            new Person("white", 45)
    );
}

2. 固定条件筛选

我们有一个需求,就是需要找出所有的黑种人,方法如下:

 public static List<Person> filterBlackPerson(List<Person> personList) {
     List<Person> result = new ArrayList<>();
     for (Person person : personList) {
         if ("black".equals(person.getColor())) {
             result.add(person);
         }
     }
     return result;
 }

通过这个方法就可以找出黑种人来

3.抽取变化属性

在2中的代码能够找出黑种人,但是接下来,我们需要查找黄种人,这个时候,上面的代码就不能满足了,所以,我们可以如下写出根据颜色查找的方法

 public static List<Person> filterPersonByColor(List<Person> personList, String color) {
     List<Person> result = new ArrayList<>();
     for (Person person : personList) {
         if (color.equals(person.getColor())) {
             result.add(person);
         }
     }
     return result;
 }

4. 其他属性的处理

接下来,我们需要找出年龄大于某个年龄的人,可以提供如下方法

 public static List<Person> filterPersonsByAge(List<Person> personList, int age) {
     List<Person> result = new ArrayList<>();
     for (Person person : personList) {
         if (person.getAge() > age) {
             result.add(person);
         }
     }
     return result;
 }

5. 一个方法兼容多个属性

我们看到,上面根据颜色和年龄分别查找的时候,需要调用不同的方法,那么,能不能统一到一个方法呢?答案是可以的,如下:

public static List<Person> filterPersons(List<Person> personList, String color, Integer age, Integer filteType) {
    List<Person> result = new ArrayList<Person>();
    for (Person person : personList) {
        if ((filteType==1 && person.getColor().equals(color)) || (filteType==2 && person.getAge() > age)) {
            result.add(person);
        }
    }
    return result;
}

我们看到,可以通过一个filteType来区分是根据什么来进行查找

6. 抽象过滤接口

如果我们的人员再增加一个属性,需要怎么处理呢?很容易想到的是,我们在上面接口的基础上在添加分支。然而,如果判断条件发生变化呢?比如年龄是小于,而不是大于怎么办?
解决办法如下:

  1. 定义一个接口,用于判断是否满足条件
public interface PersonPredicate {
    boolean test(Person person);
}
  1. 接下来,我们可以根据自己的需求定义实现,如:
/**
  * 根据肤色过滤器
  */
 public static class PersonColorPredicate implements PersonPredicate {

     private String color;

     public PersonColorPredicate(String color) {
         this.color = color;
     }

     public boolean test(Person person) {
         return color.equals(person.getColor());
     }
 }

 /**
  * 根据给定的选择器选择人员信息
  */
 public static List<Person> filterPersons(List<Person> personList,
                                          PersonPredicate predicate) {
     List<Person> result = new ArrayList<>();
     for (Person person : personList) {
         if (predicate.test(person)) {
             result.add(person);
         }
     }
     return result;
 }

接下来,如果我们需要根据颜色来判断,可以如下实现:

List<Person> persons1 = PersonUtil.filterPersons(personList, new PersonColorPredicate("yellow"));
System.out.println(persons1);

根据年龄,可以如下调用:

List<Person> persons2 = PersonUtil.filterPersons(personList, new PersonAgePredicate(36));
System.out.println(persons2);

7. 匿名内部类

上面的代码结构已经比较清晰,然而,每增加一种过滤方式,就需要创建这么一个类,如果某个过滤器只是使用一次,那么还需要这么定义的话,会造成很多类的复用性不高,同时增加了类的数量,此时,我们可以通过内部类来解决。

List<Person> persons = PersonUtil.filterPersons(personList, new PersonPredicate() {
    @Override
    public boolean test(Person person) {
        return person.getAge() > 40 && "black".equals(person.getColor());
    }
});
System.out.println(persons);

8. lambda表达式

上面的匿名内部类的方式,会让我们能够很灵活的进行处理,然而,会编写一些无用代码,比如方法的声明等等,有没有更简单的办法呢?答案是lambda表达式, 写法如下:

List<Person> persons = PersonUtil.filterPersons(personList, person -> person.getAge() > 40 && "black".equals(person.getColor()));
    System.out.println(persons);

这种写法是不是非常简单,也许你现在不太看得懂,不过不要紧,我后面会一步一步展开讲解

二、什么是Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。可以写出更简洁、更 灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。
例如:
一个Runnable类型的对象创建方式的演变:
java7中使用匿名内部类:

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello,world");
    }
};

Lambda写法如下:

Runnable r2 = ()-> System.out.println("Hello,World");

可以很明显的看到,使用Lambda表达式的方式会让代码非常的简洁。

lambda特点

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

三、Lambda语法

Lambda表达式的格式为: 函数式接口 = 参数列表 -> Lambda体
参数列表:就好比反复 add(int a , int b) ,那么int a, int b 就是参数列表;
Lambda体:可以理解为函数体
函数式接口:这个在下一章详细讲解

(一) "->"操作符

  1. 在Java8中引入了一个新的操作符号“->”,该操作符被称为箭头操作符或者是Lambda操作符
  2. 左侧:Lambda表达式的参数列表
  3. 右侧:表达式所需要执行的功能,称为Lambda体。

(二)几种语法形式

  1. 语法格式一:无参数,无返回值
    public void test1() {
        Runnable r = () -> System.out.println("Hello,Lambda");
        r.run();
    }
    
  2. 语法格式二:有一个参数,没有返回值
    public void test2() {
        Consumer<String> c = (x) -> System.out.println("Hello," + x);
        c.accept("Baby");
    }
    
  3. 语法格式三:当只有一个参数的时候,可以省略掉参数的小括号
    public void test3() {
        Consumer<String> c = x -> System.out.println("Hello," + x);
        c.accept("Test3");
    }
    
  4. 语法格式四:有两个及以上个数参数,并且有返回值,需要使用{}把lambda体包起来
    public void test3() {
        Consumer<String> c = x -> System.out.println("Hello," + x);
        c.accept("Test3");
    }
    
  5. 语法格式五:有返回值,但是lambda体只有一条语句时,可以省略{}和return
    public void test5() {
        BinaryOperator<Long> op = (x, y) -> x + y;
    }
    
  6. 语法格式六:可以指明参数类型,默认情况下,可以不指定。注意的是,如果要指明,就都得指明
    public void test6() {
        BinaryOperator<Integer> op = (Integer x, Integer y) -> x + y;
    }
    

四、Lambda类型推断

  1. 在Lambda表达式中的参数类型可以由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译;
  2. 这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型
  3. Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”

五、注意事项

在Java7之前,匿名内部类如果访问了同级的变量,那么需要把这个变量使用final修饰,而在lambda中如果引用了同级别的变量,不需要用final修饰,但是不能改变其值,其实就是Java8内部自己加上的final修饰符,不用咱们自己显示添加

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章