函数式编程汇总

news2024/11/17 1:47:42

目录

一 . Lambda 表达式

实例

省略规则

二. Stream 流

案例数据准备

入门实例

        调试技巧

常用操作

创建流

1. 单例集合

2. 数组

3. 双列集合

中间操作

1. filter

2. map

3. distinct

4. sorted

5. limit

7. flatMap

终结操作

1. forEach

2. count

3. max & min

4. collect

查找与匹配

1. anyMatch

2. allMatch

3. noneMatch

4. findAny

5. firstAny

reduce 归并

实例

注意事项:

三. Optional

1. 创建对象

2. 安全消费值

3. 获取值

4. 安全获取值

5. 过滤

6. 判断

7. 数据转换

四. 函数式接口

常见函数式接口

常见默认方法

五. 方法引用

1. 推荐用法

2. 基本格式

3. 语法详解(了解)

引用静态方法

引用对象的实例方法

构造器引用

高级用法

并行流


一 . Lambda 表达式

        Lambda 表达式可以对某些匿名内部类的写法进行简化. 它是函数式编程的一个重要体现, 让我们不用太关注什么是对象, 而是更关心对数据的处理.

基本格式:

(参数列表)->{代码}

实例:

例子1: 当我们在创建一个线程的时

没有使用 Lambda 表达式:

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我使用了匿名内部类的方式");
            }
        }).start();

结果: 我使用了匿名内部类的方式

使用 Lambda 了表达式:

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

结果: 使用 Lambda 了表达式

例子2: 

没有使用 Lambda 表达式:

    @Test
    void test2() {
        int i = calculateNum(new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                return left + right;
            }
        });
        System.out.println(i);
    }

    public static int calculateNum(IntBinaryOperator operator) {
        int a = 10;
        int b = 20;
        return operator.applyAsInt(a, b);
    }

结果: 30

使用 Lambda 了表达式:

    @Test
    void test2() {
        int i = calculateNum((int left, int right) -> {
            return left + right;
        });
        System.out.println(i);
    }

    public static int calculateNum(IntBinaryOperator operator) {
        int a = 10;
        int b = 20;
        return operator.applyAsInt(a, b);
    }

结果: 30

tips: 上面代码出现的 IntBinaryOperator 是一个接口, 并且有一个 applyAsInt 方法


例子3:

没有使用 Lambda 表达式:

    @Test
    void test3() {
        printNum(new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value % 2 == 0;
            }
        });
    }

    public static void printNum(IntPredicate predicate) {
        int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};
        for (int i : arr) {
            if (predicate.test(i)) {
                System.out.println(i);
            }
        }
    }


结果: 
2
4
6
8
10

使用 Lambda 了表达式:

    @Test
    void test3() {
        printNum((int value) -> {
            return value % 2 == 0;
        });
    }

    public static void printNum(IntPredicate predicate) {
        int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};
        for (int i : arr) {
            if (predicate.test(i)) {
                System.out.println(i);
            }
        }
    }

结果:
2
4
6
8
10

tips: 上面代码出现的 IntPredicate 也是一个接口, 并且有一个 test 方法

例子4:

没有使用 Lambda 表达式:

    @Test
    void test4() {
        Integer integer = typeConver(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        });
        System.out.println(integer);
    }

    public static <R> R typeConver(Function<String, R> function) {
        String str = "12345";
        R result = function.apply(str);
        return result;
    }

结果: 12345

使用 Lambda 了表达式:

    @Test
    void test4() {
        Integer integer = typeConver((String s) -> {
            return Integer.valueOf(s);
        });
        System.out.println(integer);
    }

    public static <R> R typeConver(Function<String, R> function) {
        String str = "12345";
        R result = function.apply(str);
        return result;
    }

结果: 12345

我们还可以让它返回一个 String 类型的值:

    @Test
    void test4() {
        String str = typeConver((String s) -> {
            return s + "上山打老虎";
        });
        System.out.println(str);
    }

    public static <R> R typeConver(Function<String, R> function) {
        String str = "12345";
        R result = function.apply(str);
        return result;
    }

结果: 12345上山打老虎

tips: 上面代码出现的 Function<T, R> 也是一个接口, 并且有一个 apply 方法

例子5:

没有使用 Lambda 表达式:

    @Test
    void test5() {
        foreachArr(new IntConsumer() {
            @Override
            public void accept(int value) {
                System.out.print(value + " ");
            }
        });
    }

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

结果: 1 2 3 4 5 6 7 8 9 10 

