一、函数式编程思想概述

在这里插入图片描述

  • 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
  • 面向对象思想强调“必须通过对象的形式来做事情”
  • 函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做
    而我们要学习的Lambda表达式就是函数式思想的体现

二、体验Lambda表达式 

  • 需求:启动一个线程,在控制台输出一句话:多线程程序启动了

2.1、方式1:定义一个类MyRunnable实现Runnable接口,重写run()方法 

//创建MyRunnable类的对象
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("多线程程序启动了...");
    }
}

public class LambdaDemo {
    public static void main(String[] args) {
        //实现类的方式实现需求
        MyRunnable my = new MyRunnable();
        //创建Thread类的对象,把MyRunnable的对象作为构造参数传递
        Thread t = new Thread(my);
        //启动线程
        t.start();
    }
}

2.2、方式2:匿名内部类的方式改进

public class LambdaDemo {
    public static void main(String[] args) {
        //匿名内部类的方式改进
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程程序启动了...");
            }
        }).start();
    }
}
  • 匿名内部类中重写run()方法的代码分析
    ①、方法形式参数为空,说明调用方法时不需要传递参数
    ②、方法返回值类型为void,说明方法执行没有结果返回
    ③、方法体中的内容,是我们具体要做的事情

2.3、方式3:Lambda表达式的方式改进

public class LambdaDemo {
    public static void main(String[] args) {
        //Lambda表达式改进
        new Thread(() -> {
            System.out.println("多线程程序启动了...");
        }).start();
    }
}
  • Lambda表达式的代码分析
    ①、():里面没有内容,可以看成是方法形式参数为空
    ②、->:用箭头指向后面要做的事情
    ③、{ }:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

2.4、三种方式实现需求的优缺点 

  • 综合上述三个方法可以看出,当我们使用实现类的方式实现需求的时候,我们可以看到我们需要先创建一个实体类,然后将实现类的对象传进去才可以使用。
  • 当我们使用你们匿名内部类的方法实现需求时,我们可以发现需要重写run方法(当我们调用其他方法,忘记去重写什么方法时会比较懵逼),相比较也是比较麻烦的。
  • 当我们使用Lambda表达式来实现需求时,我们可以看到我们不用关心创建了什么实体类、重写了什么方法。我们只需要关心它最终要做的事情是System.out.println("多线程程序启动了...");

三、Lambda表达式的标准格式 

组成Lambda表达式的三要素:形式参数,箭头,代码块

Lambda表达式的格式

  • ①、格式:(形式参数) -> {代码块}
  • ②、形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ③、->:由英文中画线和大于符号组成,固定写法。代表指向动作
  • ④、代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

在这里插入图片描述

四、Lambda表达式的练习 

  • Lambda表达式的使用前提
    ①、有一个接口
    ②、接口中有且仅有一个抽象方法

4.1、练习1(抽象方法无参无返回值)

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

 4.1.1、通过实现类方法

class EatableImpl implements Eatable{
    @Override
    public void eat() {
        System.out.println("一天一苹果,医生远离我");
    }
}

public class EatableDemo {
    public static void main(String[] args) {
        //在主方法中调用useEatable方法
        Eatable e = new EatableImpl();
        useEatable(e);
	}
    private static void useEatable(Eatable e){
        e.eat();
    }
}

 4.1.2、匿名内部类的方式改进

public class EatableDemo {
    public static void main(String[] args) {

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

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

 4.1.3、Lambda表达式的方式改进

public class EatableDemo {
    public static void main(String[] args) {
        //用Lambda表达式
        useEatable(() -> {
            System.out.println("一天一苹果,医生远离我");
        });
    }

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

 4.2、练习2(抽象方法带参无返回值)

  • 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
public interface Flyable{
    void fly(String s);
}
  • 定义一个测试类(FlyableDemo),在测试类中提供两个方法
    ①、一个方法是:useFlyable(Flyable f)
    ②、一个方法是主方法,在主方法中调用useFlyable方法

4.2.1、匿名内部类的方式 

public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中调用useFlyable方法
        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("飞机自驾游");
            }
        });
    }

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

4.2.2、Lambda表达式的方式改进

public class FlyableDemo {
    public static void main(String[] args) {
        //Lambda
        useFlyable((String s)-> {
            System.out.println(s);
            System.out.println("飞机自驾游");
        });
    }

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

4.3、练习3(抽象方法带参有返回值) 

