JAVA函数式编程

目录

参考资料

函数式编程的优势

01 lambda表达式

概述

使用案例

例一:

例二:(lambda中体现的是接口中的抽象方法是如何实现的)

例三:(lambda中体现的是接口中的抽象方法是如何实现的)

省略规则

省略的规则如下:

02 Stream流

案例

1、数据准备

2、快速入门

需求

实现

3. IDEA 快速查看 Stream 流程

常用操作

1. 创建流

1、单列集合(List、Set):集合对象.stream()​ 

2、数组([]):Arrays.stream(数组) 或者 Stream.of(数组)​

3.双列集合(Map):转换为单列集合后再创建​( map.entrySet().stream() )

2. 中间操作

filter​

map

flatMap

distinct

sorted

方法一:

方法二:

limit

3. 结尾操作

forEach

count

max&min

collect

toList

toSet

toMap

例子 

 groupingBy

例子

查找与匹配

anyMatch

allMatch

noneMatch

findAny

findFirst

reduce

注意事项

1、惰性求值

2、流是一次性的(One-Time Use)

3、不会影响原数据

例子

03 Optional

概述

Optional 类的一些重要特点和用法:(优雅的方式来处理可能为空的值)

使用

1. 创建对象

ofNullable

of

empty

2. 安全消费值

ifPresent

3. 获取值

get

 4. 安全获取值

orElse

orElseGet

orElseThrow

5. 过滤

filter

6. 判断

isPresent

7. 数据转换

map

例子

04 函数式接口 (可以改为lambda表达式实现的接口)

匿名内部类

05 方法引用

静态方法引用

实例方法引用


参考资料

参考icon-default.png?t=N7T8https://fxmktmrxi6.feishu.cn/wiki/G9Wxw9GlTiqIp2konrqcfbNon3f面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数,它主要关注的是对数据进行了什么操作

函数式编程的优势

1、易于使用并发编程,大数据量下,集合处理效率高:可以使用并行流,自动使用多线程方式处理。
2、代码可读性高
3、消灭嵌套地狱
例如,要查询未成年作家的评分在70分以上的作家的书籍,由于洋流影响所以作家和书籍可能出现重复,需要进行去重。
如果按照原来的写法,需要这么写:

01 lambda表达式

概述

Lambda是IDK8中一个语法糖,他可以对某些匿名内部类(这类匿名内部类是一个接口,并且接口中只有一个抽象方法需要被重写)的写法进行简化。

它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作,即只关注匿名内部类中抽象方法的参数(数据)及函数体(操作)。

核心原则:可推导可省略。参数类型如果可以推导出来,就可以省略。

基本格式

使用案例

例一:

在创建线程并启动时,可以使用匿名内部类的写法:

例二:(lambda中体现的是接口中的抽象方法是如何实现的)

如下方法:

例三:(lambda中体现的是接口中的抽象方法是如何实现的

该接口中虽然不止一个方法,但是只有一个 test()抽象方法必须被重写,其他方法是默认方法不需要被重写,因此,也可以使用 Lambda 表达式实现:

(第一种方式保留了完整的参数列表和方法体)

(下面第二种方式省略参数类型和return关键字)

省略规则

在例二和例三的代码中,给出了两种 Lambda 表达式,一种是单纯省略方法名和接口名,保留完整参数列表和方法体;第二种是省略参数类型,以及return 关键字等。

省略的规则如下:

1、参数类型可以省略

2、方法只有一个参数时,参数列表的括号可以省略

3、方法体只有一句代码时,大括号、return关键字和唯一一句代码的分号可以省略

02 Stream流

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

Stream (Java Platform SE 8 )icon-default.png?t=N7T8https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

Stream提供以下功能:

  • 流不存储元素。它只是通过计算操作的流水线从数据结构, 数组或I / O通道等源中传递元素。
  • 流本质上是功能性的。对流执行的操作不会修改其源。例如, 对从集合中获取的流进行过滤会产生一个新的不带过滤元素的流, 而不是从源集合中删除元素
  • Stream是惰性的, 仅在需要时才评估代码。
  • 在流的生存期内, 流的元素只能访问一次。像Iterator一样, 必须生成新的流以重新访问源中的相同元素。

你可以使用流来过滤, 收集, 打印以及从一种数据结构转换为其他数据结构等。

案例

1、数据准备

@Data​
@NoArgsConstructor​
@AllArgsConstructor​
@EqualsAndHashCode​
public class Author {​
    private Long id;​
    private String name;​
    private Integer age;​
    private String intro;​
    private List<Book> books;​
}
@Data​
@NoArgsConstructor​
@AllArgsConstructor​
@EqualsAndHashCode​
public class Book {​
    private Long id;​
    private String name;​
    private String category;​
    private Integer score;​
    private String intro;​
}
private static List<Author> getAuthors() {​
    Author author1 = new Author(1L, "雷蒙多", 33, "简介1", null);​
    Author author2 = new Author(2L, "亚拉索", 15, "简介2", null);​
    Author author3 = new Author(3L, "易", 14, "简介3", null);​
    Author author4 = new Author(3L, "易", 14, "简介3", null);​
​
    List<Book> books1 = new ArrayList<>();​
    List<Book> books2 = new ArrayList<>();​
    List<Book> books3 = new ArrayList<>();​
​
    books1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲学,爱情", 88, "用一把刀划分了爱恨"));​
    books1.add(new Book(2L, "一个人不能死在同一把刀下", "个人成长,爱情", 99, "讲述如何从失败中明悟真理"));​
​
    books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));​
    books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));​
    books2.add(new Book(4L, "吹或不吹", "爱情,个人传记", 56, "一个哲学家的恋爱观注定很难把他所在的时代理解"));​