使用了 Lambda 表达式:

    @Test
    void test5() {
        foreachArr((int value) -> {
            System.out.print(value + " ");
        });
    }

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

tips: 上面代码出现的 IntConsumer 也是一个接口, 并且有一个 accept 方法

Lambda 的简写:

Lambda 表达式 只关注 参数列表 和 方法体, 其他的一律不过问.

省略规则:

1. 参数类型可以省略

2. 方法体如果只有一局代码时, 大括号"{}" 和 return 还有 分号 “;” 可以省略

3. 方法只有一个参数时,参数列表的小括号可以省略

如果上述规则觉得有些复杂,可以不用记,可以先使用函数式编程把代码敲出来,让后将鼠标移动到 对象名 上,让后按住 art + enter 键,选 "Replace with lambda"。 

然后就变成简化后的 Lambda 表达式了

二. Stream 流

        Java8 的 Stream 使用的是函数式编程模式, 它可以被用来对集合或数组进行链状流式的操作. 可以更方便的让我们对数组或集合进行操作.

案例数据准备:

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期的去重使用
public class Book {
    // id
    private Long id;
    // 分类
    private String category;
    // 书名
    private String name;
    // 评分
    private Double score;
    // 简介
    private String intro;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期的去重使用
public class Author {
    // id
    private Long id;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    // 简介
    private String info;
    // 作品
    private List<Book> books;
}

创建一个测试类

    // 初始化一些数据
    private static List<Author> getAuthors() {
        Author author1 = new Author(1L, "周杰伦", 18, "my introduction 1", null);
        Author author2 = new Author(2L, "周星驰", 19, "my introduction 2", null);
        Author author3 = new Author(3L, "周润发", 20, "my introduction 3", null);
        Author author4 = new Author(4L, "周迅", 17, "my introduction 4", null);
        Author author5 = new Author(5L, "周一", 16, "my introduction 4", null);

        List<Book> books1 = new ArrayList<>();
        List<Book> books2 = new ArrayList<>();
        List<Book> books3 = new ArrayList<>();

        // 上面是作者和书
        books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
        books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));
        books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));

        books2.add(new Book(5L, "天啊", "书名4", 65D, "这是简介哦"));
        books2.add(new Book(6L, "高效", "书名5", 89D, "这是简介哦"));

        books3.add(new Book(7L, "久啊,天啊", "书名6", 45D, "这是简介哦"));
        books3.add(new Book(8L, "高效", "书名7", 44D, "这是简介哦"));
        books3.add(new Book(9L, "喜剧,高效", "书名8", 81D, "这是简介哦"));

        author1.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        author4.setBooks(books3);
        author5.setBooks(books2);

        return new ArrayList<>(Arrays.asList(author1, author2, author3, author4, author5));
    }

    private Author getAuthor() {
        Author author = new Author(1L, "周杰伦", 18, "my introduction 1", null);
        List<Book> books1 = new ArrayList<>();
        books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
        books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));
        books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));
        author.setBooks(books1);
        return author;
    }

入门实例:

例子1: 

现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重

(使用函数式编程)

    @Test
    void test1() {
        // 我们可以调用 getAuthors 方法获取到作家集合.
        List<Author> authors = getAuthors();
        // 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重
        authors.stream() // 把对象转换成流
                .distinct() // 去重
                .filter(new Predicate<Author>() { // 筛选小于 18 岁的作者
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() < 18;
                    }
                })
                .forEach(new Consumer<Author>() { // 打印
                    @Override
                    public void accept(Author author) {
                        System.out.println(author);
                    }
                });
    }

结果: 
Author(id=4, name=周迅, age=17, info=my introduction 4, books=[Book(id=7, name=久啊, category=书名6, score=45.0, intro=这是简介哦), Book(id=8, name=高效, category=书名7, score=44.0, intro=这是简介哦), Book(id=9, name=喜剧, category=书名8, score=81.0, intro=这是简介哦)])
Author(id=5, name=周一, age=16, info=my introduction 4, books=[Book(id=5, name=天啊, category=书名4, score=65.0, intro=这是简介哦), Book(id=6, name=高效, category=书名5, score=89.0, intro=这是简介哦)])

(使用 Lambda 表达式, 结果同上)

    @Test
    void test1() {
        // 我们可以调用 getAuthors 方法获取到作家集合.
        List<Author> authors = getAuthors();
        // 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重
        // 筛选小于 18 岁的作者
        // 打印
        authors.stream() // 把集合转换成流
                .distinct() // 去重
                .filter(author -> author.getAge() < 18)
                .forEach(author -> System.out.println(author));
    }

