【Java】函数式编程

本文介绍了Java中的函数式编程概念,包括Lambda表达式的简化代码能力,Stream流的流式操作和并行处理,以及Optional类在处理可能为空的对象时的优势。同时,文章探讨了函数式接口及其在Java中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文的思想来自于 此课程教学视频, 是课程学习的笔记整理,希望进行更详细学习的小伙伴请移步原课程学习 ~~
我愿意称函数式编程为 IDEA 的 Ctrl + Enter 大法 ~~~

在这里插入图片描述

一、什么是函数式编程 + 为什么要学习函数式编程

函数式编程是一种编程范式,其将关注点从对象转变为函数,并且具有简洁开发快速、接近于自然语言、易于并发编程等优点。

二、 函数式编程基础 —— Lambda 表达式

1. 简单理解

其是一种匿名内部类的优化写法,将匿名内部类(只含有一个方法的匿名内部接口)只留下 函数参数列表 和 方法的实现 。

(参数列表) -> {
	// 方法实现
}

其将关注点转移到 参数列表方法实现 , 省略掉无意义的创建代码 ,请看下面的代码示例 :

可以先写出匿名内部类的形式,再转换为 Lambda 表达式 , 通过 Alt + Enter 快捷键,, 此快捷键也可以把我们的 Lambda 表达式化为最简格式

在这里插入图片描述

2. 代码示例

public class LambdaTest {

    public static void main(String[] args) {
        // 可以先写出 匿名内部类的 形式, 再转化为Lambda表达式
        foreachArr((value) -> {System.out.println(value)});
    }

    public static void foreachArr(IntConsumer consumer){
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
        for (int i : arr){
            consumer.accept(i);
        }
    }
}

3. 函数式编程如何实现简化代码

请看如下的例子 ——

我们需要输入给定数组中的奇数和偶数,如果没有 Lambda 表达式,我们是不是需要写两个函数,一个判断奇数的方法 , 一个判断偶数的方法 。

但是,有了函数式编程,我们可以只写一个方法,通过在调用方法时为接口的方法传入不同的实现,来实现输出奇数和偶数 。

public class LambdaTest {

    public static void main(String[] args) {
        // 输出偶数
        foreachArr(value -> {
            if(value % 2 == 0){
                System.out.println(value);
            }
        });
        // 输出奇数
        foreachArr(value -> {
            if(value % 2 == 1){
                System.out.println(value);
            }
        });
    }

    public static void foreachArr(IntConsumer consumer){
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
        for (int i : arr){
            consumer.accept(i);
        }
    }
}

三、 Stream 流

1. 什么是 Stream 流

Stream 流是函数式编程的一种模式,其用于对集合和数组进行流式操作 。

示例 : 打印所有 年龄小于 18 的作家的集合

/**
 * @Author: WanqingLiu
 * @Date: 2023/03/04/22:32
 */
public class Test {

    public static void main(String[] args) {

        List<Author> authors =  gerAuthors();
        // 打印所有年龄小于 18 的作家的集合
        authors.stream() // 把集合转换为 流
                .distinct() // 去重操作
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() < 18;
                    }
                }) // 过滤操作
                .forEach(new Consumer<Author>() {
                    @Override
                    public void accept(Author author) {
                        System.out.println(author.getName());
                    }
                });
    }

    public static List<Author> gerAuthors(){
        Author author1 = new Author(1L, "晚晴1", 21, null);
        Author author2 = new Author(2L, "晚晴2", 15, null);
        Author author3 = new Author(3L, "晚晴3", 13, null);
        Book book1 = new Book(1L, "Java", "技术上", 30);
        Book book2 = new Book(2L, "C++", "技术上", 40);
        Book book3 = new Book(3L, "Python", "技术上", 50);
        // Book book4 = new Book(4L, "JavaScript", "技术上", 60);
        ArrayList<Book> books1 = new ArrayList<>();
        books1.add(book1);
        ArrayList<Book> books2 = new ArrayList<>();
        books2.add(book1);
        books2.add(book2);
        ArrayList<Book> books3 = new ArrayList<>();
        books3.add(book1);
        books3.add(book2);
        books3.add(book3);
        author1.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        ArrayList<Author> authors = new ArrayList<>();
        authors.add(author1);
        authors.add(author2);
        authors.add(author3);
        return authors;
    }
}