  • 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
public interface Addable {
    int add(int x,int y);
}
  • 定义一个测试类(AddableDemo),在测试类中提供两个方法
    ①、一个方法是:useAddable(Addable a)
    ②、一个方法是主方法,在主方法中调用useAddable方法
public class AddableDemo {
    public static void main(String[] args) {
        //在主方法中调用useAddle方法
        //Lambda
        useAddable((int a, int b) -> {
            return a+b;
        });
    }

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

4.4、实现Comparator接口 

4.4.1、匿名内部类的方式实现

List<Map<Integer, QueryUrgentQueryPullTotalRate>> result = new ArrayList<>();


//添加数据,其中Integer是月份


// 根据月份进行排序
        Collections.sort(result, new Comparator<Map<Integer,
 QueryUrgentQueryPullTotalRate>>() {
            @Override
            public int compare(Map<Integer, QueryUrgentQueryPullTotalRate> o1, 
Map<Integer, QueryUrgentQueryPullTotalRate> o2) {
                Integer key1 = o1.keySet().iterator().next();
                Integer key2 = o2.keySet().iterator().next();
                return key1.compareTo(key2);
            }
        });

 4.4.2、lambda表达式的方式

List<Map<Integer, QueryUrgentQueryPullTotalRate>> result = new ArrayList<>();

//添加数据,其中Integer是月份


Collections.sort(result,
                (Map<Integer, QueryUrgentQueryPullTotalRate> o1,
                Map<Integer, QueryUrgentQueryPullTotalRate> o2) -> {
                    Integer key1 = o1.keySet().iterator().next();
                    Integer key2 = o1.keySet().iterator().next();
                    return key1.compareTo(key2);
                });

五、Lambda表达式的省略模式