调试技巧:

我们再进行 Stream 调试的时候, 可以点击下图指示的按钮

就会出现下图的画面:

然会就可以看到一些调试信息了 ~~ 

常用操作:

创建流

1. 单例集合:

集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> system = authors.stream();

2. 数组

Arrags.stream(数组), 或者使用 Stream.of 来创建
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> system1 = Arrays.stream(arr);
Stream<Integer> system2 = Stream.of(arr);

3. 双列集合

转换成单例集合后再创建        
Map<String, Integer> map = new HashMap<>();
map.put("鸣人", 1);
map.put("佐助", 2);
map.put("小樱", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

中间操作

1. filter

        filter 可以对流中的元素进行条件过滤, 符合过滤条件的才能继续留在流中.

例如: 

        打印所有姓名长度大于 1 的作家的名字

        authors.stream()
                .filter(author -> author.getName().length() > 2)
                .forEach(author -> System.out.println(author));

2. map

        map 可以把元素进行计算或者转换.

        // 打印所有作家的姓名, 并在姓名后加 "***" (将 Author类 转换成 String类)
        authors.stream()
                .map(author -> author.getName())
                .map(name -> name + "***")
                .forEach(author -> System.out.println(author));

结果:
周杰伦***
周星驰***
周润发***
周迅***
周一***

3. distinct

        distinct 可以去除流中重复元素

        // 打印所有作家元素, 并且要求不能有重复元素
        authors.stream()
                .distinct()
                .forEach(author -> System.out.println(author));

注意: distinct 方法是依赖 Objects 的 equals 方法来判断是否是相同的对象的, 所以需要注意重写 equals 方法. 这个重写我们使用了 Lombok 的注解来实现了.

4. sorted

        sorted 可以对流中的对象进行排序

注意 : 如果使用空参的 sorted 方法, 在使用这个方法前, 对象要实现 Comparable 接口, 并重写 compareTo 方法:

        // 对流中的作家元素按照年龄进行升序排序.
        authors.stream()
                .sorted()
                .forEach(author -> System.out.println(author));

或者使用一个含有 Comparator 比较器的 sorted 方法:

        // 对流中的作家元素按照年龄进行升序排序.
        authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .forEach(author -> System.out.println(author));

5. limit

        limit 可以设置流的最大长度, 超出的部分将被抛弃.

        // 对流中的作家元素按照年龄进行升序排序. 然后打印年龄最小的两个作家
        authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .limit(2L)
                .forEach(author -> System.out.println(author));

6. skip

        skip 跳过流中的前 n 个元素, 返回剩下的元素.

        // 对流中的作家元素按照年龄进行升序排序. 然后打印除了年龄最小的两个作家之外的其他作家
        authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .skip(2L)
                .forEach(author -> System.out.println(author));

7. flatMap

        flatMap 可以把一个对象转换成多个对象作为流中的元素.

例子1:

        // 打印所有书籍的名字
        // 匿名内部类版
        authors.stream()
                .flatMap(new Function<Author, Stream<Book>>() {
                    @Override
                    public Stream<Book> apply(Author author) {
                        return author.getBooks().stream();
                    }
                })
                .distinct()
                .forEach(new Consumer<Book>() {
                    @Override
                    public void accept(Book book) {
                        System.out.println(book.getName());
                    }
                });

        // Lambda 表达式版
        authors.stream()
                .flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));

根据调试结果可以看出一个 Author 对象转换成多个 Book 对象作为流中的元素.

例子2:

        // 打印所有书籍的分类, 要求对分类进行去重, 不能出现这种格式: 高效,天啊
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .flatMap(book ->  Arrays.stream(book.getCategory().split(",")))
                .distinct()
                .forEach(category -> System.out.println(category));

终结操作

1. forEach

        对流中的元素进行遍历操作, 我们通过传入的参数去指定对遍历到的元素进行什么具体操作.

        // 打印所有作家的姓名
        authors.stream()
                .forEach(author -> System.out.println(author.getName()));

2. count

        可以用来获取当前流中的元素个数

        // 打印出这些作家所出书籍的数目, 注意删除重复的元素
        long count = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .count();
        System.out.println(count);

3. max & min

        可以获取流中的最值

        // 分别获取这些作家的所出书籍的最高分和最低分并打印
        Optional<Double> max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max((o1, o2) -> (int) (o1 - o2));
        System.out.println(max.get());

        Optional<Double> min = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min((o1, o2) -> (int) (o1 - o2));
        System.out.println(min.get());

