Java 常用对象转换之 Stream 流
平时的开发中会经常遇到一些对象需要转换,创建了一个项目记录一些常见对象转换的方法,例如:文件转换、日期时间转换、stream 流转换、集合对象转换等,具体的示例代码见 GitHub 项目:zzycreate/java-convert-example 。
本文记录一些常用的 Stream 操作,以备需要时直接使用。
流的基本构成
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
Stream 流的使用基本分为三种操作:生成 Stream 流数据源、Stream 流中值操作、Stream 流结束操作。另外还有 short-circuiting 操作作为补充。
Stream 流的生成
想要获取 Stream 流数据源有很多种方式:
从集合中获取:集合对象(List、Set、Queue 等)的 stream()、parallelStream() 方法可以直接获取 Stream 对象;
从数组中获取:数据对象可以利用 Arrays.stream(T[] array) 或者 Stream.of() 的工具方法获取 Stream 对象;
从 IO 流中获取:BufferedReader 提供了 lines() 方法可以逐行获取 IO 流里面的数据;
静态工厂方法:Stream.of(Object[])、IntStream.range(int, int)、Stream.iterate(Object, UnaryOperator) 等静态工厂方法可以提供 Stream 对象;
Files 类的操作路径的方法:如 list、find、walk 等;
随机数流:Random.ints();
其他诸如 Random.ints()、BitSet.stream()、Pattern.splitAsStream(java.lang.CharSequence)、JarFile.stream() 等方法;
更底层的使用 StreamSupport,它提供了将 Spliterator 转换成流的方法。
Stream 流的中间操作 (Intermediate)
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射 / 过滤,然后返回一个新的流,交给下一个操作使用。
这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。只有在 Terminal 操作执行时才会真正的执行这些 Intermediate 操作。
常用的 Intermediate 操作有:map (mapToInt, flatMap 等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
Stream 流的执行操作 (Terminal)
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。
Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
常用的 Terminal 操作有:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
Short-circuiting
当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
常用的 Short-circuiting 操作有:anyMatch、allMatch、noneMatch、findFirst、findAny、limit。
生成 Stream 流数据源
集合对象 -> Stream
集合对象本身提供了 stream() 和 parallelStream() ,两个方法可以直接获取 Stream 流
Stream<String> listStream = list.stream();
Stream<String> listParallelStream = list.parallelStream();
Stream<String> setStream = set.stream();
Stream<String> setParallelStream = set.parallelStream();
数组对象 -> Stream
数组对象转换需要利用工具类 Arrays、 Stream 的静态方法
Stream<String> arrayStream = Arrays.stream(array);
Stream<String> arrayStream1 = Stream.of(array);
IO 流 -> Stream
IO 流可以包装成 BufferedReader 转换为 Stream
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("input.txt"), StandardCharsets.UTF_8));
Stream<String> stream = reader.lines();
流对象提供的构造方法
IntStream intStream = IntStream.range(1, 4);
DoubleStream doubleStream = DoubleStream.builder().add(1.1).add(2.1).add(3.1).add(4.1).build();
LongStream longStream = LongStream.of(1L, 2L, 3L, 4L);
Stream 流的 Intermediate 操作
示例代码: StreamIntermediateExample.java
map
map 的作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。
// 转大写
List<String> stringList = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList()); // [ABC, EFG, HIJ]
// 数据计算
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9).stream()
.map(n -> n * n)
.collect(Collectors.toList()); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
// 获取对象属性
List<String> list = list.stream()
.map(Item::getDetail).map(ItemDetail::getValue)
.collect(Collectors.toList()); // [v1, v5, v3, v2, v4]
flatMap
flatMap 把 input Stream 中的层级结构扁平化
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
// 将集合对象里面的数据拿出来转换为扁平结构
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream()); // [1, 2, 3, 4, 5, 6]
filter
filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
// 对 2 取模等于 0 的是偶数,filter 留下数字中的偶数
Integer[] evens = Stream.of(sixNums)
.filter(n -> n % 2 == 0)
.toArray(Integer[]::new); // [2, 4, 6]
distinct
distinct 是对元素进行去重,去重是利用了对象的 hashCode() 和 equals() 方法。
如果 distinct()正在处理有序流,那么对于重复元素,将保留以遭遇顺序首先出现的元素,并且以这种方式选择不同元素是稳定的。
在无序流的情况下,不同元素的选择不一定是稳定的,是可以改变的。distinct()执行有状态的中间操作。
在有序流的并行流的情况下,保持 distinct()的稳定性是需要很高的代价的,因为它需要大量的缓冲开销。
如果我们不需要保持遭遇顺序的一致性,那么我们应该可以使用通过 BaseStream.unordered()方法实现的无序流。
Integer[] nums = {1, 1, 2, 3, 4, 5, 4, 5, 6};
Integer[] evens = Stream.of(nums)
.distinct()
.toArray(Integer[]::new);// [1, 2, 3, 4, 5, 6]
sorted
sorted 方法用于排序,利用 Comparator 类的静态方法可以快速构造一个比较器实现排序。
List<Integer> list = Arrays.asList(5, 2, 4, 8, 6, 1, 9, 3, 7);
// sorted() 无参方法为自然排序
List<Integer> sorted = list.stream().sorted().collect(Collectors.toList());// [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 使用 Comparator.reverseOrder() 获得一个自然逆序比较器,用于逆序排序
List<Integer> reverse = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());// [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 使用 Comparator.comparing() 获取一个自定义比较器,显现自定义对象的排序
List<Item> codeSorted = StreamIntermediateExample.newItems().stream()
.sorted(Comparator.comparing(Item::getCode))
.collect(Collectors.toList());
// [Item(name=Name1, code=1, number=1.1, detail=ItemDetail(id=101, value=v1)), Item(name=Name2, code=2, number=2.2, detail=ItemDetail(id=202, value=v2)), Item(name=Name3, code=3, number=3.3, detail=ItemDetail(id=303, value=v3)), Item(name=Name4, code=4, number=4.4, detail=ItemDetail(id=404, value=v4)), Item(name=Name5, code=5, number=5.5, detail=ItemDetail(id=505, value=v5))]
List<Item> codeReverse = StreamIntermediateExample.newItems().stream()
.sorted(Comparator.comparing(Item::getCode).reversed())
.collect(Collectors.toList());
// [Item(name=Name5, code=5, number=5.5, detail=ItemDetail(id=505, value=v5)), Item(name=Name4, code=4, number=4.4, detail=ItemDetail(id=404, value=v4)), Item(name=Name3, code=3, number=3.3, detail=ItemDetail(id=303, value=v3)), Item(name=Name2, code=2, number=2.2, detail=ItemDetail(id=202, value=v2)), Item(name=Name1, code=1, number=1.1, detail=ItemDetail(id=101, value=v1))]
unordered
某些流的返回的元素是有确定顺序的,我们称之为 encounter order。这个顺序是流提供它的元素的顺序,比如数组的 encounter order 是它的元素的排序顺序,List 是它的迭代顺序 (iteration order),对于 HashSet,它本身就没有 encounter order。
一个流是否是 encounter order 主要依赖数据源和它的中间操作,比如数据源 List 和 Array 上创建的流是有序的 (ordered),但是在 HashSet 创建的流不是有序的。
sorted() 方法可以将流转换成 encounter order 的,unordered 可以将流转换成 encounter order 的。
注意,这个方法并不是对元素进行排序或者打散,而是返回一个是否 encounter order 的流。
可以参见 stackoverflow 上的问题: stream-ordered-unordered-problems
除此之外,一个操作可能会影响流的有序,比如 map 方法,它会用不同的值甚至类型替换流中的元素,所以输入元素的有序性已经变得没有意义了,但是对于 filter 方法来说,它只是丢弃掉一些值而已,输入元素的有序性还是保障的。
对于串行流,流有序与否不会影响其性能,只是会影响确定性 (determinism),无序流在多次执行的时候结果可能是不一样的。
对于并行流,去掉有序这个约束可能会提高性能,比如 distinct、groupingBy 这些聚合操作。
peek
peek() 接受一个 Consumer 消费者方法,而 map() 接受一个 Function 方法;Consumer 方法返回值是 void,而 Function 方法有返回值,peek 和 map 方法的区别主要在于流处理过程中返回值的不同。
peek() 方法是 Intermediate 方法,而 forEach() 方法是 Terminal 方法;如果 peek 方法后没有 Terminal 方法,则 peek 并不会真正的执行,forEach 方法则会立即执行。
forEach 和 peek 都是接受 Consumer 对象的,因此如果在 Stream 流处理的过程中做一些数据操作或者打印操作,选择 peek 方法,该方法还会返回 Stream 流,用于下一步处理;如果已经是处理的最后一步,则选择 forEach 用于最终执行整个流。
// [Abc, efG, HiJ] -> [Abc, efG, HiJ]
List<String> peek = StreamIntermediateExample.newStringList().stream()
.peek(str -> {
if ("Abc".equals(str)) {
str = str.toUpperCase();
}
}).collect(Collectors.toList());
peek 方法对对象的修改,会影响到集合里面的元素,但如果集合中是 String 这种,则不会改变,
因为修改后的 String 在常量池中是另一个对象,由于 Consumer 无法返回该对象,Stream 内的元素仍然指向原来的 String。
对对象的修改则是改变堆中对象的数据,对象的引用并没有发生变化,Stream 中的元素任然指向原对象,只是对象内部已经发生了改变。
// [Name1, Name5, Name3, Name2, Name4] -> [xxx, Name5, Name3, Name2, Name4]
List<String> peek1 = StreamIntermediateExample.newItems().stream()
.peek(item -> {
if (item.getCode() == 1) {
item.setName("xxx");
}
})
.map(Item::getName)
.collect(Collectors.toList());
limit
limit 方法会对一个 Stream 进行截断操作,获取其前 N 个元素,如果原 Stream 中包含的元素个数小于 N,那就获取其所有的元素;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 截取指定元素位置以内的元素
List<Integer> limit2 = numbers.stream().limit(2).collect(Collectors.toList());// [1, 2]
List<Integer> limit6 = numbers.stream().limit(6).collect(Collectors.toList());// [1, 2, 3, 4, 5, 6]
List<Integer> limit8 = numbers.stream().limit(8).collect(Collectors.toList());// [1, 2, 3, 4, 5, 6]
skip
skip 方法会返回一个丢弃原 Stream 的前 N 个元素后剩下元素组成的新 Stream,如果原 Stream 中包含的元素个数小于 N,那么返回空 Stream;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 忽略指定元素位置以内的元素
List<Integer> skip2 = numbers.stream().skip(2).collect(Collectors.toList());// [3, 4, 5, 6]
List<Integer> skip6 = numbers.stream().skip(6).collect(Collectors.toList());// []
List<Integer> skip8 = numbers.stream().skip(8).collect(Collectors.toList());// []
parallel
parallel stream 是基于 fork/join 框架的,简单点说就是使用多线程来完成的,使用 parallel stream 时要考虑初始化 fork/join 框架的时间,
如果要执行的任务很简单,那么初始化 fork/join 框架的时间会远多于执行任务所需时间,也就导致了效率的降低。
根据附录 doug Lee 的说明,任务数量*执行方法的行数》=10000 或者执行的是消耗大量时间操作(如 io/ 数据库)才有必要使用
Java 8 为 ForkJoinPool 添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。
它是 ForkJoinPool 类型上的一个静态元素,它拥有的默认线程数量等于运行计算机上的处理器数量。
当调用 Arrays 类上添加的新方法时,自动并行化就会发生。
比如用来排序一个数组的并行快速排序,用来对一个数组中的元素进行并行遍历。自动并行化也被运用在 Java 8 新添加的 Stream API 中。
并行流是 JDK8 对多线程的应用,但是难以控制,要想用好并行流,需要深入理解 ForkJoinPool 。
以下的例子详阅:深入浅出 parallelStream
System.out.println("Hello World!");
// 构造一个 10000 个元素的集合
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
// 统计并行执行 list 的线程
Set<Thread> threadSet = new CopyOnWriteArraySet<>();
// 并行执行
list.stream().parallel().forEach(integer -> {
Thread thread = Thread.currentThread();
// System.out.println(thread);
// 统计并行执行 list 的线程
threadSet.add(thread);
});
System.out.println("threadSet 一共有" + threadSet.size() + "个线程"); // 6
System.out.println("系统一个有" + Runtime.getRuntime().availableProcessors() + "个 cpu"); // 8
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list1.add(i);
list2.add(i);
}
Set<Thread> threadSetTwo = new CopyOnWriteArraySet<>();
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread threadA = new Thread(() -> {
list1.stream().parallel().forEach(integer -> {
Thread thread = Thread.currentThread();
// System.out.println("list1" + thread);
threadSetTwo.add(thread);
});
countDownLatch.countDown();
});
Thread threadB = new Thread(() -> {
list2.stream().parallel().forEach(integer -> {
Thread thread = Thread.currentThread();
// System.out.println("list2" + thread);
threadSetTwo.add(thread);
});
countDownLatch.countDown();
});
threadA.start();
threadB.start();
countDownLatch.await();
System.out.println("threadSetTwo 一共有" + threadSetTwo.size() + "个线程"); // 9
System.out.println("---------------------------");
// [Thread[main,5,main],
// Thread[ForkJoinPool.commonPool-worker-3,5,main],
// Thread[ForkJoinPool.commonPool-worker-1,5,main],
// Thread[ForkJoinPool.commonPool-worker-4,5,main],
// Thread[ForkJoinPool.commonPool-worker-5,5,main],
// Thread[ForkJoinPool.commonPool-worker-2,5,main]]
System.out.println(threadSet);
// [Thread[ForkJoinPool.commonPool-worker-6,5,main],
// Thread[ForkJoinPool.commonPool-worker-7,5,main],
// Thread[Thread-0,5,],
// Thread[ForkJoinPool.commonPool-worker-5,5,main],
// Thread[Thread-1,5,],
// Thread[ForkJoinPool.commonPool-worker-4,5,main],
// Thread[ForkJoinPool.commonPool-worker-3,5,main],
// Thread[ForkJoinPool.commonPool-worker-2,5,main],
// Thread[ForkJoinPool.commonPool-worker-1,5,main]]
System.out.println(threadSetTwo);
System.out.println("---------------------------");
threadSetTwo.addAll(threadSet);
// [Thread[ForkJoinPool.commonPool-worker-6,5,main],
// Thread[ForkJoinPool.commonPool-worker-7,5,main],
// Thread[Thread-0,5,],
// Thread[ForkJoinPool.commonPool-worker-5,5,main],
// Thread[Thread-1,5,],
// Thread[ForkJoinPool.commonPool-worker-4,5,main],
// Thread[ForkJoinPool.commonPool-worker-3,5,main],
// Thread[ForkJoinPool.commonPool-worker-2,5,main],
// Thread[ForkJoinPool.commonPool-worker-1,5,main],
// Thread[main,5,main]]
// 执行 forEach 本身的线程也作为线程池中的一个工作线程
System.out.println(threadSetTwo);
System.out.println("threadSetTwo 一共有" + threadSetTwo.size() + "个线程");
System.out.println("系统一个有" + Runtime.getRuntime().availableProcessors() + "个 cpu");
sequential
通过 parallel() 方法可以将串行流转换成并行流,sequential() 方法将流转换成串行流。
顺序流和并行流相对,这种使用的方法很少,暂时没有研究。
Stream 流的 Terminal 操作
示例代码: StreamTerminalExample.java
collect
collect 方法是 Terminal 操作,可以将 Stream 流转换为集合,Collectors 中提供了一些便捷的生成 Collector 的方法, 例如 toList()
用于生成 List 列表, toSet()
可以用于生成 Set 堆, toMap()
可以用于生成 Map。
List<String> list = Arrays.asList("apple", "orange", "banana", "pear");
List<String> collectList = list.stream().filter(s -> s.length() > 5).collect(Collectors.toList());//[orange, banana]
Set<String> collectSet = list.stream().filter(s -> s.length() > 5).collect(Collectors.toSet());// [orange, banana]
Collectors.toMap()
有三个重构方法,推荐至少使用三个参数的 toMap() 方法, BinaryOperator<U>mergeFunction
这个参数有利于解决,生成 Map 时的主键重复问题,避免因为源数据问题产生问题。
Map<Integer, Item> collectMap1 = items.stream()
.collect(Collectors.toMap(Item::getCode, Function.identity()));
Map<Integer, Item> collectMap2 = StreamConstant.newItems().stream()
.collect(Collectors.toMap(Item::getCode, Function.identity(), (a, b) -> a));
toCollection()
可以用于生成各种各样自定义的集合结构。
Stack<String> collect = items.stream()
.map(Item::getName)
.collect(Collectors.toCollection(Stack::new));
Collector 包含四种不同的操作:supplier(初始构造器), accumulator(累加器), combiner(组合器), finisher(终结者)。
简单分组:
Map<Integer, List<Item>> groupingByCollect = StreamConstant.newItems().stream()
.collect(Collectors.groupingBy(Item::getCode));
平均值:
Double average = StreamConstant.newItems().stream()
.collect(Collectors.averagingInt(Item::getCode));
统计:
IntSummaryStatistics summaryStatistics = StreamConstant.newItems().stream()
.collect(Collectors.summarizingInt(Item::getCode));// IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}
拼接(三个参数分别是:连接符、字符串前缀、字符串后缀):
String join = list.stream()
.collect(Collectors.joining(" and ", "The ", " are fruits"));// The apple and orange and banana and pear are fruits
Collector.of()
方法可以创建了一个新的 collector,我们必须给这个 collector 提供四种功能:supplier, accumulator, combiner,finisher.
supplier 初始化构造分割符;accumulator 处理数据,并叠加数据;combiner 进行数据连接,finisher 生成最终数据。
Collector<Item, StringJoiner, String> personNameCollector =
Collector.of(
() -> new StringJoiner(" | "), // supplier
(j, p) -> j.add(p.getName().toUpperCase()), // accumulator
(j1, j2) -> j1.merge(j2), // combiner
StringJoiner::toString); // finisher
String names = StreamConstant.newItems().stream()
.collect(personNameCollector);
System.out.println(names); // NAME1 | NAME5 | NAME3 | NAME2 | NAME4
toArray
toArray()
方法可以将流中的数据放入一个数组中。无参方法只能生成 Object[]
对象数组,单参方法可以指定生成的数组类型。
List<String> list = Arrays.asList("apple", "orange", "banana", "pear");
Object[] objects = list.stream().filter(s -> s.length() > 5).toArray();
String[] strings = list.stream().filter(s -> s.length() > 5).toArray(String[]::new);
forEach/forEachOrdered
forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
一般认为,forEach 和常规 for 循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。
// Java 8
list.stream()
.filter(s -> s.length() > 5)
.forEach(System.out::println);
// Pre-Java 8
for (String s : list) {
if (s.length() > 5) {
System.out.println(s);
}
}
forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。
Stream<String> stream = list.stream().filter(s -> s.length() > 5);
stream.forEach(element -> System.out.println("1: "+element));// ok
stream.forEach(element -> System.out.println("2: "+element));// java.lang.IllegalStateException: stream has already been operated upon or closed
要想实现上述类似功能,可以使用 peek 方法,peek 是中间方法,流还没有被消费掉。或者利用 Supplier 提供者,Supplier 的 get 方法可以构造新的 Stream。
Supplier<Stream<String>> streamSupplier= () -> (list.stream().filter(s -> s.length() > 5));
streamSupplier.get().forEach(element -> System.out.println("1: "+element));
streamSupplier.get().forEach(element -> System.out.println("2: "+element));
forEachOrdered 是为了保证执行后数据的有序性。
reduce
Stream 的 reduce 方法,返回单个的结果值,并且 reduce 操作每处理一个元素总是创建一个新值。常用的方法有 average、sum、min、max、count,都可以使用 reduce 方法实现。
<U> U reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner)
#第一个参数 identity 返回实例 u,传递你要返回的 U 类型对象的初始化实例 u, 用于提供一个循环计算的初始值
#第二个参数累加器 accumulator,可以使用二元ℷ表达式(即二元 lambda 表达式),声明你在 u 上累加你的数据来源 t 的逻辑
#例如 (u,t)->u.sum(t), 此时 lambda 表达式的行参列表是返回实例 u 和遍历的集合元素 t,函数体是在 u 上累加 t
#BinaryOperator 的函数方法为 apply(U u, T t), 第一个参数为上次函数计算的返回值,第二个参数为 Stream 中的元素,函数方法会将两个值计算 apply,得到的值赋值给下次执行该方法的第一个参数
#第三个参数组合器 combiner,同样是二元ℷ表达式,(u,t)->u
#lambda 表达式行参列表同样是 (u,t),函数体返回的类型则要和第一个参数的类型保持一致
具体的一个示例:
Supplier<Stream<Integer>> supplier = () -> (Stream.of(1, 2, 3, 4).filter(p -> p > 2));
List<Integer> result = supplier.get()
.collect(() -> new ArrayList<>(), (list, item) -> list.add(item), (one, two) -> one.addAll(two));
System.out.println(result);// [3, 4]
/* 或者使用方法引用 */
result = supplier.get().collect(ArrayList::new, List::add, List::addAll);
System.out.println(result);// [3, 4]
min/max/count
一般比较大小需要比较器 Comparator, min 和 max 返回值类型是 Optional。
count 是对满足条件的数据进行统计,计算次数。等价于 returnmapToLong(e->1L).sum();
Supplier<Stream<Integer>> supplier = () -> (Stream.of(1, 2, 3, 4).filter(p -> p > 2));
Optional<Integer> min = supplier.get().min(Integer::compareTo);// Optional[3]
Optional<Integer> max = supplier.get().max(Integer::compareTo);// Optional[4]
long count = supplier.get().count();// 2
anyMatch/allMatch/noneMatch
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。对清单 13 中的 Person 类稍做修改,加入一个 age 属性和 getAge 方法。
boolean b1 = StreamConstant.newItems().stream().map(Item::getCode).anyMatch(i -> i > 3);// true
boolean b2 = StreamConstant.newItems().stream().map(Item::getCode).anyMatch(i -> i > 5);// false
boolean b3 = StreamConstant.newItems().stream().map(Item::getCode).allMatch(i -> i > 3);// false
boolean b4 = StreamConstant.newItems().stream().map(Item::getCode).allMatch(i -> i > 5);// false
boolean b5 = StreamConstant.newItems().stream().map(Item::getCode).noneMatch(i -> i > 3);// false
boolean b6 = StreamConstant.newItems().stream().map(Item::getCode).noneMatch(i -> i > 5);// true
findFirst/findAny
findFirst 是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。
findAny 是搜索到任何一个符合条件的结果返回,因为流可能是并行的,因此顺序可能不是确定的。如果顺序是确定的,使用 findFirst 可以方便的获取第一个元素。
Optional<String> first = StreamConstant.newStringList().stream().findFirst();// Optional[Abc]
String any = StreamConstant.newStringList().stream()
.filter(s -> s.length() > 5).findAny().orElse("ERROR");// ERROR
有效的特殊用法
自定义去重
利用 Map 的 key 不能重复的特性进行去重,实现下方静态方法,在需要的使用结合 filter 和 distinctByKey 方法进行去重。
示例代码: DistinctByKey.java
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
// putIfAbsent 如果 map 中有值,则返回原值,新值也不会放入 map 中,如果原来没有值,则返回 null,本次 put 的值也会放入 map 中
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
使用的时候只需要使用 filter 过滤掉重复项:
示例代码:StreamSpecialExample.java
items.stream().filter(distinctByKey(Item::getName)).collect(Collectors.toList());
求和
BigDecimal 对象求和:
// 计算 总金额
BigDecimal totalMoney = stream().map(Item::getDetail).map(ItemDetail::getMoney)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.err.println("totalMoney:" + totalMoney.setScale(2, RoundingMode.HALF_UP)); //totalMoney:166.50
基本类型求和:
// 计算 数量
double sum = stream().mapToDouble(Item::getNumber).sum();
System.err.println("sum:" + sum); //sum:16.5
参考文章
Java 8 中的 Streams API 详解
Java Stream 详解 ——鸟窝
Java 8 Stream 教程
References
zzycreate/java-convert-example: https://github.com/zzycreate/java-convert-example
StreamIntermediateExample.java: https://github.com/zzycreate/java-convert-example/blob/master/src/main/java/io/github/zzycreate/example/stream/StreamIntermediateExample.java
stream-ordered-unordered-problems: https://stackoverflow.com/questions/21350195/stream-ordered-unordered-problems
深入浅出 parallelStream: https://blog.csdn.net/u011001723/article/details/52794455
StreamTerminalExample.java: https://github.com/zzycreate/java-convert-example/blob/master/src/main/java/io/github/zzycreate/example/stream/StreamTerminalExample.java
DistinctByKey.java: https://github.com/zzycreate/java-convert-example/blob/master/src/main/java/io/github/zzycreate/example/stream/tools/DistinctByKey.java
StreamSpecialExample.java: https://github.com/zzycreate/java-convert-example/blob/master/src/main/java/io/github/zzycreate/example/stream/StreamSpecialExample.java
Java 8 中的 Streams API 详解: https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html
Java Stream 详解 ——鸟窝: https://colobu.com/2016/03/02/Java-Stream/#%E6%8E%92%E5%BA%8F_Ordering
Java 8 Stream 教程: https://www.jianshu.com/p/0c07597d8311
本文作者赵振耀,原文链接 https://zzycreate.github.io/2019/07/14/java-convert-example-stream/,相关权利归原作者所有,本次发布已征得作者许可。
更多推荐
所有评论(0)