  • 省略规则:
    ①、参数类型可以省略。但是有多个参数的情况下,不能只省略一个
    ②、如果参数有且仅有一个,那么小括号可以省略
    ③、如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
  • 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
public interface Flyable{
    void fly(String s);
}
  • 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
public interface Addable {
    int add(int x,int y);
}
/*Lambda表达式的省略模式*/
public class LambdaDemo {
    public static void main(String[] args) {
        //useAddable((int x,int y) -> {
        //    return x+y;
        //});
        //1.参数的类型可以省略,但是有多个参数的情况下,不能只省略一个
        useAddable((x,y) -> {
            return x+y;
        });

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

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

        //4.如果代码块的语句只有一条,可以省略大括号和分号;如果有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对应的接口:Runnable r = () -> System.out.println(“Lambda表达式”);
  • ④、根据调用方法的参数得知Lambda对应的接口:new Thread(() -> System.out.println(“Lambda表达式”)).start();
/*Lambda表达式的注意事项*/
public class LambdaDemo {
    public static void main(String[] args) {
        //使用Lambda表达式必须要有接口,并且要求接口中有且仅有一个抽相方法
        useInter(()->System.out.println("好好学习,天天向上"));

        //必须有上下文环境,才能推导出Lambda对应的接口
        Runnable r = () -> System.out.println("Lambda表达式");
        new Thread(r).start();

        new Thread(
                () -> System.out.println("Lambda表达式")
        ).start();
    }

    private static void useInter(Inter i){
        i.show();
    }
}

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

  • 所需类型不同
    ①、匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    ②、Lambda表达式:只能是接口
abstract class Animal {
    public abstract void method();
}
class Student {
    public void study(){
        System.out.println("爱生活,爱java");
    }
}

public class LambdaDemo{
    public static void main(String[] args) {
        //匿名内部类
        useInter(new Inter() {
            @Override
            public void show() {
                System.out.println("接口");
            }
        });

        useAnimal(new Animal() {
            @Override
            public void method() {
                System.out.println("抽象类");
            }
        });

        useStudent(new Student(){
            @Override
            public void study(){
                System.out.println("具体类");
            }
        });

        //Lambda
        useInter(() -> System.out.println("接口"));
        //useAnimal(() -> System.out.println("抽象类"));//不可以
        //useStudent(() -> System.out.println("具体类"));//不可以
    }
    private static void useStudent(Student s){
        s.study();
    }
    private static void useAnimal(Animal a){
        a.method();
    }
    private static void useInter(Inter i){
        i.show();
    }
}
  • 使用限制不同
    ①、如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    ②、如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
public interface Inter{
    void show();
    void show1();
}
public class LambdaDemo{
    public static void main(String[] args) {
        useInter(new Inter() {
            @Override
            public void show() {
                System.out.println("show");//👈只打印这一个show
            }
            @Override
            public void show1() {
                System.out.println("show");
            }
        });
    }

    private static void useInter(Inter i){
        i.show();
    }
}
  • 实现原理不同

        ①、匿名内部类:编译之后,产生一个单独的.class字节码文件

        ②、Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

八、Lambda表达式的使用场景

以下是我总结的使用场景

  • 集合操作
  • 多线程编程
  • 事件处理
  • 排序
  • 过滤
  • 映射
  • 聚合
  • 函数式编程
  • 数据库操作
  • 并行计算
  • GUI编程
  • 测试 

8.1、集合操作

Lambda表达式可以与Java 8的新集合操作方法(如stream()和forEach())一起使用,使集合的处理更加简单、灵活和易于读写。

例如,假设有一个字符串列表,想要对该列表中的所有元素进行大写转换并输出到控制台上,可以使用以下代码:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().map(String::toUpperCase).forEach(System.out::println);

这里,使用了stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用forEach()方法将结果输出到控制台。

8.2、多线程编程 

Lambda表达式可以与Java中的函数式接口一起使用,使多线程编程更加简单和可读。

例如,有一个线程,需要在后台执行某些任务,并在任务完成时通知主线程。可以使用以下代码创建一个新的线程并将任务作为Lambda表达式传递给它:

new Thread(() -> {
    // 执行后台任务
    // ...
    // 通知主线程任务已完成
}).start();

这里,使用了Java中的Thread类,并将一个Lambda表达式作为参数传递给它,该表达式将在新线程中执行后台任务。

8.3、事件处理 

Lambda表达式可以作为事件监听器传递给GUI组件等,使事件处理更加简单和可读。

例如,假设我们有一个按钮,需要在用户单击它时执行某些操作。可以使用以下代码将Lambda表达式作为事件监听器传递给该按钮:

button.addActionListener(event -> {
    // 处理按钮单击事件
    // ...
});

这里,使用了Java中的ActionListener接口,并将一个Lambda表达式作为参数传递给它,该表达式将在用户单击按钮时执行。

8.4、排序 

Lambda表达式可以用于Java中的排序算法中,使排序更加灵活和可读。

例如,假设我们有一个Person对象的列表,需要按照年龄进行排序。可以使用以下代码将Lambda表达式作为排序算法的参数传递给Collections.sort()方法:

List<Person> people = Arrays.asList(new Person("Alice", 25), 
new Person("Bob", 30), new Person("Charlie", 20));
Collections.sort(people, (p1, p2) -> p1.getAge() - p2.getAge());

这里,我们使用了Java中的Collections类的sort()方法,并将一个Lambda表达式作为参数传递给它,该表达式将比较两个Person对象的年龄并返回一个整数值,以指示它们的排序顺序。

8.5、过滤 

Lambda表达式可以用于过滤集合中的元素,使代码更加简单和可读。

例如,假设有一个整数列表,需要过滤掉其中的偶数。可以使用以下代码将Lambda表达式作为过滤器传递给Java中的stream()方法:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> oddNumbers = numbers.stream().filter(n -> n % 2 != 0).
collect(Collectors.toList());

这里,使用了Java中的stream()方法将列表转换为一个流,然后使用filter()方法过滤掉其中的偶数,最后使用collect()方法将过滤后的结果转换为一个新的列表。

8.6、映射

Lambda表达式可以用于将一个集合中的元素映射到另一个集合中,使代码更加简单和可读。

例如,假设我们有一个字符串列表,需要将其中的每个字符串转换为大写形式并存储到另一个列表中。可以使用以下代码将Lambda表达式作为映射器传递给Java中的stream()方法:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperCaseNames = names.stream().map(String::toUpperCase)
.collect(Collectors.toList());

这里,我们使用了Java中的stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用collect()方法将转换后的结果存储到一个新的列表中。

8.7、聚合 

Lambda表达式可以用于聚合集合中的元素,例如,计算集合中的元素之和、平均值、最大值、最小值等。

以下是一个计算列表中元素之和的示例: 

​List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println("Sum of numbers: " + sum);

​

这里,使用了Java中的stream()方法将列表转换为一个流,并使用reduce()方法计算流中元素的总和。reduce()方法接受两个参数:起始值和一个BinaryOperator类型的Lambda表达式。Lambda表达式将两个元素相加并返回它们的和。在这个例子中,将起始值设置为0,表示计算从0开始的累加和。

除了计算元素之和之外,还可以使用Lambda表达式计算元素的平均值、最大值、最小值等。以下是一个计算列表中元素平均值的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
double average = numbers.stream().mapToInt(Integer::intValue).average().orElse(0.0);
System.out.println("Average of numbers: " + average);

首先使用mapToInt()方法将流中的元素转换为int类型,然后使用average()方法计算这些元素的平均值。如果列表为空,则orElse()方法返回默认值0.0。

8.8、函数式编程

Lambda表达式可以使Java更加接近函数式编程,使代码更加简洁和易于理解。

例如,假设有一个接口,其中包含一个抽象方法,需要在程序中实现该接口并调用该方法。可以使用以下代码将Lambda表达式作为接口实现传递给该方法: 

interface MyInterface {
    int doSomething(int x, int y);
}
 
MyInterface myLambda = (x, y) -> x + y;
int result = myLambda.doSomething(3, 4);

这里,定义了一个名为myLambda的变量,它的类型是MyInterface,它接受两个整数参数并返回它们的和。然后,我们调用myLambda的doSomething()方法,并传递两个整数参数,得到它们的和并将结果存储在result变量中。

8.9、数据库操作

Lambda表达式可以与Java中的数据库操作API(如JDBC和Hibernate)一起使用,使数据库操作更加简单和可读。

例如,假设有一个数据库表,需要查询其中的数据并输出到控制台上。可以使用以下代码将Lambda表达式作为查询操作的参数传递给数据库API: 

try (Connection conn = DriverManager.getConnection(url, username, password);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("SELECT * FROM my_table")) {
    while (rs.next()) {
        int id = rs.getInt("id");
        String name = rs.getString("name");
        System.out.println("id: " + id + ", name: " + name);
    }
}

这里,使用了Java中的JDBC API,并将一个Lambda表达式作为查询操作的参数传递给executeQuery()方法,该表达式将处理查询结果集并输出到控制台上。

8.10、并行计算 

Lambda表达式可以与Java中的并行计算API(如Java 8中的Parallel Streams和Fork/Join框架)一起使用,使计算更加高效和快速。

例如,假设有一个大型的整数列表,需要计算其中所有元素的平方和。可以使用以下代码将Lambda表达式作为计算器传递给Java 8中的Parallel Streams API:

List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
    numbers.add(i);
}
long sum = numbers.parallelStream().mapToLong(i -> i * i).sum();
System.out.println("Sum of squares: " + sum);

这里,使用了Java 8中的Parallel Streams API,它将列表转换为一个并行流,并使用mapToLong()方法计算每个元素的平方值,最后使用sum()方法将它们加起来得到总和。在此过程中,计算将在多个线程上并行执行,从而提高了计算效率。

8.11、GUI编程

Lambda表达式可以与Java中的GUI编程框架(如JavaFX和Swing)一起使用,使GUI编程更加简单和可读。 

例如,假设有一个JavaFX应用程序,需要在用户单击按钮时执行一些操作。可以使用以下代码将Lambda表达式作为事件处理器传递给JavaFX框架:

Button button = new Button("Click me!");
button.setOnAction(event -> System.out.println("Button clicked!"));

这里,使用了JavaFX框架,并将一个Lambda表达式作为按钮的事件处理器传递给setOnAction()方法。该表达式将在用户单击按钮时执行,并输出一条消息到控制台上。

8.12、测试 

Lambda表达式可以用于编写更加简洁和可读的单元测试代码,使测试更加容易理解和维护。

例如,假设有一个函数,需要进行单元测试以确保其正确性。可以使用以下代码将Lambda表达式作为测试断言传递给JUnit测试框架:

@Test
public void testMyFunction() {
    int result = myFunction(2, 3);
    assertEquals(6, result, () -> "myFunction(2, 3) should return 6");
}

这里,使用了JUnit测试框架,并将一个Lambda表达式作为测试断言传递给assertEquals()方法。该表达式将在测试失败时计算并返回错误消息,以帮助定位和修复问题。

九、总结