2. 使用

请见本文章 :Stream 流 问题

3. 高级优化

3.1 通过转换为基本数据类型避免自动装箱和拆箱

大量的自动装箱拆箱操作会降低程序执行的效率,使用我们需要使用 Stream 流为我们提供的转换为基本数据类型流的方法转换为基本数据类型流,再对基本数据类型进行操作。

        authors.stream()
                .map(author -> author.getAge())
                .map(age -> age + 10) // 发生拆箱操作
                .filter(age -> age > 18)
                .map(age -> age + 2)
                .forEach(System.out::println);

        // 优化上面的代码
        authors.stream()
                .mapToInt(value -> value.getAge()) // 直接封装为 int
                .map(age -> age + 10)
                .filter(age -> age > 18)
                .map(age -> age + 2)
                .forEach(System.out::println);

2. 并行流

要要处理的数据分给多个线程去处理 —— parallel 将串行流转化为并行流(也可以直接通过 parallelStream() 直接得到并行流)

    public static void testParallel(){
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Integer sum = stream.parallel()
                .filter(integer -> integer > 3)
                .reduce((result, curEle) -> result + curEle)
                .get();
    }

通过 peek 进行调试 ——

 public static void testParallel(){
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5,6,7,8,9,10);
        Integer sum = stream.parallel()
                .peek(integer -> System.out.println("线程名称:" + Thread.currentThread().getName() + " 当前线程操作的元素:" + integer)) // 进行调试
                .filter(integer -> integer > 3)
                .reduce((result, curEle) -> result + curEle)
                .get();

        System.out.println(sum);
    }

在这里插入图片描述

四、Optional

1. 概述

以对象判断是否为 null 为例子 , Optional 可以帮助我们进行处理, 写出更优雅的代码避免空指针异常 。

Optional 就好像一个包装类, 可以把我们具体的数据封装到 Optional 对象内部,我们使用 Optional 中封装好的方法操作封装进去的数据 。

2. 使用

2.1 创建 Optional 对象

        Author author = new Author();
        // 调用 ofNullable 方法,把对象封装为非空对象
        Optional<Author> authorOption = Optional.ofNullable(author);
        authorOption.ifPresent(author1 -> System.out.println(author1.getName()));
    public static void main(String[] args) {
        Optional<Author> authorOptional = getAuthorOptional();
        authorOptional.ifPresent(author -> System.out.println(author.getName()));
    }

    public static Optional<Author> getAuthorOptional(){
        Author author = new Author(1L, "晚晴1", 21, null);
        return Optional.ofNullable(author);
    }

将 MyBatis 中的数据返回值设置为 Optional , MyBatis 会自动帮我们封装为 Optional 类型

ofNullable 原理 : 判断为 null 时, 使用 empty() 方法创建对象

2.2 安全消费值

如何对 Optional 对象进行消费操作

  authorOption.ifPresent(author1 -> System.out.println(author1.getName()));

2.3 获取 Optional 对象中的值

- get —— 推荐不用, null 值会报异常
- orElseGet
    // Optional 中为空时,返回我们设置的默认值
    authorOptional.orElseGet(() -> new Author());
- orElseThrow
authorOptional1.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("内部对象为 null"))

2.4 数据过滤

filter

    private static void testFilter(){
        Optional<Author> authorOptional = getAuthorOptional();
        // 过滤出大于 18 的类, 年龄小于 18 的类继续被封装为 null
        Optional<Author> authorOptional1 = authorOptional.filter(author -> author.getAge() > 18);
    }

2.5 数据判断

