Java Stream流

Stream

java.util.stream包是Java 8引入的全新的流式API,与java.ioInputStreamOutputStream不同,Stream代表的是任意Java对象的序列,两者对比如下:

java.iojava.util.stream
存储顺序读写的bytechar顺序输出的任意Java对象实例
用途序列化至文件或网络内存计算/业务逻辑

一个顺序输出的Java对象序列,不就是一个List容器吗?这个StreamList也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。换句话说,List的用途是操作一组已存在的Java对象,而Stream实现的是惰性计算,两者对比如下:

java.util.Listjava.util.stream
元素已分配并存储在内存可能未分配,实时计算
用途操作一组已存在的Java对象惰性计算

Stream可用于无限序列,但注意每个Stream都只是存储转换规则,只有调用求值方法时才会真正地进行结果的计算(此处要计算无限序列,必须先用limit(maxSize)限定为有限序列,否则将一直循环计算下去),例如:

public static void main (String[] args) {
  Stream<BigInteger> naturals = createNaturalStream();			// 不计算
  Stream<BigInteger> s2 = naturals.map(n -> n.multiply(n));		// 不计算
  Stream<BigInteger> s3 = s2.limit(100);						// 不计算
  s3.forEach(System.out::println);								// 计算

  // 改写成链式操作:
  createNaturalStream()							// 不计算
    .map(n -> n.multiply(n))					// 不计算
    .limit(100)									// 不计算
    .forEach(System.out.println);				// 计算
}

public static Stream<BigInteger> createNaturalStream() {
  return Stream.generate(new Supplier<>() {
    BigInteger bigInteger = BigInteger.valueOf(0);

    @Override
    public BigInteger get () {
      bigInteger = bigInteger.add(BigInteger.valueOf(1));
      return bigInteger;
    }
  });
}

创建Stream

  1. Stream.of()

    Stream<String> stream = Stream.of("A", "B", "C", "D");
    
  2. 基于数组:

    String[] arr = {"A", "B", "C"};
    Stream<String> stream = Arrays.stream(arr);
    
  3. 基于Collection

    List<String> list = List.of("X", "Y", "Z");
    Stream<String> stream = list.stream();
    
  4. 基于SupplierSupplier接口用于提供无限序列的实现规则

    Supplier<String> sp = new Supplier<>() {
      private String s = "";
      @Override
      public String get () {
        s += "*";
        return s;
      }
    };
    Stream<String> s = Stream.generate(sp);
    
  5. 一些API的接口:

    • Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容(此方法对于按行遍历文本文件十分有用):

      try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
        ...
      }
      
    • 正则表达式的Pattern对象有一个splitAsStream()方法,可以直接把一个长字符串分割成Stream序列而不是数组:

      Pattern p = Pattern.compile("\\s+");
      Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
      s.forEach(System.out::println);
      

基本类型Stream

因为Java的范型不支持基本类型,所以我们无法用Stream<int>这样的类型,会发生编译错误。为了保存int,只能使用Stream<Integer>,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java标准库提供了IntStreamLongStreamDoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率:

// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });

// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);

通过基本类型Stream,可以实现集合、包装类型数组与基本类型数组之间的转换:

Integer[]
Stream<Integer>
IntStream
int[]
List<Integer>
// Integer[]  <--->  Stream<Integer>
Integer[] arr = Stream.of(1, 2, 3).toArray(Integer[]::new);
Stream<Integer> stream = Arrays.stream(arr);

// List<Integer>  <--->  Stream<Integer>
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
Stream<Integer> stream = list.stream();

// Stream<Integer>  <--->  IntStream
Stream<Integer> stream = IntStream.of(1, 2, 3).boxed();
IntStream intStream = stream.mapToInt(Integer::intValue);

// IntStream  <--->  int[]
IntStream intStream = Arrays.stream(new int[] {1, 2, 3});
int[] arr = intStream.toArray();

转换操作

Stream的转换操作主要有:

  1. <R> Stream<R> map(Function<? super T, ? extends R> mapper):用于将当前Stream转换为另一个Stream

    • 有返回基本类型Streammap方法:
      • IntStream mapToInt(ToIntFunction<? super T> mapper)
      • LongStream mapToLong(ToLongFunction<? super T> mapper)
      • DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
    Stream.of(" 2019-12-31 ", "2020 - 01-09 ", "2020- 05 - 01 ")
      .map(date -> date.replaceAll("\\s", ""))
      .forEach(System.out::println);
    
  2. Stream<T> filter(Predicate<? super T> predicate):用于从当前Stream中过滤得到另一个Stream

    IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
      .filter(n -> n % 2 != 0)
      .forEach(System.out::println);
    
  3. Stream<T> sorted():排序,要求所有Stream元素都已经实现Comparable接口

    Stream<T> sorted(Comparator<? super T> comparator):重载方法,传入比较器

    // String已实现Comparable:
    Stream.of("Orange", "apple", "Banana")
      .sorted()
      .forEach(System.out::println);
    
    // 指定比较规则:
    Stream.of("Orange", "apple", "Banana")
      .sorted(String::compareToIgnoreCase)
      .forEach(System.out::println);
    
  4. Stream<T> distinct():去重,Stream元素的equals()方法决定

    Stream.of("apple", "apple", "pear")
      .distinct()
      .forEach(System.out::println);
    

截取操作

  1. Stream<T> skip(long n):跳过n个元素
  2. Stream<T> limit(long maxSize):截取最多前maxSize个元素

合并操作

  1. static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):静态方法,用于将两个Stream合并成一个Stream

    Stream<String> s = Stream.concat(
      Stream.of("A", "B", "C"),
      Stream.of("D", "E")
    );
    
  2. <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):先将当前Stream的每个元素都mapStream类型,再将这些Stream都合并成一个总的Stream后返回

    // 将多个List合并成一个Stream
    Stream<List<Integer>> s = Stream.of(
      Arrays.asList(1, 2, 3),
      Arrays.asList(4, 5, 6),
      Arrays.asList(7, 8, 9)
    );
    Stream<Integer> i = s.flatMap(list -> list.stream());
    

并行处理

通常情况下,对Stream的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。

把一个普通Stream转换为可以并行处理的Stream非常简单,只需要用parallel()进行转换:

Stream<String> s = Stream.of("apple", "pear", "orange");
String[] result = s.parallel()			// 变成一个可以并行处理的Stream
  .sorted()								// 可以进行并行排序
  .toArray(String[]::new);

经过parallel()转换后的Stream只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。

聚合操作

以上操作都只是存储转换规则,并不会进行计算;只有聚合操作才会进行真正的计算,且每个Stream仅能执行一次,这是因为每个Stream在执行完聚合操作后,流已经关闭了

  1. Optional<T> reduce(BinaryOperator<T> accumulator):用于将当前Stream的所有元素按照聚合函数聚合成一个结果,它以第一个Stream元素作为第一个结果,accumulator执行次数等于元素数-1

    T reduce(T identity, BinaryOperator<T> accumulator):重载方法,它以identity作为第一个结果,accumulator执行次数等于元素数

    // 下面以IntStream为例,方法定义略有不同,但使用完全一致
    
    // 1. 第一个元素为初始值
    OptionalInt op = IntStream.of(1, 2, 3).reduce((res, value) -> res + value);
    if (op.isPresent()) {
      System.out.println(op.getAsInt());
    }
    // 等价于:
    IntStream.of(1, 2, 3).reduce(Integer::sum).ifPresent(System.out::println);
    
    // 2. 指定初始值
    int sum = IntStream.of(1, 2, 3).reduce(0, Integer::sum);
    System.out.println(sum);
    
  2. long count():返回元素个数

    Optional<T> max(Comparator<? super T> comparator):找出最大元素

    Optional<T> min(Comparator<? super T> comparator):找出最小元素

  3. <R, A> R collect(Collector<? super T, A, R> collector):按指定规则将Stream输出为一个容器

    // 1. 输出为List:
    List<String> list = Stream.of("Apple", "Pear", "Orange").collect(Collectors.toList());
    // 等价于:
    List<String> list = Stream.of("Apple", "Pear", "Orange").toList();
    
    // 2. 输出为Set:
    Set<String> list = Stream.of("Apple", "Apple", "Pear").collect(Collectors.toSet());
    
    // 3. 输出为Map:
    Map<String, String> map = Stream.of("APPL:Apple", "MSFT:Microsoft")
      .collect(Collectors.toMap(
        s -> s.substring(0, s.indexOf(':')),
        s -> s.substring(s.indexOf(':') + 1)
      ));
    
    // 4. 输出为分组Map(组表示为key,元素列表为value):
    Map<String, List<String>> groups = Stream
      .of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots")
      .collect(Collectors.groupingBy(
        s -> s.substring(0, 1),
        Collectors.toList()				// 默认值,可省略
      ));
    
  4. Object[] toArray():输出为数组

    <A> A[] toArray(IntFunction<A[]> generator):重载方法

    String[] arr = Stream.of("Apple", "Pear", "Orange").toArray(String[]::new);
    
  5. boolean allMatch(Predicate<? super T> predicate):测试是否所有元素均满足测试条件

    boolean anyMatch(Predicate<? super T> predicate):测试是否至少有一个元素满足测试条件

  6. void forEach(Consumer<? super T> action):遍历输出

针对IntStreamLongStreamDoubleStream,还额外提供了以下聚合方法:

  1. OptionalDouble average():对所有元素求平均数
  2. int sum()long sum()double sum():对所有元素求和

参考

  1. 廖雪峰的官方网站 - Java教程 - 函数式编程 - 使用Stream
  2. Package java.util.stream
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值