函数式编程

一、准备工作

(一)实体类

1、Author类
java 复制代码
package com.xiaobai.stream_practice.entity;

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

import java.util.List;
import java.util.Objects;

/**
 * @author wangtw
 * @ClassName Author
 * @description: 作家实体类
 * @date 2024/2/1316:03
 */
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class Author implements Comparable<Author> {

    private String realName;

    private Integer age;

    private String sex;

    private String profile;

    private Double salary;

    private List<Book> bookList;

    @Override
    public String toString() {
        return "Author{" +
                "realName='" + realName + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", profile='" + profile + '\'' +
                ", salary=" + salary +
                ", bookList=" + bookList +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Author author = (Author) o;
        return Objects.equals(getRealName(), author.getRealName()) && Objects.equals(getAge(), author.getAge()) && Objects.equals(getSex(), author.getSex()) && Objects.equals(getProfile(), author.getProfile()) && Objects.equals(getSalary(), author.getSalary()) && Objects.equals(getBookList(), author.getBookList());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getRealName(), getAge(), getSex(), getProfile(), getSalary(), getBookList());
    }

    @Override
    public int compareTo(Author author) {
        return Integer.compare(this.getAge(), author.getAge());
    }
}
2、Book类
java 复制代码
package com.xiaobai.stream_practice.entity;

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

import java.util.Objects;

/**
 * @author wangtw
 * @ClassName Book
 * @description: 书籍实体类
 * @date 2024/2/1316:06
 */
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class Book {

    private String name;

    private Double price;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(getName(), book.getName()) && Objects.equals(getPrice(), book.getPrice());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName(), getPrice());
    }

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

(二)测试数据

java 复制代码
package com.xiaobai.stream_practice;

import com.xiaobai.stream_practice.entity.Author;
import com.xiaobai.stream_practice.entity.Book;

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

/**
 * @author wangtw
 * @ClassName PrepareWork
 * @description: 准备工作
 * @date 2024/2/1316:52
 */
public class PrepareWork {

    /**
     * 获取数据集合
     * @return
     */
    public static List<Author> getAuthorData() {
        Book book1 = Book.builder().name("Java核心技术")
                .price(59.4).build();
        Book book2 = Book.builder().name("码农翻身")
                .price(39.3).build();
        Book book3 = Book.builder().name("设计模式")
                .price(67D).build();
        Book book4 = Book.builder().name("剑指Java").price(36D).build();
        Book book5 = Book.builder().name("Java并发编程之美").price(50.3).build();
        Book book6 = Book.builder().name("Java8实战").price(40.5).build();

        List<Book> bookList1 = new ArrayList<>();
        bookList1.add(book1);
        bookList1.add(book2);
        bookList1.add(book3);

        List<Book> bookList2 = new ArrayList<>();
        bookList2.add(book4);
        bookList2.add(book5);

        List<Book> bookList3 = new ArrayList<>();
        bookList3.add(book6);

        Author author1 = Author.builder()
                .realName("张三")
                .age(41)
                .profile("拉开大家疯狂的就撒开积分的空间撒了饭")
                .sex("男")
                .salary(8000D)
                .bookList(bookList1).build();

        Author author2 = Author.builder()
                .realName("李四")
                .age(35)
                .profile("fdjsklafjdklsa")
                .sex("男")
                .salary(9000D)
                .bookList(bookList2).build();

        Author author3 = Author.builder()
                .realName("王五")
                .age(44)
                .profile("的UI为i哦uu哦u乌俄u无恶u五")
                .sex("男")
                .salary(6500D)
                .bookList(bookList3).build();

        Author author4 = Author.builder()
                .realName("孙一")
                .age(30)
                .profile("积分可贷款,草莓,新农村,男明星们,内存,你们,")
                .sex("男")
                .salary(7500D).build();

        Author author5 = Author.builder()
                .realName("赵二")
                .age(28)
                .profile("发啊副度撒ufdsvkjj")
                .sex("女")
                .salary(7999D).build();

        Author author6 = Author.builder()
                .realName("赵二")
                .age(28)
                .profile("发啊副度撒ufdsvkjj")
                .sex("女")
                .salary(7999D).build();

        List<Author> authorList = new ArrayList<>();
        authorList.add(author1);
        authorList.add(author2);
        authorList.add(author3);
        authorList.add(author4);
        authorList.add(author5);
        authorList.add(author6);

        return authorList;
    }
}

二、lambda表达式

(一)概述

lambda表达式是Jdk中的一个语法糖,可以对某些匿名内部类的写法进行简化,主要是关注对数据进行的操作。

(二)基本格式

(参数列表) -> { Lambda体 }