​
    books3.add(new Book(5L, "你的剑就是我的剑", "爱情", 56, "无法想象一个武者能对他的伴侣这么的宽容"));​
    books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));​
    books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));​
    author1.setBooks(books1);​
    author2.setBooks(books2);​
    author3.setBooks(books3);​
    author4.setBooks(books3);​
    ​
    return new ArrayList<>(Arrays.asList(author1, author2, author3, author4));​
}

2、快速入门

需求

用 getAuthors() 方法获取到作家的集合,打印出所有年龄小于18的作家的名字,并且要注意去重。

实现

因为List并不是stream对象,为此使用集合对象时需要先将其转换为stream对象,拿到stream流,然后才可以调用stream对象的方法进行过滤处理。这里先使用匿名内部类的方式实现。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()    // 将List转为Stream流对象​
            .distinct()    // 去重,靠的是Author类中的@EqualsAndHashCode注解,如果没有这个注解就需要自己编写这两个方法​
            .filter(new Predicate<Author>() {   // 调用filter对年龄进行过滤,首先使用匿名内部类的方式实现​
                @Override​
                public boolean test(Author author) {​
                    return author.getAge() < 18;​
                }​
            })​
            .forEach(new Consumer<Author>() {   // forEach方法用来遍历剩余的每个元素进行消费,也使用匿名内部类的方式实现​
                @Override​
                public void accept(Author author) {​
                    System.out.println(author.getName());​
                }​
            });​
}

Lambda表达式实现

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    ​
    authors.stream()    // 将List转为Stream流对象​
            .distinct()    // 去重,靠的是Author类中的@EqualsAndHashCode注解,如果没有这个注解就需要自己编写这两个方法​
            .filter(author -> author.getAge() < 18)   // 调用filter对年龄进行过滤​
            .forEach(author -> System.out.println(author.getName()));  // forEach方法用来遍历剩余的每个元素进行消费​
}

3. IDEA 快速查看 Stream 流程

1、首先将断点打在使用流的地方:

2、然后使用DEBUG运行程序,在DEBUG窗口查看【Current Stream Chain】

3、逐个方法对应的Tab就可以查看流的处理流程

常用操作

1. 创建流

Java 中有两类集合:

一类是单列集合,父接口为Collection

一类是双列集合,父接口为Map

1、单列集合(List、Set):集合对象.stream()​ 
List<Author> authors = getAuthors();​
Stream<Author> stream = authors.stream();​
2、数组([]):Arrays.stream(数组) 或者 Stream.of(数组)​
Integer[] arr = {1, 2, 3, 4, 5};​
Stream<Integer> stream1 = Arrays.stream(arr);​
Stream<Integer> stream2 = Stream.of(arr);​
3.双列集合(Map):转换为单列集合后再创建​( map.entrySet().stream() )
​Map<String, Integer> map = new HashMap<>();​
map.put("xiaoxin", 19);​
map.put("ameng", 17);​
map.put("wukong", 16);

Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

2. 中间操作