4. collect

        collect 把当前流转换成一个集合

例子1:

        // 获取一个存放所有作者名字的 List 集合
        List<String> authorsName = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());
        System.out.println(authorsName);

例子2:

        // 获取一个所有书名的 Set 集合
        Set<String> booksName = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getName())
                .collect(Collectors.toSet());
        System.out.println(booksName);

例子3:

        // 获取一个 Map 集合, map 的 key 作为 作者名, value 为 List<Book>
        // 匿名内部类版
        Map<String, List<Book>> map = authors.stream()
                .distinct()
                .collect(Collectors.toMap(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) {
                        return author.getName();
                    }
                }, new Function<Author, List<Book>>() {
                    @Override
                    public List<Book> apply(Author author) {
                        return author.getBooks();
                    }
                }));

        // Lambda 表达式版
        Map<String, List<Book>> map = authors.stream()
                .distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));

注意: key 值不能重复, 不然会报错!!!

查找与匹配

1. anyMatch

        anyMatch 可以用来判断是否有任意元素符合匹配条件, 结果为 boolean 类型

        // 判断是否有年龄在 19 岁以上的作家
        boolean b = authors.stream()
                .anyMatch(author -> author.getAge() > 19);
        System.out.println(b);

2. allMatch

        可以用来判断是否所有元素符合匹配条件, 结果为 boolean 类型

        // 判断是否所有年龄在 19 岁以上的作家
        boolean b = authors.stream()
                .allMatch(author -> author.getAge() > 19);
        System.out.println(b);

3. noneMatch

        noneMatch 可以用来判断是否所有元素都不符合匹配条件, 结果为 boolean 类型, 都不符合返回 true, 都不符合返回 false

        boolean b = authors.stream()
                .noneMatch(author -> author.getAge() > 100);
        System.out.println(b);

4. findAny

        findAny 获取流中的任意一个元素.

        // 获取任意一个年龄大于 18 的作家, 如果存在就输出它的姓名
        Optional<Author> optionalAuthor = authors.stream()
                .filter(author -> author.getAge() > 18)
                .findAny();
        optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

5. firstAny

        firstAny 获取流中的第一个元素.

        // 获取一个年龄最小的作家, 如果存在就输出它的姓名
        Optional<Author> optionalAuthor = authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .findFirst();
        optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

reduce 归并

        对流中的数据, 按照你指定的计算方式计算出一个结果.(缩紧操作)

        reduce 的作用十八 stream 中的元素给组合起来, 我们可以传入一个初始值, 他会按照我们计算方式依次拿流中的元素和在初始化值进行计算, 计算结果再和后面的元素计算.

        reduce  的两个参数的重载形式内部的计算方式如下:

T result = identity; // 相当于初始值
for (T element : this stream) {
    result = accumulator.apply(result, element);
}
return result;

// 你可以把这段代码想象成
int[] arr = {1, 2, 3, 4, 5};
int sum = 0;
for (int i : arr) {
    sum = sum + i;
}
return sum;

实例:

例子1:

        // 使用 reduce 求所有作家年龄的和
        // 使用匿名内部类方式
        Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象
                .map(author -> author.getAge())
                // 此处的 0 相当于初始值 identity
                .reduce(0, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer element) {
                        return result+ element;
                    }
                });
        System.out.println(ageCount);

        // 使用Lambda表达式
        Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result+ element);
        System.out.println(ageCount);

例子2:

        // 使用 reduce 求作者年龄的最大值
        // 使用匿名内部类方式
        Integer maxAge = authors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer element) {
                        return result < element ? element : result;
                    }
                });
        System.out.println(maxAge);        

        // 使用Lambda表达式
        Integer maxAge = authors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
        System.out.println(maxAge);

reduce  的一个参数的重载形式内部的计算方式如下:

    boolean foundAny = false;
    T result = null;
    for (T element : this stream) {
        if (!foundAny) { // 第一次遍历走到这里
            foundAny = true; // 将 foundAny 置为true, 相当于这里面的代码只执行一次
            result = element; // 将第一个元素给到 result
        }
        else // 其余的遍历走下面这行代码
            result = accumulator.apply(result, element);
    }
    return foundAny ? Optional.of(result) : Optional.empty();