(三)示例

1、创建一个线程类对象
java 复制代码
        // 匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行线程(匿名内部类)");
            }
        }).start();

        // Lambda写法
        new Thread(() -> System.out.println("开始执行线程(Lambda写法)")).start();
2、实现Consumer<T>接口
java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 匿名内部类写法
        authorData.forEach(new Consumer<Author>() {
            @Override
            public void accept(Author author) {
                System.out.println(author.getRealName());
            }
        });

        // Lambda写法
        authorData.forEach(a -> System.out.println(a.getRealName()));
3、实现Comparator接口
java 复制代码
        Integer[] arr = {4, 2, 0, 8, 9};
        // 匿名表达式写法
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });

        // Lambda写法
        Arrays.sort(arr, (o1, o2) -> Integer.compare(o1, o2));

(四)Lambda表达式省略规则

1、参数类型可以省略;
2、方法体只有一句代码时大括号return和这句代码的分号可以省略;
3、方法只有一个参数时小括号可以省略;

三、函数式接口

(一)概述

只有一个抽象方法的接口称为函数式接口,只有函数式接口的实现可以使用Lambda表达式;

@FunctionInterface标识只用于验证当前接口是是否是函数式接口。

(二)常见函数式接口

1、消费型接口(Consumer)

抽象方法有参数无返回值

java 复制代码
        // 消费型接口
        Consumer<Double> consumer = new Consumer<Double>() {
            @Override
            public void accept(Double t) {
                System.out.println(t);
            }
        };
2、供给型接口(Supplier)

无参有返回值

java 复制代码
        // 供给型接口
        Supplier<Double> supplier = new Supplier<Double>() {
            @Override
            public Double get() {
                return Math.PI;
            }
        };
3、函数型接口(Function)

有参有返回值

java 复制代码
        // 函数型接口
        Function<Double, String> function = new Function<Double, String>() {
            @Override
            public String apply(Double aDouble) {
                return String.valueOf(aDouble);
            }
        };
4、断定型接口(Predicate)

对传入的参数做条件判断,并返回判断结果

java 复制代码
        // 断定型接口
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String value) {
                return StringUtils.hasText(value);
            }
        };

四、Stream流

(一)创建流

根据一个数据源(集合、数组等集合)创建Stream流

1、基于集合对象创建Stream
java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();
        // 串行流
        Stream<Author> stream = authorData.stream();
        // 并行流
        Stream<Author> parallelStream = authorData.parallelStream();

        // 双列集合,先转成单列集合再转成Stream
        Map<String, Integer> map = new HashMap<>();
        map.put("张一", 1);
        map.put("王二", 2);
        map.put("李三", 3);
        map.put("赵四", 4);
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
2、基于数组创建Stream
java 复制代码
        String[] arr = {"张一", "王二", "李三", "赵四"};
        Stream<String> stream = Arrays.stream(arr);
3、通过Stream的of方法创建
java 复制代码
Stream<String> stream = Stream.of("张一", "王二", "李三", "赵四");
4、通过Stream的generate和iterate创建无限Stream
java 复制代码
        // 使用iterate创建无限流,从2开始自增2
        Stream<Integer> iterate = Stream.iterate(2, a -> a + 2);

		// 使用generate创建无限流,
        Stream<Double> generate = Stream.generate(() -> Math.random());

(二)中间操作

1、filter(筛选)

接收Lambda,从流中排除某些元素

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        stream = stream.filter(a -> a.getAge() > 30);
        stream.forEach(a -> System.out.println(a));
2、distinct(去重)

通过流所生成元素的hashCode()和equals()方法去掉重复元素

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        stream = stream.distinct();
        stream.forEach(author -> System.out.println(author));
3、sorted(排序)

待排序集合的元素需要实现Comparable接口

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        // 这里Author实体类实现了Comparable接口,重写了compareTo方法
        stream = stream.sorted();
        stream.forEach(author -> System.out.println(author));

        // 根据其它属性排序
        Stream<Author> stream1 = authorData.stream();
        stream1.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).forEach(author -> System.out.println(author));
4、map(映射)

将每个元素映射成一个新类型的元素,一一对应

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        stream.map(author -> author.getRealName()).forEach(name -> System.out.println(name));
5、flatMap

将每个元素映射成一个流,然后把所有流连接成一个流

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        
        // 映射的时候需要判断每个author的bookList是否为空
        stream.flatMap(author -> ObjectUtils.isEmpty(author.getBookList())
                        ? Collections.EMPTY_LIST.stream() : author.getBookList().stream())
                .forEach(book -> System.out.println(book));
6、limit