filter​

filter(Predicate<? super T> predicate):对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
例如:打印所有姓名长度大于1的作家的姓名。

方法一:
public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .filter(author -> author.getName().length() > 1)​
            .forEach(author -> System.out.println(author.getName()));​
}

方法二:
public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .filter(author -> author.getName().length() > 1)​
            //author类型转换成name
            .map(author -> author.getName())
            .forEach(name -> System.out.println(name));​
}
map

map(Function<? super T,? extends R> mapper):对流中的元素进行计算或类型转换。​
例如:打印所有作家的姓名。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .map(author -> author.getName())​
            .forEach(name -> System.out.println(name));​
}

当然,其实这个需求单独用forEach也可以实现。但是经过map操作之后,流中的数据类型会改变,一定程度上减轻了数据量级

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .forEach(author -> System.out.println(author.getName()));​
}

除了使用map(),也可以直接类型转换

 使用mapStruct进行对象转换

flatMap

flatMap(Function<? super T,? extends Stream<? extends R>> mapper):map 只能把一个对象转换成另一个对象来作为流中的元素,而fatMap可以把一个对象转换成多个对象作为流中的元素

例如,打印所有书籍的名字,并对重复的元素进行去重

刚开始可能会想到使用map()方法,取出author中的books列表,然后进行去重:

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .map(author -> author.getBooks())​
            .distinct()​
            .forEach(System.out::println);​
}

 但是,map()返回的是author中的List<Book>对象,使用distinct进行去重时,流中的元素为List<Book>,而不是Book,因此去重的结果并不是我们预期的。​

这时,就可以使用flatMap(),将流中列表类型的元素转换为新的流,新流中包含的就是列表中的元素,再使用distinct去重时,去重的对象就是Book对象了。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    // 匿名内部类形式​
    authors.stream()​
            .flatMap(new Function<Author, Stream<?>>() {​
                @Override​
                public Stream<?> apply(Author author) {​
                    return author.getBooks().stream();​
                }​
            })​
            .distinct()​
            .forEach(System.out::println);​
​
    // Lambda​
    authors.stream()​
            .flatMap(author -> author.getBooks().stream())​
            .distinct()​
            .forEach(System.out.println(book.getName()));​
}

 例二:打印现有书籍的所有分类,要求对分类进行去重,且不能出现多个分类(爱情,文艺)的格式

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .flatMap(author -> author.getBooks().stream())​
            .distinct()​
            .flatMap(book -> Arrays.stream(book.getCategory().split(",")))​
            .distinct()​
            .forEach(category -> System.out.println(category));​  //category是一个数组
}

通过Arrays.stream(book.getCategory().split(","))转变成如下数组 

 

distinct

distinct():可以去除流中的重复元素

🔣注意:distinct方法是依赖类中的equals方法来判断是否是相同对象的,所以如果要对某个类型的对象进行去重,这个类中必须重写equals() 和 hashCode() 方法。

(不知道使用注解@EqualsAndHashCode是否一定行得通)

例如:打印所有作家的姓名,并且要求其中不能有重复元素。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .distinct()​
            .forEach(author -> System.out.println(author.getName()));​
}
sorted
  • sorted():对数据流中的元素按自然顺序排序
  • sorted(Comparator<? super T> comparator):根据提供的Comparator对流中的元素排序

例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。​

方法一:

就必须让 Author 继承 Comparable 接口,并实现 compareTo() 方法定义排序规则

@Data​
@NoArgsConstructor​
@AllArgsConstructor​
@EqualsAndHashCode​
public class Author implements Comparable<Author> {​
    private Long id;​
    private String name;​
    private Integer age;​
    private String intro;​
    private List<Book> books;​
​
    @Override​
    public int compareTo(Author o) {​
        // 升序排序​
        // return this.getAge() - o.getAge();​
        // 降序排序​
        return o.getAge() - this.getAge();​
    }​
}

然后在main函数中调用:

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .distinct()​
            .sorted()​
            .forEach(author -> System.out.println(author.getAge()));​
}
方法二:

保持Author不动,不让 Author 实现 Comparable 接口,而是在 sorted() 中定义排序规则

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .distinct()​
            .sorted((o1, o2) -> o2.getAge() - o1.getAge()) // 降序排列​
            .forEach(author -> System.out.println(author.getAge()));​
}
limit