  • Lambda表达式是Java 8中最强大和灵活的新特性之一,它可以用于各种不同的编程任务,使代码更加简单、灵活和易于读写。Lambda表达式的语法非常简洁,通常由一个参数列表、一个箭头符号和一个表达式主体组成。
  • Lambda表达式可以用于各种不同的编程任务,包括函数式编程、集合处理、数据库操作、Web开发、并行计算、GUI编程、测试等。使用Lambda表达式可以使代码更加简单、灵活和易于读写,并帮助开发人员减少代码的冗余和重复。
  • 除了Lambda表达式之外,Java 8还引入了其他重要的新特性,例如Stream API、接口默认方法、方法引用、Optional类型等。这些新特性一起使Java变得更加现代化、强大和易于使用。

总之,Lambda表达式是Java编程中不可或缺的一部分,它使Java变得更加现代化、灵活和强大,并且是Java 8中最重要的新特性之一。

参考文章:让你秒懂的Lambda表达式超级详细讲解-CSDN博客 

https://murphy.blog.csdn.net/article/details/131350405?ydreferer=aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjc1NTUzNS9jYXRlZ29yeV8xMDYxMTg5My5odG1s?ydreferer=aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjc1NTUzNS9jYXRlZ29yeV8xMDYxMTg5My5odG1s

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