截断流,使其元素不超过给定数量

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 获取前5条的数据
        Stream<Author> stream = authorData.stream();
        stream.limit(5).forEach(author -> System.out.println(author));
7、skip

skip(long n),跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,返回一个空流

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Stream<Author> stream = authorData.stream();
        // 获取集合中第三条和第四条数据
        stream.skip(2).limit(2).forEach(author -> System.out.println(author));

(三)终结操作

1、forEach

内部迭代集合,外部迭代和内部迭代的区别:http://cw.hubwiz.com/card/c/57525f2eda97b6e9299d301b/1/2/1/

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 遍历所有元素
        authorData.stream().forEach(author -> System.out.println(author));
2、count()

返回流中的元素总数

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        long count = authorData.stream().count();
        System.out.println(count);
3、max/min

返回流中最大值/最小值

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 获取年龄最大的作者
        Optional<Author> max = authorData.stream().max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
        // 安全获取Optional的值
        System.out.println(max.orElseGet(() -> new Author()));

        // 获取工资最低的作者
        Optional<Author> min = authorData.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        // 安全获取Optional的值
        System.out.println(min.orElseGet(() -> new Author()));
4、anyMatch

接收Predicate参数,检查Stream流是否包含满足条件的元素,返回值为boolean类型

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        boolean match = authorData.stream().anyMatch(author -> author.getAge() <= 30);
        System.out.println(match);
5、allMatch

接收Predicate参数,检查Stream流中的所有元素是否满足条件,返回值为boolean类型

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        boolean allMatch = authorData.stream().allMatch(author -> author.getAge() <= 30);
        System.out.println(allMatch);
6、noneMatch

只有所有元素都不满足条件,才会返回true

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        boolean noneMatch = authorData.stream().noneMatch(author -> author.getAge() > 100);
        System.out.println(noneMatch);
7、findAny

返回Stream流中的任一个元素

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 30).findAny();
        System.out.println(authorOptional.orElseGet(() -> new Author()));
8、findFirst

返回Sream流中的第一个元素

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 30).findFirst();
        System.out.println(authorOptional.orElseGet(() -> new Author()));
9、reduce

可以将流中元素反复结合起来,得到一个值

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 计算所有工资总和
        Double sum = authorData.stream().map(author -> author.getSalary())
                .reduce((double) 0, (s1, s2) -> s1 + s2);
        System.out.println(sum);

        Optional<Double> sumOptional = authorData.stream().map(author -> author.getSalary())
                .reduce((s1, s2) -> s1 + s2);
        sumOptional.orElseGet(() -> 0D);
10、collect

将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 将所有作者的名字转换成List
        List<String> realNameList = authorData.stream().map(author -> author.getRealName())
                .collect(Collectors.toList());
        
        // 将所有作者按照工资进行分组
        Map<Double, List<Author>> authorMap = authorData.stream().collect(Collectors.groupingBy(author -> author.getSalary()));

(四)并行流

当流中有大量元素时,可以使用并行流提高操作的效率。并行流是把任务分配给多个线程去完成。

java 复制代码
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 串行流转并行流并计算
        Integer sum1 = stream.parallel()
                .peek(num -> System.out.println(Thread.currentThread().getName() + ":" + num))
                .filter(num -> num > 5)
                .reduce((n1, n2) -> n1 + n2)
                .get();
        System.out.println(sum1);

        // 直接获取并行流
        Integer sum2 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).parallelStream()
                .peek(num -> System.out.println(Thread.currentThread().getName() + ":" + num))
                .filter(num -> num > 5)
                .reduce((n1, n2) -> n1 + n2)
                .get();
        System.out.println(sum2);

注:peek方法接收Consumer参数,可以对并行流进行调试。

五、方法引用

1、引用类的静态方法

方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,方法参数列表和返回类型与接口中抽象方法的参数列表和返回类型完全一致,这个时候我们就可以引用类的静态方法。

格式:类名::方法名

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 获取年龄的字符串集合,使用String.valueOf转换
        List<String> ageList = authorData.stream().map(author -> author.getAge())
                .map(age -> String.valueOf(age))
                .collect(Collectors.toList());

        // 年龄由整型转换成字符串时,使用静态方法引用
        List<String> ageList1 = authorData.stream().map(author -> author.getAge())
                .map(String::valueOf)
                .collect(Collectors.toList());
2、引用对象的实例方法

方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,方法参数列表和返回类型与接口中抽象方法的参数列表和返回类型完全一致,可以引用对象的实例方法。