isPresent
    private static void testIsPresent(){
        Optional<Author> authorOptional = getAuthorOptional();
        if(authorOptional.isPresent()){
            // 进行消费操作
        }
    };

2.6 数据转换

map
    private static void testMap(){
        Optional<Author> authorOptional = getAuthorOptional();
        authorOptional.map(author -> author.getBooks())
                .ifPresent(books -> System.out.println(books));
    }

五、函数式接口

1. 什么是函数式接口

可以用于进行函数式编程被lambda表达式化简的接口就是函数式接口,其具有如下特点:

  1. @FunctionalInterface 注解修饰 —— 没有此注释,但是只含一个抽象方法的接口也是函数式接口,其类似于一个提升作用,提升此接口必须有且只有一个抽象方法。
  2. 只含有一个 抽象 方法
@FunctionalInterface
public interface InterfaceA {
    // 只含有一个抽象方法
    void function();
}

2. 常见的函数式接口

我们通过接口的 参数类型 和 返回值 决定接口的作用

2.1 Consumer 消费型接口

需要一个参数,返回值为空,只能消费这个参数

2.2 Functional 计算转换型接口

一个参数,一个返回值,参数和返回值泛型不一样

2.3 Predicate 判断型接口

传入一个参数,返回的类型为 boolean 型

2.4 Supplier 生产型接口

没有参数,只有一个返回值

2.5 其他函数式接口

定位到 function 包下,去找自己需要用的

在这里插入图片描述

3. 函数式接口中的常见方法

作用于函数式接口 —— 初学者不建议看,常常用于自定义方法

3.1 and

用于拼接两个判断条件 ,使用场景为自定义方法

示例:

	// 输出大小为偶数并且名字长度大于1的作家
    public static void printNum(IntPredicate predicate1, IntPredicate predicate2 ){
        int[] arr = {1, 2, 3, 4, 5};
        for (int i : arr){
        	// 用 and 操作连接两个 predicate
            if(predicate1.and(predicate2).test(i)){
                System.out.println(i);
            }
        }
    }
    public static void main(String[] args) throws Throwable {
      		 printNum(value -> value % 2  == 0, value -> value > 3);
	}

3.2 or

    // 输出大小为偶数,或者名字长度大于1的作家
    public static void testOr(IntPredicate predicate1, IntPredicate predicate2 ){
        int[] arr = {1, 2, 3, 4, 5};
        for (int i : arr){
            if(predicate1.or(predicate2).test(i)){
                System.out.println(i);
            }
        }
    }

3.3 negate

    // 打印作家年龄小于等于 17 的
    public static void testNegate(){
        List<Author> authors = gerAuthors();
        authors.stream()
                .filter(((Predicate<Author>) author -> author.getAge() > 17).negate()).forEach(author -> System.out.println(author.getName()));
    }

六、 方法引用

Java 中的语法糖, 是对 Lambda 表达式的另一种简化 , 其规则比较繁琐,不需要纠结于记忆 。

可以转换为方法引用格式的 Lambda 具有如下特点:

1. 基本格式

类名或者对象名 :: 方法名

2. 推荐用法 *

编译器的 Alt + Enter 快捷键看看能不能转换为方法引用,能转就转

3. 方法应用的语法

3.1 引用类的静态方法

类名::静态方法名
  1. 方法体中只有一行代码,并且是调用了某个类的静态方法
  2. 重写的抽象方法的所有参数按照顺序传入此静态方法

3.2 引用对象的实例方法

对象名::方法名
  1. 方法体只有一行代码,并且其调用某个对象的成员方法
  2. 重写的抽象方法的所有参数按照顺序传入此成员方法

3.3 引用类的实例方法

类名::方法名
  1. 方法体只有一行代码,并且其调用第一个参数的成员方法
  2. 剩余的所有参数按照顺序传入此成员方法中

3.4 构造器引用

类名 :: new 
  1. 只有一行代码,这行代码调用的是构造方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值