一、准备工作

(一)实体类

1、Author类
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类
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 +
                '}';
    }
}

(二)测试数据

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、创建一个线程类对象
        // 匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行线程(匿名内部类)");
            }
        }).start();

        // Lambda写法
        new Thread(() -> System.out.println("开始执行线程(Lambda写法)")).start();
2、实现Consumer<T>接口
        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接口
        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)

抽象方法有参数无返回值

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

无参有返回值

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

有参有返回值

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

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

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

四、Stream流

(一)创建流

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

1、基于集合对象创建Stream
        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
        String[] arr = {"张一", "王二", "李三", "赵四"};
        Stream<String> stream = Arrays.stream(arr);
3、通过Stream的of方法创建
Stream<String> stream = Stream.of("张一", "王二", "李三", "赵四");
4、通过Stream的generate和iterate创建无限Stream
        // 使用iterate创建无限流,从2开始自增2
        Stream<Integer> iterate = Stream.iterate(2, a -> a + 2);

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

(二)中间操作

1、filter(筛选)

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

        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()方法去掉重复元素

        List<Author> authorData = PrepareWork.getAuthorData();

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

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

        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(映射)

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

        List<Author> authorData = PrepareWork.getAuthorData();

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

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

        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

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

        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个,返回一个空流

        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/

        List<Author> authorData = PrepareWork.getAuthorData();

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

返回流中的元素总数

        List<Author> authorData = PrepareWork.getAuthorData();

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

返回流中最大值/最小值

        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类型

        List<Author> authorData = PrepareWork.getAuthorData();

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

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

        List<Author> authorData = PrepareWork.getAuthorData();

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

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

        List<Author> authorData = PrepareWork.getAuthorData();

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

返回Stream流中的任一个元素

        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流中的第一个元素

        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

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

        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中元素做汇总的方法。

        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()));

(四)并行流

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

        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、引用类的静态方法

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

        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、引用对象的实例方法

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

        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、引用类的实例方法

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

        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

        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):如果调用对象包含值,则进行消费操作,否则不消费;

        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提供的异常对象;

        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对象;

        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;

        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();

        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));
04-01 06:58