limit(long maxSize):设置流的最大长度(元素数量),超出的部分将被舍弃。

例如,对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .distinct()​
            .sorted((o1, o2) -> o2.getAge() - o1.getAge()) // 降序排列​
            .limit(2)​   //只返回年龄最大的两个元素
            .forEach(author -> System.out.println(author.getAge()));​
}

3. 结尾操作

必须要有结尾操作,中间操作才会被调用到,进而生效,否则中间操作不会被执行

forEach

forEach(Consumer<? super T> action):对流中的元素进行遍历操作,可以通过传入的参数指定对遍历到的元素进行什么具体操作

count

count():获取当前流中元素的个数

max&min
  • max(Comparator<? super T> comparator):通过传入的Comparator对元素进行比较,得到最大值;​
  • min(Comparator<? super T> comparator):通过传入的Comparator对元素进行比较,得到最小值。

Comparator的实现方法和 sorted() 中一致。

例如,获取这些作家的所出书籍的最高分并打印(最低分同理,改为min即可)。

max和min返回的是一个Optional对象,需要通过 get() 获取到原始对象才可以使用。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    Optional<Book> max = authors.stream()​
            .flatMap(author -> author.getBooks().stream())​
            .distinct()​
            .max((o1, o2) -> o1.getScore() - o2.getScore());​
    if (max.isPresent()) {​
        Book book = max.get();​
        System.out.println(book.getScore());​
    }​
    // 上面对max进行判断与输出的代码,也可以简化为Lambda表达式如下​
    max.ifPresent(book -> System.out.println(book.getScore()));​
​
    // 因为这里最后只需要输出分数,因此当我们取到 Stream<Book> 流对象后​
    // 可以将流转换为 Stream<Integer> 流,只包含分数就可以,降低数据量级​
    Optional<Integer> maxed = authors.stream()​
            .flatMap(author -> author.getBooks().stream())​
            .distinct()​
            .map(book -> book.getScore())​
            .max((o1, o2) -> o1 - o2);​
    maxed.ifPresent(score -> System.out.println(score));​
}
collect

collect(Collector<? super T,A,R> collector):将当前流转换为一个集合。

在某些场景下,集合通过流处理之后,需要导出为一个新的集合进行使用,这时候就需要使用 collect() 方法。

转成list集合:.collect(Collectors.toList())

转成String(逗号隔开):Collectors.joining(",")

转成set集合:.collect(Collectors.toSet())

........

toList

获取一个存放所有作者名字的List集合。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    List<String> nameList = authors.stream()​
            .map(author -> author.getName())​
            .collect(Collectors.toList());​
​
    System.out.println(nameList);​
}
toSet

获取一个所有书名的Set集合

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    Set<String> bookSet = authors.stream()​
            .flatMap(author -> author.getBooks().stream())​
            .map(book -> book.getName())​
            .collect(Collectors.toSet());​
    System.out.println(bookSet);​
}
toMap

获取一个Map集合,map的key为作者名,value为List<Book>

由于 toMap() 的匿名内部类比较复杂,先给出匿名内部类的方式,便于理解原理。

//内部类实现

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    Map<String, List<Book>> books = authors.stream()​
            .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();​
                }​
            }, new BinaryOperator<List<Book>>() {​
                @Override​
                public List<Book> apply(List<Book> books1, List<Book> books2) {​
                    return books2;​
                }​
            }));​
    System.out.println(books);​
}

Lambda表达式简化

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    Map<String, List<Book>> books = authors.stream()​
            .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks(), (books1, books2) -> books2));​   //name重复时保留后面的books
    System.out.println(books);​
}
例子 
Map<Long, AurrCategory> categoryMap = 
                categoryDao
                .selectList(Wrappers.emptyWrapper())
                .stream()
                .collect(Collectors.toMap(AurrCategory::getId, Function.identity()));

Function.identity():将流中获取出来的AurrCategory对象本身作为map的值,AurrCategory的id作为键