例子3:

        // 使用 reduce 求作者年龄的最小值
        // 使用匿名内部类方式
        Optional<Integer> minAge = authors.stream()
                .map(author -> author.getAge())
                .reduce(new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer element) {
                        return result > element ? element : result;
                    }
                });
        minAge.ifPresent(age -> System.out.println(age));

        // 使用Lambda表达式
        Optional<Integer> minAge = authors.stream()
                .map(author -> author.getAge())
                .reduce((result, element) -> result > element ? element : result);
        minAge.ifPresent(age -> System.out.println(age));

注意事项:

1. 惰性求值 (如果没有终结操作, 中间操作是不会得到执行的)

2. 流是一次性的 (一旦一个流对象经过一个终结操作后, 这个流不能再被使用)

3. 不会影响原数据 (我们在流中可以多数据地做很多处理, 但是正常情况下(不调用原数据的set方法等)是不会影响原来集合中的元素的)

三. Optional

        我们在编写代码的时候经常会出现空指针异常, 所以在很多情况下我们需要做各种非空的判断.

        比如:

Author author = getAuthor();
if (author != null) {
    System.out.println(author.getName());
}

尤其是对象中的属性还有一个对象的情况下, 这种判断会更多.

而过多的判断语句会使我们的代码显得臃肿不堪.

所以在 JDK 8 中引入了 Optional, 养成使用 Optional 的习惯后, 可以写出更优雅的代码来避免空指针异常. 并且在很多的函数式编程相关的 API 中也都用到了 Optional.

1. 创建对象

        Optional 就好像是包装类,可以把我们的具体数据封装 Optional 对象内部。 然后我们去使用Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。


        我们一般使用 Optional 的静态方法 ofNullable 来把数据封装成一个 Optional 对象。 无论传入的参数是否为null都不会出现问题。

        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        authorOptional.ifPresent(author -> System.out.println(author.getName()));

        你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下 getAuthor 方法,让其的返回值就是封装好的 Optional 的话,我们在使用时就会方便很多。
        而且在实际开发中我们的数据很多是从数据库获取的。Mybatis 从 3.5 版本可以也已经支持 Optional 了。 我们可以直接把 dao 方法的返回值类型定义成 Optional 类型,MyBastis 会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。

        如果你确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。

        Author authors = new Author();
        Optional<Author> authorOptional = Optional.of(authors);

但是一定要注意,如果使用of的时候传入的参数必须不为 null。

如果一个方法的返回值类型是 Optional 类型。 而如果我们经判断发现某次计算得到的返回值为 null,这个时候就需要把 null 封装成 Optional 对象返回。这时则可以使用 Optional 的静态方法 empty 来进行封装。

Optional.empty()

2. 安全消费值

        我们获取到一个optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其 ifPresent 方法对来消费其中的值。这个方法会判断其内封装的数据是否为空, 不为空时才会执行具体的消费代码。这样使用起来就更加安全了。


例如, 以下写法就优雅的避免了空指针异常。

        Optional<Author> authorOptional = Optional.ofNullable(authors);
        authorOptional.ifPresent(author -> System.out.println(author.getName()));

3. 获取值

        如果我们想获取值自己进行处理可以使用get方法获取,但是不推荐。因为当Optional内部的数据为空的时候会出现异常。
 

        Author authors = new Author();
        Optional<Author> authorOptional = Optional.of(authors);
        System.out.println(authorOptional.get().getName());

4. 安全获取值

如果我们期望安全的获取值。我们不推荐使用get方法,而是使用Optional提供的以下方法。

  • orElseGet

获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。
 

        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        System.out.println(authorOptional.orElseGet(new Supplier<Author>() {
            @Override
            public Author get() {
                return new Author(); // 为空时的默认返回值
            }
        }).getName());

获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。

  • orElseThrow
        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        System.out.println(authorOptional.orElseThrow(new Supplier<Throwable>() {
            @Override
            public Throwable get() {
                return new RuntimeException("数据为空"); // 为空时抛出异常
            }
        }).getName());

5. 过滤

        我们可以使用 filter 方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的 Optional 对象。

        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        Optional<Author> newAuthor = authorOptional.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge() > 18;
            }
        });

6. 判断

        我们可以使用 isPresent 方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现 Optional 的好处,更推荐使用ifPresent方法

        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        if (authorOptional.isPresent()) {
            System.out.println(authorOptional.get().getName());
        }

7. 数据转换

        Optional 还提供了 map 可以让我们的对数据进行转换,并且转换得到的数据也还是被 Optional 包装好的,保证了我们的使用安全。


