函数式编程(Lambda、Stream流、Optional等)

打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

# 声明

文档泉源:Github@shuhongfan
源文档:B站UP主:三更草堂
# 函数式编程-Stream流

# 概述

# 为什么学?

基操,否则看不懂别人写的优雅代码
简化代码,不想看到有些恶心代码
大数据下处理聚集效率高
  1. // 【恶心级代码】查询未成年作家的评分在70以上的书籍 由于洋流影响所以作家和书籍可能出现重复,需要进行去重
  2. List<Book> bookList = new ArrayList<>();
  3. Set<Book> uniqueBookValues = new HashSet<>();
  4. Set<Author> uniqueAuthorValues = new HashSet<>();
  5. for (Author author : authors) {
  6.     if (uniqueAuthorValues.add(author)) {
  7.         if (author.getAge() < 18) {
  8.             List<Book> books = author.getBooks();
  9.             for (Book book : books) {
  10.                 if (book.getScore() > 70) {
  11.                     if (uniqueBookValues.add(book)) {
  12.                         bookList.add(book);
  13.                     }
  14.                 }
  15.             }
  16.         }
  17.     }
  18. }
  19. System.out.println(bookList);
复制代码
lambda表达式简化:
  1. List<Book> collect = authors.stream()
  2.     .distinct()
  3.     .filter(author -> author.getAge() < 18)
  4.     .map(author -> author.getBooks())
  5.     .flatMap(Collection::stream)
  6.     .filter(book -> book.getScore() > 70)
  7.     .distinct()
  8.     .collect(Collectors.toList());
  9. System.out.println(collect);
复制代码
# 函数式编程思想

# 概念

面向对象思想需要关注用什么对象完成什么事故。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。
优点:

  • 代码简便,开发快速
  • 靠近天然语言,易于明白
  • 易于"并发编程"
# Lambda表达式

# 概述

Lambda是JDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个紧张体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
# 焦点原则

可推导可省略
# 基本格式
  1. (参数列表)->{代码}
复制代码
# 例一

我们在创建线程并启动时可以使用匿名内部类的写法:
  1. new Thread(new Runnable() {
  2.     @Override
  3.     public void run() {
  4.         System.out.println("你知道吗 我比你想象的 更想在你身边");
  5.     }
  6. }).start();
复制代码
可以使用Lambda的格式对其进行修改。修改后如下:
  1. new Thread(()->{
  2.     System.out.println("你知道吗 我比你想象的 更想在你身边");
  3. }).start();
复制代码
# 例二:

现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。
  1. public static int calculateNum(IntBinaryOperator operator){
  2.     int a = 10;
  3.     int b = 20;
  4.     return operator.applyAsInt(a, b);
  5. }
  6. public static void main(String[] args) {
  7.     int i = calculateNum(new IntBinaryOperator() {
  8.         @Override
  9.         public int applyAsInt(int left, int right) {
  10.             return left + right;
  11.         }
  12.     });
  13.     System.out.println(i);
  14. }
复制代码
Lambda写法:
  1. public static void main(String[] args) {
  2.     int i = calculateNum((int left, int right)->{
  3.         return left + right;
  4.     });
  5.     System.out.println(i);
  6. }
复制代码
# 例三

现有方法定义如下,其中IntPredicate是一个接口。先使用匿名内部类的写法调用该方法。
  1. public static void printNum(IntPredicate predicate){
  2.     int[] arr = {1,2,3,4,5,6,7,8,9,10};
  3.     for (int i : arr) {
  4.         if(predicate.test(i)){
  5.             System.out.println(i);
  6.         }
  7.     }
  8. }
  9. public static void main(String[] args) {
  10.     printNum(new IntPredicate() {
  11.         @Override
  12.         public boolean test(int value) {
  13.             return value%2==0;
  14.         }
  15.     });
  16. }