但是如果流中存在具有相同 id 的多个 AurrCategory 对象,使用这个版本的 toMap 可能会导致 IllegalStateException,因为默认情况下 toMap 不允许有重复的键,所以需要传入第三个参数Collectors.toMap(AurrCategory::getId, Function.identity(), (identities1, identities2) -> identities2,id重复时取后一个AurrCategory 对象。

 groupingBy

Collectors.groupingBy 是一个收集器(collector),它用于将流(Stream)中的元素根据某个属性进行分组,并将结果收集到一个 Map 中,map的键是流中元素的属性,值是拥有相同属性的对象的list集合

例子

groupedByKolId 将包含从 kolCategories 列表中收集到的 AurrKolCategory 对象的分组映射。每个键(kolId)对应一个列表,列表中包含了所有 kolId 相同的 AurrKolCategory 对象。

如果流中的 AurrKolCategory 对象具有不同的 kolId 值,那么它们将被分配到不同的列表中,每个列表对应一个唯一的 kolId。如果多个对象具有相同的 kolId,则它们会被放在同一个列表中。

List<AurrKolCategory> kolCategories = ...; // 假设这是一个AurrKolCategory对象的列表

Map<Integer, List<AurrKolCategory>> groupedByKolId = kolCategories.stream()
    .collect(Collectors.groupingBy(AurrKolCategory::getKolId));

查找与匹配

anyMatch

anyMatch(Predicate<? super T> predicate):判断流内是否有任意符合匹配条件的元素,结果为boolean类型。只要有一个元素满足条件就返回true

例如,判断是否有年龄在29岁以上的作家

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    boolean b = authors.stream()​
            .anyMatch(author -> author.getAge() > 29);​
    System.out.println(b);  // true​
}
allMatch

allMatch(Predicate<? super T> predicate):与anyMatch()类似,判断流内是否所有元素都满足匹配条件,结果为boolean类型。当所有元素都满足条件时才返回true。

noneMatch

noneMatch(Predicate<? super T> predicate):与上面两个类似,判断流内是否所有元素都不满足匹配条件,结果为boolean类型。当所有元素都不满足条件时才返回true

findAny

findAny():获取流中的任意一个元素,返回的是一个 Optional 对象。该方法没有办法保证获取的一定是流中的第一个元素,因此用的更多的是下面的 findFirst() 方法

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    // 查找年龄大于18的作家​
    Optional<Author> optionalAuthor = authors.stream()​
            .filter(author -> author.getAge() > 18)​
            .findAny();​
    ​
    // 如果存在就输出他的名字​
    optionalAuthor.ifPresent(author -> System.out.println(author));​
}
findFirst

findFirst():获取流中的第一个元素,返回的是一个 Optional 对象。与findAny()的用法一样。

findAny() 并不像他的字面意思一样,可以查找一个满足条件的元素,他只是在最后处理完的流中随机获取一个元素并返回。因此,如果要做筛选的话,还是要依赖 filter() 方法。​

那么 findAny() 和 findFirst() 存在的意义是什么呢?​
因为流处理结束后,最终的流是可能为空的,比如说下面的代码中,如果作家年龄都小于18,那么最后的流将会是空的,如果直接使用很可能会报空指针异常。因此,findAny() 和 findFirst() 方法主要是用来避免空指针异常的。​
当调用 findAny() 和 findFirst() 方法时,返回的是一个 Optional 对象,Optional 对象的 ifPresent() 方法便可以对流元素对象进行判空,不为空才执行相应逻辑

reduce


reduce详细介绍icon-default.png?t=N7T8https://fxmktmrxi6.feishu.cn/wiki/AynIw0Kpqice3hkXH5Qc8YfIn5f#part-KhUTdeRJ3odYRxxOGdNcmu0KnHc

注意事项

1、惰性求值

  • 在对流进行操作时,操作不会立即执行,而是等到需要结果时才进行计算,即没有结尾操作,中间操作是不会执行的。​
  • 这种延迟计算的特性可以提高性能,因为它只计算流中实际需要的元素,而不是对整个流进行操作。

2、流是一次性的(One-Time Use)

  • Stream 流是一次性的,一旦对流进行了结尾操作(如收集结果、循环遍历等),流就会被消耗掉,无法再次使用。​
  • 如果需要对同一组数据进行多个操作,可以创建一个新的流来进行操作。
public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    Stream<Author> stream = authors.stream();​
    // 第一次对流进行处理,并执行了结尾操作​
    stream.forEach(System.out::println);​
    // 流终结后再次使用流,报错!!!​
    stream.forEach(System.out::println);​
}

3、不会影响原数据

  • Stream 流的操作不会直接修改原始数据源中的元素,也不会影响原始数据源的结构。​
  • 所有的流操作都是基于数据源的副本或视图进行的,保持了原始数据的不变性。​
  • 除非在流中调用了流中元素对象的setter类似的方法,例如:
public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
​
    authors.stream()​
            .map(new Function<Author, Object>() {​
                @Override​
                public Object apply(Author author) {​
                    author.setAge(author.getAge() + 10);​
                    return author;​
                }​
            }).forEach(System.out::println);​
}

例子

categoryLevelMap.getOrDefault(2, new ArrayList<>()).stream().map(categoryMap::get).map(AurrCategory::getName).collect(Collectors.joining(","));

03 Optional

概述

在从数据库查询数据或者执行一些其他操作的时候,查询出来的结果可能是为空的,返回的是 null,如果不对返回值进行判断,直接对 null 进行操作,则会报空指针异常

传统的方式是使用 if 条件判断来判断对象是否为空,并执行相应的处理逻辑:

if (authors != null) {​
    System.out.println(author.getName());​
}

如果存在大量的非空判断,代码就会显得十分臃肿,可读性也会降低。因此 Java 8 引入了 Optional 类,用于处理可能为空的值的容器类。它提供了一种优雅的方式来处理可能存在或不存在的值,避免了空指针异常的发生

Optional官方文档 (Java Platform SE 8 )icon-default.png?t=N7T8https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

Optional 类的一些重要特点和用法:(优雅的方式来处理可能为空的值)

容器类:Optional 是一个容器类,可以包含一个非空的值或者为空。它通过 of()、ofNullable() 和 empty() 等静态方法来创建 Optional 对象。

避免空指针异常:Optional 提供了一种安全的方式来处理可能为空的值。通过使用 Optional,我们可以避免显式地进行空值检查,从而减少了空指针异常的风险

方法链操作:Optional 提供了一系列方法来对包含的值进行操作,如 map()、flatMap()、filter() 等。这些方法可以在值存在的情况下进行操作,并返回一个新的 Optional 对象。

isPresent() 和 ifPresent():Optional 提供了 isPresent() 方法来检查值是否存在,以及 ifPresent() 方法来在值存在时执行特定的操作

默认值:Optional 提供了 orElse()、orElseGet() 和 orElseThrow() 等方法来获取值或者提供默认值,以应对值为空的情况。

空值处理:Optional 提供了 ifPresentOrElse() 方法来在值存在或者为空时执行不同的操作。

使用

1. 创建对象

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

ofNullable

一般使用 Optional 的静态方法 ofNullable(ofNullable(T value))来把数据封装成一个 Optional 对象。如果传入的参数为null,将会返回一个空的 Optional 对象。

public static void main(String[] args) {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    optionalAuthor.ifPresent(System.out::println);​
}

这样处理感觉和使用 if 做判空好像差不多,在获取数据后都要加一行代码来处理。但是如果改造下 getAuthor 方法,让其的返回值就是封装好的 Optional 的话,我们在使用时就会方便很多。​

在实际开发中,数据很多是从数据库获取的,MyBatis 从 3.5 版本开始也已经支持 Optional 了,可以直接把 Dao 层方法的返回值类型定义成 Optional 类型,封装的过程也不需要自己操作,MyBatis 会自己把数据封装成 Optional 对象返回。

of

of(T value):of 方法只能处理非空的对象。如果确定一个对象不是空的,则可以使用 of 方法来把数据封装成 Optional 对象。

empty

empty():返回一个空的 Optional 对象。如果明确知道某个对象为 null,那么可以直接使用 empty() 封装

ofNullable() 本质也是调用 of() 和 empty() 实现的

public static <T> Optional<T> ofNullable(T value) {​
    return value == null ? empty() : of(value);​
}

2. 安全消费值

ifPresent

ifPresent(Consumer<? super T> consumer):如果 Optional 对象中的对象不为 null,那么就会调用 consumer 中定义的逻辑处理,如果为 null,则不执行任何操作

获取到一个 Optional 之后,可以使用其 ifPresent() 方法来消费其中的值。这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用起来就更加安全了。

以下写法就避免了空指针异常:

public static void main(String[] args) {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    optionalAuthor.ifPresent( author -> System.out.println(author.getName()) );​
}

3. 获取值

get

get():如果 Optional 中的值不为 null,则使用.get()返回该对象,如果为空,则会抛出 NoSuchElementException 异常。

