Lambda 表达式


Java8 新特性中有几个比较主要的特性,Lambda 表达式、函数式接口、方法引用和几个新增库 Stream API、Optional 类等。

Lambda 表达式简介

什么是 Lambda ?

  • Lambda 表达式其实就是一种匿名函数,在这之前大家可能还接触过匿名内部类,虽然也是一种匿名函数,但是它并没有 Lambda 表达式那么简洁。

  • Lambda 表达式的特点就是可以传递,在使用 Lambda 表达式的时候我们可以将代码像数据一样传递。

  • 使用 Lambda 表达式能够极大的减少我们代码的冗余,而且使用也相当的方便,熟练之后会大大加快我们写代码的速度。

函数式编程思想概述:

  • 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。

  • 面向对象思想强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”。

  • Lambda 表达式就是函数式思想的体现。


Lambda 表达式入门案例

下面用 Java8 的方法与老版本进行使用对比:

  • 尝试找出公司员工年龄大于 35 岁的所有员工,并用不同的方法来实现一下。
  • 假设有一个 Staff 类来代表员工,并使用构造函数来创建员工的集合。

Staff 类:

public class Staff {
    int age;
    String name;

    public String getName() {
        return name;
    }

    public Staff(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Staff(int age) {
        this.age = age;
    }
}

实现方式一:常规方法

public List<Staff> getStaffs(List<Staff> staffs) {

    List<Staff> stas = new ArrayList<>();

    for(Staff sta: staffs){
        if (sta.getAge()>=35){
            stas.add(sta);
        }
    }
    return stas;
}

@Test
public void test1(){

    List<Staff> list = getStaffs(staffs);

    for (Staff staf: list) {
        System.out.println(staf);
    }
}

实现方式二:策略设计模式

我们需要写一个接口,然后实行这个接口,最后是调用接口。这种方法看起来就比前面的那种更有层次,而且假如又需要查找员工工资大于 5000 的员工的话,就重新写一个类来实现接口就好了

import java.util.ArrayList;
import java.util.List;


// 接口
interface MyPredicate<Staff> {
    boolean test(Staff staff);
}


// 实现接口
class filterStaffByAge implements MyPredicate<Staff> {
    @Override
    public boolean test(Staff staff) {
        return staff.getAge() >= 35;
    }
}


// 测试类
public class Test {

    // 过滤
    public List<Staff> filterStaff(List<Staff> list, MyPredicate<Staff> mp){
        List<Staff> staffs = new ArrayList<>();

        for (Staff staff: list) {
            if (mp.test(staff)){
                staffs.add(staff);
            }
        }
        return staffs;
    }

    // 测试结果
    public static void main(String[] args) {
        ArrayList<Staff> staffs = new ArrayList<>();
        staffs.add(new Staff(24, "xiaohua"));
        staffs.add(new Staff(36, "xiaoming"));
        staffs.add(new Staff(21, "xiaoguang"));
        staffs.add(new Staff(37, "xiaoliuhua"));

        List<Staff> list = new Demo().filterStaff(staffs, new filterStaffByAge());
        for (Staff staff: list) {
            System.out.println(staff);
            // Staff{age=36, name='xiaoming'}
            // Staff{age=37, name='xiaoliuhua'}
        }
    }
}

实现方式三:匿名内部类

package com.annotation;

import java.util.ArrayList;
import java.util.List;


// 接口
interface MyPredicate<Staff> {
    boolean test(Staff staff);
}


// 测试类
public class Demo {

    // 过滤
    public List<Staff> filterStaff(List<Staff> list, MyPredicate<Staff> mp){
        List<Staff> staffs = new ArrayList<>();

        for (Staff staff: list) {
            if (mp.test(staff)){
                staffs.add(staff);
            }
        }
        return staffs;
    }