例如我们想获取作家的书籍集合:

        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        authorOptional.map(new Function<Author, List<Book>>() {
            @Override
            public List<Book> apply(Author author) {
                return author.getBooks();
            }
        }).ifPresent(books -> System.out.println(books));

四. 函数式接口

        只有一个抽象方法的接口我们称之为函数接口。
        JDK的函数式接口都加上了 @FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

常见函数式接口

  • Consumer消费接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。

  • Function计算转换接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。

  • Predicate判断接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。

  • Supplier生产型接口

根据其中抽象方法的参数列表和返回值类型知道, 我们可以在方法中创建对象, 把创建好的对象返回

常见默认方法

  • and

我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 and 方法相当于是使用 && 来拼接两个判断条件
例如: 
打印作家中年龄大于17并且姓名的长度大于1的作家。
 

        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge() > 18;
            }
        }.and(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getName().length() > 2;
            }
        })).forEach(author -> System.out.println(author));

        // 简写
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 && author.getName().length() > 2).forEach(author -> System.out.println(author));

  • or

我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 or 方法相当于是使用| |来拼接两个判断条件。
例如:
打印作家中年龄大于17或者姓名的长度小于2的作家。

        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge() > 18;
            }
        }.or(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getName().length() > 2;
            }
        })).forEach(author -> System.out.println(author));

        // 简写
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 || author.getName().length() > 2).forEach(author -> System.out.println(author));

  • negate

Predicate 接口中的方法。negate 方法相当于是在判断添加前面加了个 ! 表示取反
例如:
打印作家中年龄不大于17的作家。

        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge() > 17;
            }
        }.negate()).forEach(author -> System.out.println(author));

五. 方法引用

        我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法) ,我们可以用方法引用进一步简化代码。


1. 推荐用法

        我们在使用lambda时不需要考虑什么时候用方法引用, 用哪种方法引用, 方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码, 并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
        当我们方法引用使用的多了慢慢的也可以直接写出方法引用。

转换成


2. 基本格式

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


3. 语法详解(了解)

引用静态方法

         其实就是引用类的静态方法

格式:

类名::方法名

使用前提
        如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。

例如:
如下代码就可以用方法引用进行简化

        // 使用方法引用前
        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        authorOptional.map(author -> author.getAge())
                .map(new Function<Integer, String>() {
                    @Override
                    public String apply(Integer age) {
                        return String.valueOf(age);
                    }
                });

        // 使用方法引用后
        Author authors = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(authors);
        authorOptional.map(author -> author.getAge())
                .map(String::valueOf);

引用对象的实例方法

格式:

对象名::方法名

使用前提
        如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法, 并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法

例如:

        // 使用方法引用前
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        StringBuilder sb = new StringBuilder();
        authorStream.map(author -> author.getName()).forEach(new Consumer<String>() {
            @Override
            public void accept(String name) {
                sb.append(name);
            }
        });

优化后:

        // 使用方法引用前
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        StringBuilder sb = new StringBuilder();
        authorStream.map(author -> author.getName()).forEach(sb::append);

引用类的实例方法

格式:

类名::方法名

使用前提
        如果我们在重写方法的时候,方法体中只有一行代码, 并且这行代码是调用了第一个参数的成员方法, 并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。


 

    interface UseString{
        String use(String str, int start, int length);
    }

    public static String subAuthorName(String str, UseString useString) {
        int start = 0;
        int length = 1;
        return useString.use(str, start, length);
    }
    
    @Test
    void testMethodReference() {
        // 优化前
        subAuthorName("周杰伦", new UseString() {
            @Override
            public String use(String str, int start, int length) {
                return str.substring(start, length); // 第一个对象把后面两个按照顺序传入了,所以可以直接简化
        // 优化后
        subAuthorName("周杰伦", String::substring); 
            }
        });
    }

构造器引用

        如果方法体中的一行代码是构造器的话可以使用构造器引用

格式:

类名::new

使用前提

        如果我们在重写方法的时候,方法体中只有一行代码, 并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。

        // 优化前
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.map(author -> author.getName())
                .map(new Function<String, StringBuilder>() {
                    @Override
                    public StringBuilder apply(String name) {
                        return new StringBuilder(name);
                    }
                })
                .map(stringBuilder -> stringBuilder.append("好酷哦").toString())
                .forEach(str -> System.out.println(str));

        // 优化后
        List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.map(author -> author.getName())
                .map(StringBuilder::new)
                .map(stringBuilder -> stringBuilder.append("好酷哦").toString())
                .forEach(str -> System.out.println(str));

高级用法