如果想获取值自己进行处理,可以使用 get() 方法获取,但是因为空的 Optional 调用 get() 会抛异常,因此还需要使用 isPresent() 方法判断 Optional 中值是否存在,如果存在返回 true,否则返回 false。

public static void main(String[] args) {​
    Author author = getAuthor();​
    //方法一 (和直接对对象进行判空差不多,因此,建议直接使用 ifPresent 方法)
    Optional<Author> optionalAuthor = Optional.of(author);​
    if (optionalAuthor.isPresent()) {​
        //通过get()获取出对应的对象进行操作
        Author author2 = optionalAuthor.get();​
        System.out.println( author2.getName() );​
    }​
    //方法二
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    optionalAuthor.ifPresent(author -> author.getName());
}

项目中: 

Optional<BaseKol> optional = kolRepository.find(parameter);
optional.ifPresent(baseKol -> resultMap.put(baseKol.getId(), baseKol));

 “消费者”是指 Consumer<T> 接口的实现,它接受一个类型为 T 的参数。在 Optional 类的上下文中,T 就是 Optional 泛型中指定的类型。

具体来说,当你看到 Optional.ifPresent(Consumer<T> action) 方法时,这里的 Consumer<T> 中的 T 与 Optional<T> 中的 T 是相同的类型。这意味着,如果 Optional 对象包含一个值,那么这个值将会作为参数传递给 Consumer

 4. 安全获取值

直接使用 get() 方法获取值还是可能存在异常问题,因此,如果期望安全地获取值,可以使用 Optional 的下列方法。

orElse,orElseGet,orElseThrow()三者经常同Optional.ofNullable() 一起使用

orElse

orElse() : 当值为null时, 返回的是该方法的参数 。但值不为null时, 返回值本身

public T orElse(T other) {
        return value != null ? value : other;
    }
//如果ofNullable()不等于null, 则返回scRespDTO.getMsgBody().getSuccess()的值, 
//如果为null, 则返回false (即返回orElse()的参数)
Optional.ofNullable(scRespDTO.getMsgBody().getSuccess()).orElse(false)
orElseGet

orElseGet(Supplier<? extends T> other):如果存在则返回值,否则返回设置好的默认值

public static void main(String[] args) {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    Author author2 = optionalAuthor.orElseGet(new Supplier<Author>() {​
        @Override​
        public Author get() {​
            return new Author();​
        }​
    });​
}

 改用 Lambda 表达式

public static void main(String[] args) {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    Author author2 = optionalAuthor.orElseGet(() -> new Author());​
}

【Tip: orElse() 和 orElseGet() 方法没有区别嘛?】

当值为null时,两者都是返回这两方法的参数。但是区别就是,orElseGet() 并不是直接返回参数本身,而是返回参数的get()值,且该参数对象必须实现 supplier 接口(该接口为函数式接口)。这样使得orElseGet()更加灵活。

orElseThrow

orElseThrow(Supplier<? extends X> exceptionSupplier):如果存在则返回值,否则抛出 Supplier 指定的异常

该方法在 Spring 框架中用的比较多,可以用 Spring 来做统一的异常捕获。

public static void main(String[] args) throws Throwable {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.of(author);​
    Author author2 = optionalAuthor.orElseThrow(new Supplier<Throwable>() {​
        @Override​
        public Throwable get() {​
            return new RuntimeException("记录不存在");​
        }​
    });​
}

改为 Lambda 表达式:

public static void main(String[] args) throws Throwable {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.of(author);​
    Author author2 = optionalAuthor.orElseThrow(() -> new RuntimeException("记录不存在"));​
}

5. 过滤

filter

filter(Predicate<? super T> predicate):如果存在值并且值满足设定的条件,返回满足条件的元素组成的 Optional,否则返回空的 Optional

public static void main(String[] args) throws Throwable {​
    Author author = getAuthor();​
    Optional<Author> optionalAuthor = Optional.ofNullable(author);​
    optionalAuthor.filter(author1 -> author1.getAge() > 18).ifPresent(author1 -> System.out.println(author1.getName()));​
}

6. 判断

isPresent

isPresent():对Optional对象中是否存在值进行判断,如果存在返回true,否则返回false。​
一般都是直接使用 ifPresent(),单独使用 isPresent() 和使用 if(xx != null) 差不多。

7. 数据转换

map

map(Function<? super T,? extends U> mapper):与 Stream 中的 map() 方法类似,用于数据转换或计算。

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

public static void main(String[] args) throws Throwable {​
    Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());​
    // 使用map将 Optional<Author> 转换为 Optional<List<Book>>​
    Optional<List<Book>> optionalBooks = optionalAuthor.map(new Function<Author, List<Book>>() {​
        @Override​
        public List<Book> apply(Author author) {​
            return author.getBooks();​
        }​
    });​
    // 如果Optional<List<Book>> 不为空,取出数据进行消费。​
    optionalBooks.ifPresent(new Consumer<List<Book>>() {​
        @Override​
        public void accept(List<Book> books) {​
            // 遍历books​
            books.forEach(new Consumer<Book>() {​
                @Override​
                public void accept(Book book) {​
                    System.out.println(book.getName());​
                }​
            });​
        }​
    });​
}

改为 Lambda 表达式:

public static void main(String[] args) throws Throwable {​
    Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());​
    Optional<List<Book>> optionalBooks = optionalAuthor.map(author -> author.getBooks());​
    optionalBooks.ifPresent(books -> books.forEach(book -> System.out.println(book.getName())));​
}

例子

Optional.ofNullable(tuneInfo.getDuration()).map(Double::longValue).orElse(null)

04 函数式接口 (可以改为lambda表达式实现的接口)

函数式接口(Functional Interface)是指 只包含一个抽象方法 的接口。Java 8 引入了函数式接口的概念,以支持函数式编程的特性。

JDK 内置的函数式接口都加上了 @FunctionalInterface 注解,但是并非加这个注解的才是函数式接口,只要满足“只包含一个抽象方法”,就是函数式接口。

以下是函数式接口的一些特点和用途:

单一抽象方法:函数式接口只能包含一个抽象方法,可以有默认方法和静态方法,但只能有一个抽象方法。这个抽象方法定义了接口的核心功能。

Lambda 表达式:函数式接口可以使用 Lambda 表达式来创建接口的实例。Lambda 表达式提供了一种简洁的方式来定义函数式接口的实现。

方法引用:函数式接口可以使用方法引用来引用已有的方法作为接口的实现。方法引用提供了一种更简洁的方式来表示函数式接口的实现。

Java 8 Stream API:Java 8 的 Stream API 中广泛使用了函数式接口。Stream API 提供了一种流式操作的方式,可以对集合进行过滤、映射、排序等操作。

匿名内部类
public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()​
            .filter(new Predicate<Author>() {​
                @Override​
                public boolean test(Author author) {​
                    return author.getAge() > 17;​
                }​
            }.and(new Predicate<Author>() {​
                @Override​
                public boolean test(Author author) {​
                    return author.getName().length() > 1;​
                }​
            }))​
            .forEach(System.out::println);​
}

 改用Lambda表达式

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()​
            .filter(((Predicate<Author>) author -> author.getAge() > 17).and(author -> author.getName().length() > 1))​
            .forEach(System.out::println);​
}

05 方法引用

方法引用(Method Reference)是 Java 8 引入的一种语法糖,用于简化 Lambda 表达式的写法。它提供了一种更简洁的方式来引用已有的方法作为函数式接口的实现。

方法引用可以看作是 Lambda 表达式的一种特殊形式,如果方法体中只有一个方法的调用的话,它可以直接引用已有的方法,而不需要编写完整的 Lambda 表达式,进而进一步简化编码。

基本格式如下

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

方法引用的使用取决于函数式接口的抽象方法的参数和返回类型。以下是方法引用的几种形式:

静态方法引用:ClassName::staticMethodName,例如 Integer::parseInt

实例方法引用:instance::instanceMethodName,例如 String::length

对象方法引用:ClassName::instanceMethodName,例如 System.out::println

构造方法引用:ClassName::new,例如 ArrayList::new

静态方法引用

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

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()​
            .map(author -> author.getName())​
            .map(name -> String.valueOf(name))​
            .forEach(name -> System.out.println(name));​
}

如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()​
            .map(author -> author.getName())  // 也可以使用方法引用,但是这里主要是介绍静态方法引用​
            .map(String::valueOf)​
            .forEach(System.out::println);​
}

实例方法引用

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

public static void main(String[] args) {​
    List<Author> authors = getAuthors();​
    authors.stream()​
            .map(Author::getName)​  ///
            .map(String::valueOf)​
            .forEach(System.out::println);​
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值