【JAVA】你认识强大的 Stream 和 Optional 吗?

2023-08-31 13:28:19 浏览数 (2)

前言

早已仰慕 Stream 流久已,终于有机会彻底的了解其特性以及用法了,关于源码的深度理解可能还需要继续增加功底,在学 Stream 的时候,同时认识了强大的 Optional,奈斯!

接下来的博文直接上用法了,想了解更多,建议阅读以下两篇博文:

一文带你入门Java Stream流,太强了

Java 8 Optional 最佳指南

Stream

流的操作可以分为两种类型:

1)中间操作,可以有多个,每次返回一个新的流,可进行链式操作。

2)终端操作,只能有一个,每次执行完,这个流也就用光光了,无法执行下一个操作,因此只能放在最后。

中间操作不会立即执行,只有等到终端操作的时候,流才开始真正地遍历,用于映射、过滤等。通俗点说,就是一次遍历执行多个操作,性能就大大提高了。  

0x1. 创建流

如果是数组的话,可以使用 Arrays.stream() 或者 Stream.of() 创建流;如果是集合的话,可以直接使用 stream() 方法创建流,因为该方法已经添加到 Collection 接口中。

代码语言:javascript复制
public static void main(String[] args) {
    String[] arr = new String[]{"a", "b", "c"};
    Stream<String> stream = Arrays.stream(arr);

    stream = Stream.of("a", "b", "c");

    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    stream = list.stream();
}

查看 Stream 源码的话,你会发现 of() 方法内部其实调用了 Arrays.stream() 方法:

代码语言:javascript复制
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

另外,集合还可以调用 parallelStream() 方法创建并发流,默认使用的是 ForkJoinPool.commonPool() 线程池。

代码语言:javascript复制
List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();

0x2. 操作流

0x2.1. 过滤

通过 filter() 方法可以从流中筛选出我们想要的元素。

代码语言:javascript复制
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    Stream<String> stream = list.stream().filter(element -> element.contains("王"));
    stream.forEach(System.out::println);
}

运行结果:

代码语言:javascript复制
王力宏

0x2.2. 映射

如果想通过某种操作把一个流中的元素转化成新的流中的元素,可以使用 map() 方法。

代码语言:javascript复制
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    Stream<Integer> stream = list.stream().map(String::length);
    stream.forEach(System.out::println);
}

map() 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数,此时参数 为 String 类的 length 方法,也就是把 Stream<String> 的流转成一个 Stream<Integer> 的流。

运行结果:

代码语言:javascript复制
3
3
2
3

0x2.3. 匹配

Stream 类提供了三个方法可供进行元素匹配,它们分别是:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true
  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true
  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部不匹配,则返回 true
代码语言:javascript复制
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");

    boolean  anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));
    boolean  allMatchFlag = list.stream().allMatch(element -> element.length() > 1);
    boolean  noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));
    System.out.println(anyMatchFlag);
    System.out.println(allMatchFlag);
    System.out.println(noneMatchFlag);
}

运行结果:

代码语言:javascript复制
true
true
true

0x2.4. 组合

reduce() 方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:

Optional<T> reduce(BinaryOperator<T> accumulator) 没有起始值,只有一个参数,就是运算规则,此时返回 Optional

T reduce(T identity, BinaryOperator<T> accumulator) 有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。

代码语言:javascript复制
public static void main(String[] args) {
    Integer[] ints = {0, 1, 2, 3};
    List<Integer> list = Arrays.asList(ints);

    Optional<Integer> optional = list.stream().reduce((a, b) -> a   b);
    Optional<Integer> optional1 = list.stream().reduce(Integer::sum);
    System.out.println(optional.orElse(0));
    System.out.println(optional1.orElse(0));

    int reduce = list.stream().reduce(6, (a, b) -> a   b);
    System.out.println(reduce);
    int reduce1 = list.stream().reduce(6, Integer::sum);
    System.out.println(reduce1);
}

orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值(上述代码的默认值就是0)。该方法的参数类型和值得类型一致。

运行结果:

代码语言:javascript复制
6
6
12
12

0x3. 转换流

既然可以把集合或者数组转成流,那么也应该有对应的方法,将流转换回去——collect() 方法就满足了这种需求。

代码语言:javascript复制
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");

    String[] strArray = list.stream().toArray(String[]::new);
    System.out.println(Arrays.toString(strArray));

    List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());
    List<String> list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
    System.out.println(list1);
    System.out.println(list2);

    String str = list.stream().collect(Collectors.joining(", ")).toString();
    System.out.println(str);
}

Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如说 toList() 方法将元素收集到一个新的 java.util.List 中;比如说 toCollection() 方法将元素收集到一个新的 java.util.ArrayList 中;比如说 joining() 方法将元素收集到一个可以用分隔符指定的字符串中。

运行结果:

代码语言:javascript复制
[周杰伦, 王力宏, 陶喆, 林俊杰]
[3, 3, 2, 3]
[周杰伦, 王力宏, 陶喆, 林俊杰]
周杰伦, 王力宏, 陶喆, 林俊杰

0x4. 完整实例

代码语言:javascript复制
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
 
public class Java8Tester {
   public static void main(String args[]){
      // 计算空字符串
      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      
      System.out.println("列表: "  strings);
        
      count = strings.stream().filter(string->string.isEmpty()).count();
      System.out.println("空字符串数量为: "   count);
        
      count = strings.stream().filter(string -> string.length() == 3).count();
      System.out.println("字符串长度为 3 的数量为: "   count);
        
      filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
      System.out.println("筛选后的列表: "   filtered);
        
      mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
      System.out.println("合并字符串: "   mergedString);
        
      squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
      System.out.println("Squares List: "   squaresList);
      System.out.println("列表: "  integers);
        
      IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
        
      System.out.println("列表中最大的数 : "   stats.getMax());
      System.out.println("列表中最小的数 : "   stats.getMin());
      System.out.println("所有数之和 : "   stats.getSum());
      System.out.println("平均数 : "   stats.getAverage());
      System.out.println("随机数: ");
        
      random.ints().limit(10).sorted().forEach(System.out::println);
        
      // 并行处理
      count = strings.parallelStream().filter(string -> string.isEmpty()).count();
      System.out.println("空字符串的数量为: "   count);
   }
}

运行结果:

代码语言:javascript复制
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

Optional

Optional 提供了一种用于表示可选值而非空引用的类级别解决方案;  

0x1. 创建 Optional 对象

1)可以使用静态方法 empty() 创建一个空的 Optional 对象:

代码语言:javascript复制
Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty

2)可以使用静态方法 of() 创建一个非空的 Optional 对象

代码语言:javascript复制
Optional<String> opt = Optional.of("id10t.");
System.out.println(opt); // 输出:Optional[id10t.]

当然了,传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException

3)可以使用静态方法 ofNullable() 创建一个即可空又可非空的 Optional 对象:

代码语言:javascript复制
String name = "id10t.";
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional[id10t.]
optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull); // 输出:Optional.empty

ofNullable() 方法内部有一个三元表达式,如果为参数为 null,则返回私有常量 EMPTY;否则使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。  

0x2. 判断值是否存在

可以通过方法 isPresent() 判断一个 Optional 对象是否存在,如果存在,该方法返回 true,否则返回 false,取代了 obj != null 的判断。

代码语言:javascript复制
Optional<String> opt = Optional.of("id10t.");
System.out.println(opt.isPresent()); // 输出:true

Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(opt.isPresent()); // 输出:false

Java 11 后还可以通过方法 isEmpty() 判断与 isPresent() 相反的结果。

0x3. 非空表达式

Optional 类有一个非常现代化的方法 ifPresent(),允许我们使用函数式编程的方式执行一些代码,因此,我把它称为非空表达式。

代码语言:javascript复制
Optional<String> opt = Optional.of("id10t.");
opt.ifPresent(str -> System.out.println(str.length()));

Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行 action,空时执行 emptyAction

代码语言:javascript复制
Optional<String> opt = Optional.ofNullable("id10t.");
// Optional<String> opt = Optional.ofNullable(null);
opt.ifPresentOrElse(
        str -> System.out.println(str.length()),
        () -> System.out.println("null"));

0x4. 设置(获取)默认值

有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse()orElseGet() 方法就派上用场了。

orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。该方法的参数类型和值得类型一致。

代码语言:javascript复制
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("id10t.");
System.out.println(name); // 输出:id10t.

orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数。

代码语言:javascript复制
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"id10t.");
System.out.println(name); // 输出:id10t.

从输出结果以及代码的形式上来看,这两个方法极其相似,这不免引起我们的怀疑,Java 类库的设计者有必要这样做吗?

当 Optional 对象的值不为 null 时:

代码语言:javascript复制
public class A {
    public static void main(String[] args) {
        String name = "id10t.";
        System.out.println("orElse");
        String name2 = Optional.ofNullable(name).orElse(getDefaultValue());

        System.out.println("orElseGet");
        String name3 = Optional.ofNullable(name).orElseGet(A::getDefaultValue);
    }

    public static String getDefaultValue() {
        System.out.println("getDefaultValue");
        return "idiot";
    }
}

运行结果:

代码语言:javascript复制
orElse
getDefaultValue
orElseGet

orElseGet() 没有去调用 getDefaultValue(),性能更佳;  

0x5. 过滤值

filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件,如果表达式的结果为 false,则返回一个 EMPTY 的 Optional 对象,否则返回过滤后的 Optional 对象。

代码语言:javascript复制
public static void main(String[] args) {
    String password = "123456";
    Optional<String> opt = Optional.ofNullable(password);
    System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent());
}

进阶:

代码语言:javascript复制
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;

password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);

0x6. 转换值

map() 方法,该方法可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改。

代码语言:javascript复制
public staticvoid main(String[] args) {
    String name = "id10t.";
    Optional<String> nameOptional = Optional.of(name);
    Optional<Integer> intOpt = nameOptional
            .map(String::length);

    System.out.println(intOpt.orElse(0));
}

进阶:

代码语言:javascript复制
public static void main(String[] args) {
    String password = "password";
    Optional<String>  opt = Optional.ofNullable(password);

    Predicate<String> len6 = pwd -> pwd.length() > 6;
    Predicate<String> len10 = pwd -> pwd.length() < 10;
    Predicate<String> eq = pwd -> pwd.equals("password");

    boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
    System.out.println(result);
}

后记

感谢大佬沉默王二的文章,参考链接如下:

一文带你入门Java Stream流,太强了

Java 8 Optional 最佳指南

现在就处于查漏补缺的状态,缺啥补啥,夯实基础!

0 人点赞