基本数据类型优化

        我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
        即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。 但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
        所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了 很多专门针对基本数据类型的方法。


例如: mapToInt,mapToLong,mapToDouble,flatMapTolnt,flatMapToDouble等。

        List<Author> authors = getAuthors();
        // 优化前
        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(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age -> age > 18)
                .map(age -> age + 2)
                .forEach(System.out::println);

并行流

        当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。

parallel 可以把串行流改成并行流.

        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5 ,6, 7, 8, 9, 10);
        Integer sum = stream.parallel()
                .peek(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer num) {
                        System.out.println("数字" + num + " 的线程为 " + Thread.currentThread().getName());
                    }
                })
                .filter(num -> num > 5)
                .reduce((result, element) -> result + element)
                .get();

结果: 
数字7 的线程为 main
数字6 的线程为 main
数字2 的线程为 main
数字1 的线程为 ForkJoinPool.commonPool-worker-11
数字8 的线程为 main
数字4 的线程为 main
数字10 的线程为 ForkJoinPool.commonPool-worker-4
数字5 的线程为 ForkJoinPool.commonPool-worker-11
数字3 的线程为 ForkJoinPool.commonPool-worker-9
数字9 的线程为 ForkJoinPool.commonPool-worker-2

也可以通过 parallelStream 直接获取并行流对象

        List<Author> authors = getAuthors();
        authors.parallelStream()
                .mapToInt(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age -> age > 18)
                .map(age -> age + 2)
                .forEach(System.out::println);

以上内容参考自 up 主 --- 三更草堂的视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决MySQL8.0本地计算机上的MySQL服务启动后停止没有报告任何错误

1.启动MySQL的错误信息如下 &#xff08;1&#xff09;“本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止。” &#xff08;2&#xff09;又在PowerShell中运行"net start MySQL"&#xff0c;服务启动失败。“MySQL 服务无法启…

27、git的安装和配置(自用简易版)

1.git的安装 安装没有什么好说的&#xff0c;运行安装包&#xff0c;一直下一步下一步&#xff0c;就好了 2.配置 首先配置用户名和邮箱吧 git config -global user.name "liu_liangyi"git config -global user.email 993261877qq.com配置好后可以查看一下,输入指令…

Java - LambdaQueryWrapper 的常用方法

1、查看项目中是否导入mybatisPlus的jar包 2、servie 层和实现类要集成mybatisPlus service 继承IService<> 实现类中要继承IService的实现类ServiceImpl<mapper,实体类> 3、如果想要mapper中的一些方法&#xff0c;mapper 要继承BaseMapper<实体类> 4、在实…

Nginx替代产品-Tengine健康检测

1、官网地址 官网地址&#xff1a;The Tengine Web Server 文档地址&#xff1a;文档 - The Tengine Web Server 健康检测模块&#xff1a;ngx_http_upstream_check_module - The Tengine Web Server 2、安装 下载 wget https://tengine.taobao.org/download/tengine-3.…

JAVA智慧物业源码 智慧物业系统源码

JAVA智慧物业源码 智慧物业系统源码 基于SpringBoot、Spring Security、Jwt、Vue的前后端分离的后台管理系统 编号&#xff1a;LQ8 1、系统环境 Java EE 8Servlet 3.0Apache Maven 3 2、主框架 Spring Boot 2.2.xSpring Framework 5.2.xSpring Security 5.2.x 3、持久层…

​全球人类读书会《乡村振兴战略下传统村落文化旅游设计》中国建筑出版传媒许少辉博士著作

​全球人类读书会《乡村振兴战略下传统村落文化旅游设计》中国建筑出版传媒许少辉博士著作

基于Xml方法的Bean的配置-实例化Bean的方法-构造方法

SpringBean的配置详解 Bean的实例化配置 Spring的实例化方法主要由以下两种 构造方法实例化&#xff1a;底层通过构造方法对bean进行实例化 构造方法实例化bean又分为无参方法实例化和有参方法实例化&#xff0c;在Spring中配置的<bean>几乎都是无参构造该方式&#xff…

如何把文件从本地上传云服务器

1、从服务器下载文件到本地&#xff08;如win电脑&#xff09; scp&#xff1a;命令&#xff0c; iss_train0110.33.16.2是服务器用户名&#xff0c;10.33.16.2是服务器ip&#xff0c; :是选择 /mnt/linaro/sample/sample/YOLOv8/cpp/yolov8_bmcv/yolov8_bmcv.soc&#xff1a;服…

AJAX 技术学习笔记(基础)