复制代码
Lambda写法:
  1. public static void main(String[] args) {
  2.     printNum((int value)-> {
  3.         return value%2==0;
  4.     });
  5. }
  6. public static void printNum(IntPredicate predicate){
  7.     int[] arr = {1,2,3,4,5,6,7,8,9,10};
  8.     for (int i : arr) {
  9.         if(predicate.test(i)){
  10.             System.out.println(i);
  11.         }
  12.     }
  13. }
复制代码
# 例四

现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
  1. public static <R> R typeConver(Function<String,R> function){
  2.     String str = "1235";
  3.     R result = function.apply(str);
  4.     return result;
  5. }
  6. public static void main(String[] args) {
  7.     Integer result = typeConver(new Function<String, Integer>() {
  8.         @Override
  9.         public Integer apply(String s) {
  10.             return Integer.valueOf(s);
  11.         }
  12.     });
  13.     System.out.println(result);
  14. }
复制代码
Lambda写法:
  1. Integer result = typeConver((String s)->{
  2.    return Integer.valueOf(s);
  3. });
  4. System.out.println(result);
复制代码
# 例五

现有方法定义如下,其中IntConsumer是一个接口。先使用匿名内部类的写法调用该方法。
  1. public static void foreachArr(IntConsumer consumer){
  2.     int[] arr = {1,2,3,4,5,6,7,8,9,10};
  3.     for (int i : arr) {
  4.         consumer.accept(i);
  5.     }
  6. }
  7. public static void main(String[] args) {
  8.     foreachArr(new IntConsumer() {
  9.         @Override
  10.         public void accept(int value) {
  11.             System.out.println(value);
  12.         }
  13.     });
  14. }
复制代码
Lambda写法:
  1. public static void main(String[] args) {
  2.      foreachArr((int value)->{
  3.          System.out.println(value);
  4.      });
  5. }
复制代码
# 省略规则


  • 参数范例可以省略
  • 方法体只有一句代码时大括号return和唯一 一句代码的分号可以省略
  • 方法只有一个参数时小括号可以省略
  • 以上这些规则都记不住也可以省略不记
# Stream流

# 概述

Java8的Stream使用的是函数式编程模式,犹如它的名字一样,它可以被用来对聚集或数组进行链状流式的操作。可以更方便的让我们对聚集或数组操作。
# 案例数据准备
  1. <dependencies>
  2.      <dependency>
  3.          <groupId>org.projectlombok</groupId>
  4.          <artifactId>lombok</artifactId>
  5.          <version>1.18.16</version>
  6.      </dependency>
  7. </dependencies>
复制代码
  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @EqualsAndHashCode//用于后期的去重使用
  5. public class Author {
  6.     //id
  7.     private Long id;
  8.     //姓名
  9.     private String name;
  10.     //年龄
  11.     private Integer age;
  12.     //简介
  13.     private String intro;
  14.     //作品
  15.     private List<Book> books;
  16. }
复制代码
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @EqualsAndHashCode        // 用于后期的去重使用
  5. public class Book {
  6.     //id
  7.     private Long id;
  8.     //书名
  9.     private String name;
  10.     //分类
  11.     private String category;
  12.     //评分
  13.     private Integer score;
  14.     //简介
  15.     private String intro;
  16. }
复制代码
  1. private static List<Author> getAuthors() {
  2.     // 数据初始化
  3.     Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
  4.     Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
  5.     Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
  6.     Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
  7.     // 书籍列表
  8.     List<Book> books1 = new ArrayList<>();
  9.     List<Book> books2 = new ArrayList<>();
  10.     List<Book> books3 = new ArrayList<>();
  11.     books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
  12.     books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
  13.     books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
  14.     books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
  15.     books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
  16.     books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
  17.     books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
  18.     books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
  19.     author.setBooks(books1);
  20.     author2.setBooks(books2);
  21.     author3.setBooks(books3);
  22.     author4.setBooks(books3);
  23.     List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
  24.     return authorList;
  25. }