    public static void main(String[] args) {
        ArrayList<Staff> staffs = new ArrayList<>();
        staffs.add(new Staff(24, "xiaohua"));
        staffs.add(new Staff(36, "xiaoming"));
        staffs.add(new Staff(21, "xiaoguang"));
        staffs.add(new Staff(37, "xiaoliuhua"));
        // 使用匿名内部类
        List<Staff> list = new Demo().filterStaff(staffs, new MyPredicate<Staff>(){
            @Override
            public boolean test(Staff staff) {
                return staff.getAge() >= 35;
            }
        });
        for (Staff staff: list) {
            System.out.println(staff);
        }
    }
}

实现方式四:Lambda 表达式

Lambda 表达式免去了上述 Java8 之前要么重新写方法,要么重新实现接口的繁琐。
Lambda 表达式是对匿名内部类的使用优化。

        // Lambda表达式
        List<Staff> list = new Demo().filterStaff(staffs, (e)->e.getAge()>=35);  // 代替接口实现
        // Java8的新特性:使用“::”方法引用
        list.forEach(System.out::println);

实现方式五:Lambda 表达式 + Stream API

前面的示例都依赖了 filterStaff 方法,那如果现在只有员工数据,没有 filterStaff 函数,那么 Java8 可不可以实现找出年龄大于 35 的员工呢?答案是可以的,那就要用到 Java8 新特性中的 Stream API 。

    staffs.stream()
        .filter((e)->e.getAge()>=35)
        .forEach(System.out::println);

Lambda 表达式语法

Lambda 表达式的标准格式:

格式:(形式参数) -> {代码块}

  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可。
  • ->:由英文中画线和大于符号组成,固定写法,代表指向动作。
  • 代码块:是我们具体要做的事情。

Lambda 表达式的使用前提:

  • 有一个接口。
  • 接口中有且仅有一个抽象方法。

Lambda 语法格式:

//语法格式一:无参数,无返回值。
() -> System.out.println("Hello");

// 语法格式二:有一个参数,并且无返回值。
(e) -> System.out.println(e);

// 语法格式三:若只有一个参数,参数的小括号可以省略。
e -> System.out.println(e);

// 语法格式四:有两个以上的参数,又返回值,有多条执行语句。
(x, y) -> {
    System.out.println("Lambda");
    return Integer.compare(x, y);
};

// 语法格式五:如果有两个以上的参数,又返回值,只有一条执行语句,Lambda 体的大括号和return可以省略。
Comparator<Integer>con=(x, y) -> Integer.compare(x, y);

// 语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文推断出数据类型,即“类型推断”。
(Integer x, Integery) -> Integer.compare(x, y);

示例 1:无参无返回值的抽象方法

  • 定义一个接口 (Eatable),里面定义一个抽象方法:void eat();
  • 定义一个测试类 (EatableDemo),在测试类中提供两个方法
    • 一个方法是:useEatable(Eatable e)
    • 一个方法是主方法,在主方法中调用 useEatable 方法
// 接口
public interface Eatable {
    void eat();
}

// 实现类
public class EatableImpl implements Eatable {
    @Override
    public void eat() {
        System.out.println("一天一苹果,医生远离我");
    }
}
//测试类
public class EatableDemo {
    // 在主方法中调用 useEatable 方法
    public static void main(String[] args) {

        // 实现类的方式
        Eatable e = new EatableImpl();
        useEatable(e);

        // 匿名内部类的方式
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("一天一苹果,医生远离我");
            }
        });

        // Lambda 表达式的方式
        useEatable(() -> {
            System.out.println("一天一苹果,医生远离我");
        });
    }

    private static void useEatable(Eatable e) {
        e.eat();
    }
}

示例 2:有参无返回值的抽象方法

interface Flyable {
    void fly(String s);
}

public class Test {

    public static void main(String[] args) {

        // 匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("飞机自驾游");
            }
        });
        System.out.println("--------");

        // Lambda
        useFlyable((String s) -> {
            System.out.println(s);
            System.out.println("飞机自驾游");
        });

    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }
}

示例 3:有参有返回值的抽象方法

public interface Addable {
    int add(int x, int y);
}

public class AddableDemo {

    public static void main(String[] args) {
        // lambda 实现 Addable
        useAddable((int x, int y) -> {
            return x + y;
        });
    }

    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

Lambda 表达式的省略模式

省略的规则:

  • 参数类型可以省略。
  • 如果参数有且仅有一个,那么小括号可以省略。
  • 如果代码块的语句只有一条,可以省略大括号和分号,和 return 关键字。
public interface Addable {
    int add(int x, int y);
}

public interface Flyable {
    void fly(String s);
}

public class LambdaDemo {

    public static void main(String[] args) {
//        useAddable((int x, int y) -> {
//            return x + y;
//        });
        // 参数的类型可以省略
        useAddable((x, y) -> {
            return x + y;
        });

//        useFlyable((String s) -> {
//            System.out.println(s);
//        });
//        // 如果参数有且仅有一个,那么小括号可以省略
//        useFlyable(s -> {
//            System.out.println(s);
//        });

        // 如果代码块的语句只有一条,可以省略大括号和分号
        useFlyable(s -> System.out.println(s));

        // 如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
        useAddable((x, y) -> x + y);
    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }

    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

Lambda 表达式和匿名内部类的区别

所需类型不同:

  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类。
  • Lambda 表达式:只能是接口。

使用限制不同:

  • 如果接口中有且仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类。
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式。

实现原理不同:

  • 匿名内部类:编译之后,产生一个单独的 .class 字节码文件。
  • Lambda 表达式:编译之后,没有一个单独的 .class 字节码文件,对应的字节码是在运行的时候动态生成。

函数式接口

什么是函数式接口?

  • Lambda 表达式需要“函数式接口”的支持。

  • 函数式接口:接口中只有一个抽象方法,称为函数式接口。可以使用注解 @FunctionalInterface 修饰(检测是否为函数式接口)。

Java8 四大内置核心函数式接口

Java8 给我们准备了现成的函数式接口,除了很特殊的场景需要自己写函数式接口外,大部分时候我们都可以使用内置的函数式接口,下面来看看四大内置接口及其使用方法:

Consumer<T>: 消费型接口
   void accep(T t);

Supplier<T>: 供给型接口
   T get();

Function<T, R>: 函数型接口
   R apply(T t);

Predicate<T>: 断言型接口
   boolean Test(T t);

使用示例:

// 取10个随机数
List<Integer> numlist = getNumList(10, ()->(int)Math.random());
    for (Integer num: numlist) {
        System.out.println(num);
    }
}

// 取随机数
public List<Integer> getNumList(int num, Supplier<Integer> sup){
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < num; i++) {
        Integer n=sup.get();
        list.add(n);
    }
    return list;
}


//Function<T, R>:函数型接口
//用于处理字符串
public String StrHandler(String str, Function<String, String> fun){
    return fun.apply(str);
}

// 测试函数型接口
@Test
public void test3(){

    String str=StrHandler("\t\t\t 就是对快睡觉",(x)->x.trim());
    System.out.println(str);

    String uper=StrHandler("\t\t\t 就是对快睡觉",(x)->x.toUpperCase());
    System.out.println(uper);

    String newStr=StrHandler("\t\t\t  就是对快睡觉",(x)->x.substring(2,5));
    System.out.println(newStr);

}



//Predicate<T>:断言型接口
//将满足条件的字符串放入集合中
public List<String> StrFilter(List<String> list, Predicate<String> pre){
    List<String> strList = new ArrayList<>();

    for (String str: list) {
        if (pre.test(str)){
            strList.add(str);
        }
    }
    return strList;
}

// 测试断言型接口
@Test
public void test4(){

    List<String> list = Arrays.asList("aaa", "bbb", "ccc", "bbbb", "jahdgja");
    List<String> strList = StrFilter(list, s->s.length()>3);
    
    for (String str: strList) {
        System.out.println(str);
    }

}

方法引用

方法引用:指若 Lambda 体中的内容已经有方法实现了,那么我们就可以使用“方法引用”。

主要有三种语法格式:

  1. 对象::实例方法名

  2. 类::静态方法名

  3. 类::实例方法名

注意:
Lambda 体中调用方法的参数列表和返回值类型,要与函数式接口中抽象方法的参数列表和返回值一致。
如若 Lambda 参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 ClassName::method

构造器引用

  • 格式:ClassName::new

  • 注意:需要调用的构造器的参数列表,要与函数式接口中抽象方法的参数列表保持一致。

数组引用:

  • 格式:Type::new

posted @ 2023-04-03 16:22  Juno3550  阅读(47)  评论(0编辑  收藏  举报