Asynchronous JavaScript And XML 概念&#xff1a;异步的 JavaScript 和 XML 原生 AJAX 介绍 作用&#xff1a; 和服务器进行数据交换&#xff0c;利用HTML一起代替耦合的JSP动态页面完成异步交互 同步交互和异步交互&#xff1a; 同步交互&#xff1a;客户端向服务器端发…

基于Java新枫之谷游戏攻略设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

JVM内存泄漏分析的demo

本文参考&#xff1a; JVM调优参数、方法、工具以及案例总结 JVM监控和调优常用命令工具总结 - Pickle - 博客园 (cnblogs.com) 面试官问我JVM调优&#xff0c;我忍不住了&#xff01; - Java3y - 博客园 (cnblogs.com) 从实际案例聊聊Java应用的GC优化 (qq.com) JVM调优的…

微分方程应用案例

下表1给出了近两个世纪美国人口统计表&#xff08;单位&#xff1a;百万&#xff09;&#xff0c;建立数学模型并检验&#xff0c;最后用它预报2010年美国的人口。 年 1790 1800 1810 1820 1830 1840 1850 1860 人口 3.9 5.3 7.2 9.6 12.9 17.1 23.2 31.4 年…

Chromedriver 在 Python 中查看源代码的方法

Python 中可以属性来查看需要爬取的网站的源代码。 对应具体的是&#xff1a;chrome.page_source 需要注意的是首先需要导入包 from selenium.webdriver import Chrome 然后进行初始化&#xff1a;chrome Chrome(serviceService(r"C:\Users\yhu\Downloads\chromedrive…

华为智慧搜索,下一片流量蓝海的“入海口”

几年前开始&#xff0c;TMT业界就发出了一类质疑的声音&#xff1a;移动互联网的各个APP彼此割裂&#xff0c;是在“孤岛炼油”。 大量的应用程序和服务互不打通&#xff0c;形成了严重的数据孤岛&#xff0c;用户只能进行站内搜索&#xff0c;很难穿透APP壁垒&#xff0c;进行…

平衡二叉树的定义,插入操作以及插入新结点后的调整规则(ALV树)

1.定义 平衡二叉树( Balanced Binary Tree&#xff09;&#xff0c;简称平衡树&#xff08;AVL树&#xff09;。 1.特点 树上任一结点的左子树和右子树的高度之差不超过1。 结点的平衡因子左子树高-右子树高。 2.平衡二叉树的判定 平衡二叉树结点的平衡因子的值只可能是-1…

全国职业技能大赛云计算--高职组赛题卷①(容器云)

全国职业技能大赛云计算--高职组赛题卷①&#xff08;容器云&#xff09; 第二场次题目&#xff1a;容器云平台部署与运维任务1 Docker CE及私有仓库安装任务&#xff08;5分&#xff09;任务2 基于容器的web应用系统部署任务&#xff08;15分&#xff09;任务3 基于容器的持续…

Mysql详解Explain索引优化最佳实践

目录 1 Explain工具介绍2 explain 两个变种3 explain中的列3.1 id列3.2 select_type列3.3 table列3.4. type列3.5 possible_keys列3.6 key列3.7 key_len列3.8 ref列3.9 rows列3.10 Extra列 4 索引最佳实践4.1.全值匹配4.2.最左前缀法则4.3.不在索引列上做任何操作&#xff08;计…

使用 Feature Flags 实现数据库灰度迁移的监控与可观测性

作者&#xff1a;观测云与胡博 场景描述 很多企业会遇到数据库升级、或数据库迁移的情况&#xff0c;尤其是在自建数据库服务向云数据库服务、自建机房向云机房、旧数据库向新数据库迁移等场景。 然而&#xff0c;我们需要在整个移植过程中保证其稳定性、避免数据遗失、服务宕…

Windows 安装 chromedriver 和 Python 调试

下载 chromedriver 从官方网站上下载 chromedriver 的版本&#xff0c;这个版本需要和你 Chrome 的版本对应上。 下载的地址为&#xff1a;ChromeDriver - WebDriver for Chrome - Downloads 这个地方&#xff0c;将会打开一个新的浏览器界面&#xff0c;Chrome for Testing …

9参数化重采样时频变换,基于MATLAB平台,程序已调通,可直接替换数据进行分析。

参数化重采样时频变换&#xff0c;基于MATLAB平台&#xff0c;程序已调通&#xff0c;可直接替换数据进行分析。 9matlab参数化重采样时频变换 (xiaohongshu.com)