复制代码
# 快速入门

# 需求

我们可以调用getAuthors方法获取到作家的聚集。现在需要打印所有年龄小于18的作家的名字,并且要注意去重。
# 实现
  1. // 打印所有年龄小于18的作家的名字,并且要注意去重
  2. List<Author> authors = getAuthors();
  3. authors.stream()//把集合转换成流
  4.         .distinct()//先去除重复的作家
  5.         .filter(author -> author.getAge()<18)//筛选年龄小于18的
  6.         .forEach(author -> System.out.println(author.getName()));//遍历打印名字
复制代码
# Stream进行Debug


Sream视图:

# 常用操作

# 创建流

单列聚集: 聚集对象.stream()
  1. List<Author> authors = getAuthors();
  2. Stream<Author> stream = authors.stream();
复制代码
数组:Arrays.stream(数组) 或者使用Stream.of来创建
  1. Integer[] arr = {1,2,3,4,5};
  2. Stream<Integer> stream = Arrays.stream(arr);
  3. Stream<Integer> stream2 = Stream.of(arr);
复制代码
双列聚集:转换成单列聚集后再创建
  1. Map<String,Integer> map = new HashMap<>();
  2. map.put("蜡笔小新",19);
  3. map.put("黑子",17);
  4. map.put("日向翔阳",16);
  5. Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
复制代码
# 中间操作

# filter

可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
比方:打印所有姓名长度大于1的作家的姓名
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.         .filter(author -> author.getName().length()>1)
  4.         .forEach(author -> System.out.println(author.getName()));
复制代码
# map

可以把对流中的元素进行计算或范例转换。
比方:打印所有作家的姓名
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.          .map(author -> author.getName())
  4.          .forEach(name->System.out.println(name));
复制代码
  1. //        打印所有作家的姓名
  2. List<Author> authors = getAuthors();
  3. //        authors.stream()
  4. //                .map(author -> author.getName())
  5. //                .forEach(s -> System.out.println(s));
  6. authors.stream()
  7.         .map(author -> author.getAge())
  8.         .map(age->age+10)
  9.         .forEach(age-> System.out.println(age));
复制代码
# distinct

可以去除流中的重复元素。
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。以是需要注意重写equals方法。
比方:打印所有作家的姓名,并且要求其中不能有重复元素。
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.         .distinct()
  4.         .forEach(author -> System.out.println(author.getName()));
复制代码
# sorted

可以对流中的元素进行排序。
注意:假如调用空参的sorted()方法,需要流中的元素实现了Comparable接口。
比方:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
  1. List<Author> authors = getAuthors();
  2. // 对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
  3. authors.stream()
  4.         .distinct()
  5.         .sorted()        // Author 实现了 Comparable接口,并重写了 compareTo 比较规则
  6.         .forEach(author -> System.out.println(author.getAge()));
复制代码
  1. List<Author> authors = getAuthors();
  2. // 对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
  3. authors.stream()
  4.          .distinct()
  5.          .sorted((o1, o2) -> o2.getAge()-o1.getAge())        // 自定义比较规则
  6.          .forEach(author -> System.out.println(author.getAge()));
复制代码
# limit

可以设置流的最大长度,超出的部门将被抛弃。
比方:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.         .distinct()
  4.         .sorted()
  5.         .limit(2)
  6.         .forEach(author -> System.out.println(author.getName()));
复制代码
# skip

跳过流中的前n个元素,返回剩下的元素
比方:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
  1. // 打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.         .distinct()
  5.         .sorted()
  6.         .skip(1)
  7.         .forEach(author -> System.out.println(author.getName()));
复制代码
# flatMap

map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
例一:打印所有书籍的名字。要求对重复的元素进行去重。
  1. // 打印所有书籍的名字。要求对重复的元素进行去重。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.         .flatMap(author -> author.getBooks().stream())
  5.         .distinct()
  6.         .forEach(book -> System.out.println(book.getName()));
复制代码
例二:打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
  1. // 打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情     爱情
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.         .flatMap(author -> author.getBooks().stream())        // 一个作者有多本书
  5.         .distinct()
  6.         .flatMap(book -> Arrays.stream(book.getCategory().split(",")))        // 一本书对应多个类别
  7.         .distinct()
  8.         .forEach(category-> System.out.println(category));
复制代码
# 终结操作

# forEach

对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么详细操作。
例子:输出所有作家的名字
  1. // 输出所有作家的名字
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.         .map(author -> author.getName())
  5.         .distinct()
  6.         .forEach(name-> System.out.println(name));
复制代码
# count

可以用来获取当前流中元素的个数。
例子:打印这些作家的所出书籍的数目,注意删除重复元素。
  1. // 打印这些作家的所出书籍的数目,注意删除重复元素。
  2. List<Author> authors = getAuthors();
  3. long count = authors.stream()
  4.         .flatMap(author -> author.getBooks().stream())
  5.         .distinct()
  6.         .count();
  7. System.out.println(count);
复制代码
# max&min

可以用来或者流中的最值。
例子:分别获取这些作家的所出书籍的最高分和最低分并打印。
  1. // 分别获取这些作家的所出书籍的最高分和最低分并打印。
  2. //Stream<Author>  -> Stream<Book> ->Stream<Integer>  ->求值
  3. List<Author> authors = getAuthors();
  4. Optional<Integer> max = authors.stream()
  5.         .flatMap(author -> author.getBooks().stream())
  6.         .map(book -> book.getScore())
  7.         .max((score1, score2) -> score1 - score2);
  8. Optional<Integer> min = authors.stream()
  9.         .flatMap(author -> author.getBooks().stream())
  10.         .map(book -> book.getScore())
  11.         .min((score1, score2) -> score1 - score2);
  12. System.out.println(max.get());
  13. System.out.println(min.get());
复制代码
# collect

把当前流转换成一个聚集。
例子:获取一个存放所有作者名字的List聚集。
  1. // 获取一个存放所有作者名字的List集合。
  2. List<Author> authors = getAuthors();
  3. List<String> nameList = authors.stream()
  4.         .map(author -> author.getName())
  5.         .collect(Collectors.toList());
  6. System.out.println(nameList);
复制代码
获取一个所有书名的Set聚集。
  1. // 获取一个所有书名的Set集合。
  2. List<Author> authors = getAuthors();
  3. Set<Book> books = authors.stream()
  4.         .flatMap(author -> author.getBooks().stream())
  5.         .collect(Collectors.toSet());
  6. System.out.println(books);
复制代码
获取一个Map聚集,map的key为作者名,value为List
  1. // 获取一个Map集合,map的key为作者名,value为List<Book>
  2. List<Author> authors = getAuthors();
  3. Map<String, List<Book>> map = authors.stream()
  4.         .distinct()
  5.         .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
  6. System.out.println(map);
复制代码
# 查找与匹配

# anyMatch

可以用来判断是否有任意符合匹配条件的元素,结果为boolean范例。
例子:判断是否有年龄在29以上的作家
  1. // 判断是否有年龄在29以上的作家
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4.         .anyMatch(author -> author.getAge() > 29);
  5. System.out.println(flag);
复制代码
# allMatch

可以用来判断是否都符合匹配条件,结果为boolean范例。假如都符合结果为true,否则结果为false。
例子:判断是否所有的作家都是成年人
  1. // 判断是否所有的作家都是成年人
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4.         .allMatch(author -> author.getAge() >= 18);
  5. System.out.println(flag);
复制代码
# noneMatch

可以判断流中的元素是否都不符合匹配条件。假如都不符合结果为true,否则结果为false
例子:判断作家是否都没有凌驾100岁的。
  1. // 判断作家是否都没有超过100岁的。
  2. List<Author> authors = getAuthors();
  3. boolean b = authors.stream()
  4.         .noneMatch(author -> author.getAge() > 100);
  5. System.out.println(b);
复制代码
# findAny

获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
例子:获取任意一个年龄大于18的作家,假如存在就输出他的名字
  1. // 获取任意一个年龄大于18的作家,如果存在就输出他的名字
  2. List<Author> authors = getAuthors();
  3. Optional<Author> optionalAuthor = authors.stream()
  4.         .filter(author -> author.getAge()>18)
  5.         .findAny();
  6. optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
复制代码
# findFirst

获取流中的第一个元素。
例子:获取一个年龄最小的作家,并输出他的姓名。
  1. // 获取一个年龄最小的作家,并输出他的姓名。
  2. List<Author> authors = getAuthors();
  3. Optional<Author> first = authors.stream()
  4.         .sorted((o1, o2) -> o1.getAge() - o2.getAge())
  5.         .findFirst();
  6. first.ifPresent(author -> System.out.println(author.getName()));
复制代码
# reduce归并

对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。
reduce两个参数的重载形式内部的计算方式如下:
  1. T result = identity;
  2. for (T element : this stream)
  3.         result = accumulator.apply(result, element)        // 计算方式
  4. return result;
复制代码
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply详细进行什么计算也是我们通过方法参数来确定的,类似:
  1. int[] arr = {1, 2, 3, 4, 5};
  2. int sum = 0;
  3. for (int element : arr) {
  4.     sum = sum + element;
  5. }
  6. return sum;
复制代码
例子:使用reduce求所有作者年龄的和
  1. // 使用reduce求所有作者年龄的和
  2. List<Author> authors = getAuthors();
  3. Integer sum = authors.stream()
  4.         .distinct()
  5.         .map(author -> author.getAge())
  6.         .reduce(0, (result, element) -> result + element);
  7. System.out.println(sum);
复制代码
使用reduce求所有作者中年龄的最大值
  1. // 使用reduce求所有作者中年龄的最大值
  2. List<Author> authors = getAuthors();
  3. Integer max = authors.stream()
  4.         .map(author -> author.getAge())
  5.         .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
  6. System.out.println(max);
复制代码
使用reduce求所有作者中年龄的最小值
  1. // 使用reduce求所有作者中年龄的最小值
  2. List<Author> authors = getAuthors();
  3. Integer min = authors.stream()
  4.         .map(author -> author.getAge())
  5.         .reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
  6. System.out.println(min);
复制代码
reduce一个参数的重载形式内部的计算
  1. boolean foundAny = false;
  2. T result = null;
  3. for (T element : this stream) {
  4.     if (!foundAny) {
  5.         foundAny = true;
  6.         result = element;        // 将流元素中的第一个值设置为了初始值,所以我们才不需要指定初始值
  7.     }
  8.     else
  9.         result = accumulator.apply(result, element);        // 计算方式
  10. }
  11. return foundAny ? Optional.of(result) : Optional.empty();
复制代码
假如用一个参数的重载方法去求最小值代码如下:
  1. // 使用reduce求所有作者中年龄的最小值
  2. List<Author> authors = getAuthors();
  3. Optional<Integer> minOptional = authors.stream()
  4.         .map(author -> author.getAge())
  5.         .reduce((result, element) -> result > element ? element : result);
  6. minOptional.ifPresent(age-> System.out.println(age));
复制代码
# 注意事项


  • 惰性求值(假如没有终结操作,没有中间操作是不会得到执行的)
  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
  • 【正常操作】不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来聚集中的元素的。这往往也是我们盼望的)
不正常操作:对原对象数据进行操作了,如在Stream中进行了原对象的set操作
# Optional

# 概述

我们在编写代码的时候出现最多的就是空指针异常。以是在很多情况下我们需要做各种非空的判断。
比方:
  1. Author author = getAuthor();
  2. if(author!=null){
  3.     System.out.println(author.getName());
  4. }
复制代码
尤其是对象中的属性还是一个对象的情况下。这种判断会更多。
而过多的判断语句会让我们的代码显得臃肿不堪。
以是在JDK8中引入了Optional,养成使用Optional的风俗后你可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中也都用到了Optional,假如不会使用Optional也会对函数式编程的学习造成影响。
# 使用

# 创建对象

Optional就好像是包装类,可以把我们的详细数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
我们一般使用Optional静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。
  1. Author author = getAuthor();
  2. Optional<Author> authorOptional = Optional.ofNullable(author);
复制代码
你可能会觉得还要加一行代码来封装数据比较麻烦。但是假如改造下getAuthor方法,让其的返回值就是封装好的Optional的话,我们在使用时就会方便很多。
而且在现实开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本也已经支持Optional了。我们可以直接把dao方法的返回值范例定义成Optional范例,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
假如你确定一个对象不是空的,则可以使用Optional静态方法of来把数据封装成Optional对象。
  1. Author author = new Author();
  2. Optional<Author> authorOptional = Optional.of(author);
复制代码
但是一定要注意,假如使用of,那传入的参数必须不为null。(传入null会出现空指针异常)
假如一个方法的返回值范例是Optional范例。而假如我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional静态方法empty来进行封装。
  1. Optional.empty()
复制代码
以是哪种方式会更方便?ofNullable
  1. public static <T> Optional<T> ofNullable(T value) {
  2.     return value == null ? empty() : of(value);
  3. }
复制代码
# 安全消费值

我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent方法对来消费其中的值。
这个方法会判断其内封装的数据是否为空,不为空时才会执行详细的消费代码。这样使用起来就更加安全了。
比方,以下写法就优雅的避免了空指针异常。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. authorOptional.ifPresent(author -> System.out.println(author.getName()));
复制代码
# 获取值

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

假如我们盼望安全的获取值。不推荐使用get方法,而是使用Optional提供的以下方法:
1、orElseGet
获取数据并且设置数据为空时的默认值。假如数据不为空就能获取到该数据。假如为空则根据你传入的参数来创建对象作为默认值返回。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. Author author1 = authorOptional.orElseGet(() -> new Author());
复制代码
2、orElseThrow
获取数据,假如数据不为空就能获取到该数据。假如为空则根据你传入的参数来创建异常抛出。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. try {
  3.     Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author为空"));
  4.     System.out.println(author.getName());
  5. } catch (Throwable throwable) {
  6.     throwable.printStackTrace();
  7. }
复制代码
# 过滤

我们可以使用filter方法对数据进行过滤。假如原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. authorOptional
  3.                 .filter(author -> author.getAge()>100)
  4.                 .ifPresent(author -> System.out.println(author.getName()));
复制代码
# 判断

我们可以使用isPresent方法进行是否存在数据的判断。假如为空返回值为false,假如不为空,返回值为true。但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. if (authorOptional.isPresent()) {
  3.     System.out.println(authorOptional.get().getName());
  4. }
复制代码
# 数据转换

Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。
比方我们想获取作家的书籍聚集。
  1. private static void testMap() {
  2.     Optional<Author> authorOptional = getAuthorOptional();
  3.     Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
  4.     optionalBooks.ifPresent(books -> System.out.println(books));
  5. }
复制代码
# 函数式接口

# 概述

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

# Consumer 消费接口

根据其中抽象方法的参数列表和返回值范例知道,我们可以在方法中对传入的参数进行消费。
  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3.     /**
  4.      * Performs this operation on the given argument.
  5.      *
  6.      * @param t the input argument
  7.      */
  8.     void accept(T t);
  9. }
复制代码
# Function 计算转换接口

根据其中抽象方法的参数列表和返回值范例知道,我们可以在方法中对传入的参数计算或转换,把结果返回
  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3.     /**
  4.      * Applies this function to the given argument.
  5.      *
  6.      * @param t the function argument
  7.      * @return the function result
  8.      */
  9.     R apply(T t);
  10. }
复制代码
# Predicate 判断接口

根据其中抽象方法的参数列表和返回值范例知道,我们可以在方法中对传入的参数条件判断,返回判断结果
  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3.     /**
  4.      * Evaluates this predicate on the given argument.
  5.      *
  6.      * @param t the input argument
  7.      * @return {@code true} if the input argument matches the predicate,
  8.      * otherwise {@code false}
  9.      */
  10.     boolean test(T t);
  11. }
复制代码
[#](#Supplier 生产型接口) Supplier 生产型接口

根据其中抽象方法的参数列表和返回值范例知道,我们可以在方法中创建对象,把创建好的对象返回
  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3.     /**
  4.      * Gets a result.
  5.      *
  6.      * @return a result
  7.      */
  8.     T get();
  9. }
复制代码
# 常用的默认方法

# and 与判断

我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相称于是使用&&来拼接两个判断条件
比方:
打印作家中年龄大于17并且姓名的长度大于1的作家。
  1. List<Author> authors = getAuthors();
  2. Stream<Author> authorStream = authors.stream();
  3. authorStream.filter(new Predicate<Author>() {
  4.     @Override
  5.     public boolean test(Author author) {
  6.         return author.getAge()>17;
  7.     }
  8. }.and(new Predicate<Author>() {
  9.     @Override
  10.     public boolean test(Author author) {
  11.         return author.getName().length()>1;
  12.     }
  13. })).forEach(author -> System.out.println(author));
复制代码
[#](#or 或判断) or  或判断

我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相称于是使用||来拼接两个判断条件。
比方:
打印作家中年龄大于17或者姓名的长度小于2的作家。
  1. //        打印作家中年龄大于17或者姓名的长度小于2的作家。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.        .filter(new Predicate<Author>() {
  5.            @Override
  6.            public boolean test(Author author) {
  7.                return author.getAge()>17;
  8.            }
  9.        }.or(new Predicate<Author>() {
  10.            @Override
  11.            public boolean test(Author author) {
  12.                return author.getName().length()<2;
  13.            }
  14.        })).forEach(author -> System.out.println(author.getName()));
复制代码
# negate 非判断

Predicate接口中的方法。negate方法相称于是在判断添加前面加了个! 表示取反
比方:
打印作家中年龄不大于17的作家。
  1. //        打印作家中年龄不大于17的作家。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4.         .filter(new Predicate<Author>() {
  5.             @Override
  6.             public boolean test(Author author) {
  7.                 return author.getAge()>17;
  8.             }
  9.         }.negate()).forEach(author -> System.out.println(author.getAge()));
复制代码
# 方法引用

我们在使用lambda时,假如方法体中只有一个方法的调用的话(包罗构造方法),我们可以用方法引用进一步简化代码
明白:转化成匿名内部类有助于明白
# 推荐用法

我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键alt+enter尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
# 基本格式
  1. 类名或者对象名::方法名
复制代码
# 语法详解(了解)

# 引用类的静态方法

其实就是引用类的静态方法
# 格式
  1. 类名::方法名
复制代码
# 使用前提

假如我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
比方:如下代码就可以用方法引用进行简化
  1. List<Author> authors = getAuthors();
  2. Stream<Author> authorStream = authors.stream();
  3. authorStream.map(author -> author.getAge())
  4.         .map(age->String.valueOf(age));
复制代码
注意,假如我们所重写的方法是没有参数的,调用的方法也是没有参数的也相称于符合以上规则。
优化后如下:
  1. List<Author> authors = getAuthors();
  2. Stream<Author> authorStream = authors.stream();
  3. authorStream.map(author -> author.getAge())
  4.         .map(String::valueOf);
复制代码
# 引用对象的实例方法

# 格式
  1. 对象名::方法名
复制代码
# 使用前提

假如我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
比方:
  1. List<Author> authors = getAuthors();
  2. Stream<Author> authorStream = authors.stream();
  3. StringBuilder sb = new StringBuilder();
  4. authorStream.map(author -> author.getName())
  5.         .forEach(name->sb.append(name));
复制代码
优化后:
  1. List<Author> authors = getAuthors();
  2. Stream<Author> authorStream = authors.stream();
  3. StringBuilder sb = new StringBuilder();
  4. authorStream.map(author -> author.getName())
  5.         .forEach(sb::append);
复制代码
# 引用类的实例方法

# 格式
  1. 类名::方法名
复制代码
# 使用前提

假如我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
比方:
  1. interface UseString{
  2.     String use(String str, int start, int length);
  3. }
  4. public static String subAuthorName(String str, UseString useString){
  5.     int start = 0;
  6.     int length = 1;
  7.     return useString.use(str, start, length);
  8. }
  9. public static void main(String[] args) {
  10.     subAuthorName("森泽佳奈", new UseString() {
  11.         @Override
  12.         public String use(String str, int start, int length) {        // 第一个参数 str
  13.             return str.substring(start,length);
  14.         }
  15.     });
  16. }
复制代码
优化后如下:
  1. public static void main(String[] args) {
  2.      subAuthorName("森泽加奈", String::substring);
  3. }
复制代码
# 构造器引用

假如方法体中的一行代码是构造器的话就可以使用构造器引用。
# 格式
  1. 类名::new
复制代码
# 使用前提

假如我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
比方:
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.         .map(author -> author.getName())
  4.         .map(name->new StringBuilder(name))
  5.         .map(sb->sb.append("-三更").toString())
  6.         .forEach(str-> System.out.println(str));
复制代码
优化后:
  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3.         .map(author -> author.getName())
  4.         .map(StringBuilder::new)
  5.         .map(sb->sb.append("-三更").toString())
  6.         .forEach(str-> System.out.println(str));
复制代码
# 高级用法

# 基本数据范例优化

比方:mapToInt、mapToLong、mapToDouble、flatMapToInt、flatMapToDouble等。
  1. private static void test27() {
  2.     List<Author> authors = getAuthors();
  3.     authors.stream()
  4.             .map(author -> author.getAge())
  5.             .map(age -> age + 10)        // 会经历拆箱、装箱                大数据量下这个时间浪费还是不能忽略的
  6.             .filter(age->age>18)
  7.             .map(age->age+2)
  8.             .forEach(System.out::println);
  9.     authors.stream()
  10.             .mapToInt(author -> author.getAge())
  11.             .map(age -> age + 10)
  12.             .filter(age->age>18)
  13.             .map(age->age+2)
  14.             .forEach(System.out::println);
  15. }
复制代码
# 并行流

当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把使命分配给多个线程去完成。假如我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的明白和认识。而假如我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel方法可以把串行流转换成并行流。
  1. private static void test28() {
  2.    
  3.     Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  4.    
  5.     Integer sum = stream.parallel()
  6.             .peek(new Consumer<Integer>() {
  7.                 @Override
  8.                 public void accept(Integer num) {
  9.                     System.out.println(num+Thread.currentThread().getName());
  10.                 }
  11.             })
  12.             .filter(num -> num > 5)
  13.             .reduce((result, ele) -> result + ele)
  14.             .get();
  15.    
  16.     System.out.println(sum);
  17. }
复制代码
也可以通过parallelStream直接获取并行流对象。
  1. List<Author> authors = getAuthors();
  2. authors.parallelStream()
  3.         .map(author -> author.getAge())
  4.         .map(age -> age + 10)
  5.         .filter(age->age>18)
  6.         .map(age->age+2)
  7.         .forEach(System.out::println);
复制代码
# 对应MD文档

链接:函数式编程

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

不到断气不罢休

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表