格式:对象名::方法名

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        StringBuilder stringBuilder = new StringBuilder();
        authorData.stream().map(author -> author.getRealName())
                .forEach(name -> stringBuilder.append(name));
        System.out.println(stringBuilder.toString());

        // 使用对象的方法引用
        StringBuilder stringBuilder1 = new StringBuilder();
        authorData.stream().map(author -> author.getRealName())
                .forEach(stringBuilder1::append);
        System.out.println(stringBuilder1.toString());
3、引用类的实例方法

方法体中只有一行代码,调用方法的调用者必须是抽象方法的第一个参数,调用方法的参数列表和抽象方法的其他参数一致。

格式:类名::方法名

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 排序,compareTo方法的调用者是抽象方法的第一个参数
        Collections.sort(authorData, new Comparator<Author>() {
            @Override
            public int compare(Author o1, Author o2) {
                return o1.compareTo(o2);
            }
        });
        // 优化
        Collections.sort(authorData, Author::compareTo);

        // 获取作者的姓名,getRealName方法的调用者时抽象方法的第一个参数
        Stream<String> stream = authorData.stream().map(new Function<Author, String>() {
            @Override
            public String apply(Author author) {
                return author.getRealName();
            }
        });
        // 优化
        Stream<String> stream1 = authorData.stream().map(Author::getRealName);
4、构造器引用

方法体中只有一行代码,仅有的这行代码是一个通过new调用构造器的return语句,抽象方法的参数列表和调用的构造器参数列表完全一致,并且抽象方法返回的正好是通过构造器创建的对象。

格式:类名::new

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        Optional<Author> first = authorData.stream().findFirst();
        first.orElseGet(() -> new Author());

        // 优化:构造器引用(供给型接口)
        first.orElseGet(Author::new);

        // 函数式接口
        Function<String, Integer> function1 = Integer::new;
        // 数组构造引用
        Function<Integer, String[]> function2 = String[]::new;

六、Optional使用

1、用途

避免空指针

2、创建对象

(1)Optional.ofNullable(T t):若t不为null,则创建Optional实例,否则创建空实例;

(2)Optional.of(T t):创建了一个维护t对象的Optional实例,t对象不能为null;

(3)Optional.empty():创建一个空的Optional实例;

3、安全消费值

ifPresent(Consumer<? super T> action):如果调用对象包含值,则进行消费操作,否则不消费;

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 筛选
        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 40)
                .findFirst();

        // 安全消费
        authorOptional.ifPresent(System.out::println);
4、安全获取值

(1)orElseGet(Supplier<? extends T> other):如果调用对象包含值,则返回该值,否则返回供给型接口创建的值;

(2)orElseThrow(Supplier<? extends X> exceptionSupplier):如果调用对象包含值,则返回该值,否则报由Supplier提供的异常对象;

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 筛选
        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 100)
                .findFirst();

        // 安全获取值
        Author author = authorOptional.orElseGet(Author::new);
        System.out.println(author);

        Author author1 = authorOptional.orElseThrow(() -> new RuntimeException("获取值失败"));
        System.out.println(author1);
5、过滤

Optional.ofNullable(T t).filter(Predicate<? super T> predicate):对数据进行过滤,如果有数据但是不符合判断,也会变成一个无数据的Optional对象;

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 筛选
        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 30)
                .findFirst();

        // Optional筛选
        Optional<Author> author1 = authorOptional.filter(author -> author.getAge() > 40);
        System.out.println(author1.orElseGet(Author::new));
6、判断

isPresent():判断是否包含值,非空返回true;

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 筛选
        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 30)
                .findFirst();
        if (authorOptional.isPresent()) {
            System.out.println(authorOptional.get());
        }
7、数据转换

optional.map(Function mapper):如果有值对其处理,返回处理后的Optional,否则返回Optional.empty();

java 复制代码
        List<Author> authorData = PrepareWork.getAuthorData();

        // 筛选
        Optional<Author> authorOptional = authorData.stream().filter(author -> author.getAge() > 40)
                .findFirst();

        // 转换
        Optional<List<Book>> books = authorOptional.map(Author::getBookList);
        System.out.println(books.orElseGet(ArrayList::new));

代码地址:https://gitee.com/wangtianwen1996/cento-practice/tree/master/src/test/java/com/xiaobai/stream_practice

参考:
https://www.bilibili.com/video/BV1Gh41187uR/?spm_id_from=333.999.0.0&vd_source=228bb3012271d4ddf3f85e6d1e965d18

剑指Java:核心原理与应用实践

相关推荐
七星静香8 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员9 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU9 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie613 分钟前
在IDEA中使用Git
java·git
Elaine20239128 分钟前
06 网络编程基础
java·网络
G丶AEOM29 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201330 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀31 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海34 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel