说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢? 在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作 而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历

循环遍历的弊端

Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),比如for循环:

  • for循环的语法就是“怎么做”
  • for循环的循环体才是“做什么”

循环遍历的弊端:遍历麻烦,需要创建新集合,多遍到的数据在加入新集合,反复操作增加了工作量

Stream的更优写法

下面来看一下借助Java 8的Stream API,什么才叫优雅:

  1. import java.util.ArrayList;
  2. public class Test {
  3. public static void main(String[] args) {
  4. ArrayList<String> list = new ArrayList<String>();
  5. list.add("一缕清风");
  6. list.add("Kenguba");
  7. list.add("庆古八");
  8. list.stream()
  9. .filter(s->s.startsWith("一"))
  10. .filter(s->s.length()>3)
  11. .forEach(System.out::println); // 一缕清风
  12. }
  13. }
  14. //JavaScript中
  15. var array = ["一缕清风", "Kenguba", "庆古八"]
  16. array
  17. .filter(s => s.startsWith("一"))
  18. .filter(s => s.length > 3)
  19. .forEach(s => console.log(s))

流式思想概述

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算
  • 数据源 流的来源。 可以是集合,数组 等

整体来看,流式思想类似于工厂车间的“生产流水线”
流式思想概述.png
这张图中展示了过滤、映射、跳过、计数等多步操作

  • 这里的 filter、map、skip 都是在对函数模型进行操作,集合元素并没有真正被处理。
  • 只有当终结方法 count 执行的时候

整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性
和以前的Collection操作不同,Stream操作还有两个基础的特征:

  • Pipelining
    • 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)
  • 内部迭代
    • 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法

⚠️注意: 请暂时忘记对传统IO流的固有印象!

获取一个流的几种常用的方式

  1. Java 8新加入的常用的流接口。(这并不是一个函数式接口)
  2. java.util.stream.Stream<T>
  1. 所有的 Collection 集合 都可以通过 stream 默认方法获取流

    1. new ArrayList<>().stream();
    2. new LinkedList<>().stream();
    3. new HashSet<>().stream();
    4. new HashMap<>().keySet().stream();
    5. new HashMap<>().values().stream();
    6. new HashMap<>().entrySet().stream();
  2. 数组 :可以用 Stream 接口的静态方法 of 获取对应的流

    1. Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    2. Stream.of(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
    3. Stream.of(new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" });

    常用方法,流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种

  • 延迟方法
    • 返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法
  • 终结方法
    • 返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用
    • 终结方法包括 count 和 forEach 方法

Stream 方式

forEach 逐一处理

会将每一个流元素交给该函数进行处理,它是终结方法,只要写了它,后面就再没有返回流了
虽然方法名字叫 forEach ,但是与for循环中的“for-each”不同

  1. 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理
  2. void forEach(Consumer<? super T> action);
  3. //java.util.function.Consumer<T>接口是一个消费型接口
  4. Consumer<T>接口 中包含一个接口方法 void accept(T t),意为消费一个指定泛型的数据
  5. @FunctionalInterface
  6. public interface Consumer<T> {
  7. void accept(T t);
  8. default Consumer<T> andThen(Consumer<? super T> after) {
  9. Objects.requireNonNull(after);
  10. return (T t) -> { accept(t); after.accept(t); };
  11. }
  12. }
  1. import java.util.function.Consumer;
  2. import java.util.stream.Stream;
  3. public class Test {
  4. public static void main(String[] args) {
  5. Stream.of(1, 2, 3).forEach(new Consumer<Integer>() {
  6. @Override
  7. public void accept(Integer integer) {
  8. System.out.println(integer);
  9. }
  10. });
  11. Stream.of(1, 2, 3).forEach(s -> System.out.println(s));
  12. Stream.of(1, 2, 3).forEach(System.out::println);
  13. }
  14. }

count 统计个数

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数,它是终结方法,只要写了它,后面就再没有返回流了

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
long count();
import java.util.stream.Stream;

public class Test {
  public static void main(final String[] args) {
    Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").filter(s -> s.startsWith("张"));
    System.out.println(ss.count());
  }
}

filter 过滤

可以通过 filter 方法将一个流转换成另一个子集流

该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件
Stream<T> filter(Predicate<? super T> predicate)

java.util.stream.Predicate 函数式接口
其中唯一的抽象方法为
boolean test(T t);
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;


public class Test {
  public static void main(final String[] args) {
    Stream.of("张无忌", "赵敏").filter(new Predicate<String>() {
      @Override
      public boolean test(String s) {
        return s.startsWith("张");
      }
    }).forEach(new Consumer<String>() {
      @Override
      public void accept(final String s) {
        System.out.println(s);
      }
    });

    Stream.of("张无忌", "赵敏").filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
    Stream.of("张无忌", "赵敏").filter(s -> s.startsWith("张")).forEach(System.out::println);
  }
}

map 映射

如果需要将流中的元素映射到另一个流中,可以使用 map 方法

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

java.util.stream.Function 函数式接口,其中唯一的抽象方法为
R apply(T t);
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

public class Test {
  public static void main(final String[] args) {
    Stream.of("1", "2", "3", "4", "5").map(new Function<String, Object>() {
      @Override
      public Object apply(String s) {
        return Integer.parseInt(s);
      }
    }).forEach(new Consumer<Object>() {
      @Override
      public void accept(Object o) {
        System.out.println(o);
      }
    });

    Stream.of("1", "2", "3", "4", "5").map(s -> Integer.parseInt(s)).forEach(s -> {
      System.out.println(s + 1);
    });

    Stream.of("1", "2", "3", "4", "5").map(Integer::parseInt).forEach(System.out::println);
  }
}

limit 取用前几个

limit 方法可以对流进行截取,只取用前n个

Stream<T> limit(long maxSize);
import java.util.stream.Stream;

public class Test {
  public static void main(final String[] args) {
    Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").limit(2);
    System.out.println(ss.count());
  }
}

skip 跳过前几个

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:

Stream<T> skip(long n);
import java.util.stream.Stream;

public class Test {
  public static void main(final String[] args) {
    Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").skip(2);
    System.out.println(ss.count());
  }
}

concat 组合

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
import java.util.stream.Stream;

public class Test {
  public static void main(final String[] args) {
    Stream<String> stream1 = Stream.of("五华县");
    Stream<String> stream2 = Stream.of(new String[] { "横陂镇" });
    Stream.concat(stream1, stream2).forEach(System.out::println);
  }
}

⚠️注意: 这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的

集合元素处理(Stream方式)

import java.util.*;
import java.util.stream.Stream;

class Person {
  public String name;

  public Person(String name) {
    this.name = name;
  }
}

public class Test {
  public static void main(final String[] args) {
    ArrayList<String> one = new ArrayList<>();
    Collections.addAll(one, "宋远桥", "苏星河", "石破天", "老子", "庄子");
    ArrayList<String> two = new ArrayList<>();
    Collections.addAll(two, "张无忌", "张三丰", "张天爱", "张二狗");
    Stream<String> stringStream1 = one.stream().filter(s -> s.length() == 3).limit(2);
    Stream<String> stringStream2 = two.stream().filter(s -> s.startsWith("张")).limit(2);
    Stream.concat(stringStream1, stringStream2).map(name -> new Person(name)).forEach(System.out::println);
  }
}