Java8中两个最为重要特性:第一个是Lambda表达式,另一个是Stream API。
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
Stream流 是 JDK1.8 加入的新特性,Stream流跟I/O流不同,Stream流用于操作数据源(集合、数组等)所生成的元素序列。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算!
stream 是 java8 卓越的新特性之一,这种风格是将要处理的数据看成一种管道流,在管道中进行一系列的中间操作,然后使用最终操作得到最终想要的数据以及数据结构。由于中间操作以及最终操作的很多方法的入参都是函数式接口,因此,stream 往往配合 lambda 表达式进行使用。
流的数据来源可以是数组、集合等
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
stream特点
-
Stream自己不会存储数据。
-
Stream不会改变源对象。相反,它们会返回一个持有结果的新Stream对象。
-
延迟执行,Stream 会等到需要结果时才会执行。
-
对Stream的操作是以lamnda表达式为参数的。
-
可以是无限的,用 Stream 可以轻松表示全体自然数,这是集合类不可能做到的。
-
stream的聚合、消费或收集操作只能进行一次,再次操作会报错,Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了。
List<String> title = Arrays.asList("Wmyskxz", "Is", "Learning", "Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
// 运行上面程序会报以下错误
/*
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at Test1.main(Tester.java:17)
*/
和list不同,Stream代表的是任意Java对象的序列,且stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。它可以“存储”有限个或无限个元素。
例如:我们想表示一个全体自然数的集合,使用list是不可能写出来的,因为自然数是无线的,不管内存多大也没法放到list中,但是使用Sream就可以。
引言
传统集合的多步遍历代码几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
@SpringBootTest(classes = DemoApplication.class)
class DemoApplicationTests {
@Test
void contextLoads() {
List<String> list = new ArrayList<>();
list.add("左宥");
list.add("尚夏");
list.add("钱後");
list.add("钱理群");
List<String> qianList = new ArrayList<>();
for (String s : list) {
if (s.startsWith("钱")){
qianList.add(s);
}
}
List<String> shortList = new ArrayList<>();
for (String s : qianList) {
if (s.length() < 3){
shortList.add(s);
}
}
for (String s : shortList) {
System.out.println(s);
}
}
// stream的写法
// 很显然,简单明了多了
list.stream()
.filter(s -> s.startsWith("钱"))
.filter(s -> s.length() < 3)
.forEach(System.out::println);
}
流式思想概述
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”
。
图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字 3是最终结果。
这里的 filter 、 map 、 skip 都是在对函数模型
进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
上面的意思就是说,filter 、 map 、 skip都相当于定义,并不会执行该流,只有使用终结方法,例如count时,该流的所有操作才会执行。
备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)
。
获取流方式
java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
-
所有的 Collection 集合都可以通过 stream() 默认方法获取流
-
Stream接口的静态方法
of
可以获取数组对应的流
package com.demo.streamDemo;
import java.util.*;
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
// 1. 根据Collection获取流,其所有实现类均可获取Stream流
List<String> list = new ArrayList<>();
Stream<String> streamList = list.stream();
Set<String> hashSet = new HashSet<>();
Stream<String> streamSet = hashSet.stream();
// 2. map获取流
Map<String,String> hashMap = new HashMap<>();
Stream<String> streamKeys = hashMap.keySet().stream();
Stream<String> streamValues = hashMap.values().stream();
Stream<Map.Entry<String, String>> entryStream = hashMap.entrySet().stream();
// 3. 数组获取流
// 如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of
String[] array = {"叶文洁","罗辑","水滴"};
Stream<String> array1 = Stream.of(array);
// 4. 迭代
// 理论上,使用while循环就可以输出全部自然数了。。。
Stream<Integer> integerStream = Stream.iterate(0, (x) -> (x + 1));
integerStream.limit(10).forEach(System.out::println);
// 5. 生成
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.limit(10).forEach(System.out::print);
}
}
常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
-
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。
-
非终结方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
public static void main(String[] args) {
String[] array = {"叶文洁","罗辑","水滴"};
Stream<String> arrayStream = Stream.of(array);
/**
* 一、筛选与切片
*/
// filter 过滤,将一个流转换成另一个子集流。
arrayStream = Stream.of(array);
// 叶文洁
Stream<String> result = arrayStream.filter(s -> s.startsWith("叶"));
result.forEach(System.out::println);
System.out.println("==============filter================");
// limit,取前几个
arrayStream = Stream.of(array);
Stream<String> limit = arrayStream.limit(2);
// 叶文洁,逻辑
limit.forEach(System.out::println);
System.out.println("=============limit=================");
// skip,跳过前几个
arrayStream = Stream.of(array);
Stream<String> skip = arrayStream.skip(2);
// 水滴
skip.forEach(System.out::println);
System.out.println("=============skip=================");
// distinct 去重,如果是对对象去重,则是利用hashcode 跟 equals 作为重复判断依据
arrayStream = Stream.of(array);
Stream<String> distinct = arrayStream.distinct();
System.out.println("==================distinct=================");
/**
* 二、中间操作
*
*/
// map映射,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
arrayStream = Stream.of("10","12");
Stream<Integer> integerStream = arrayStream.map(Integer::parseInt);
// t t
integerStream.forEach(s -> System.out.println(s instanceof Integer));
System.out.println("============map==================");
// 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
// HeloWrd
List<String> words = Arrays.asList("Hello", "Wrold");
words.stream()
.map(word -> word.split(""))
.flatMap((Arrays::stream)) // 扁平化,把map(Arrays::stream)生成的单个流合并成一个流
.distinct() // 去重,如果是对对象去重,则是利用hashcode 跟 equals 作为重复判断依据
.forEach(System.out::println);
System.out.println("=============flatMap=================");
// 排序 sorted
// sorted(Comparator com)——定制排序
arrayStream = Stream.of("c","b","a");
List<String> collect = arrayStream.sorted().collect(Collectors.toList());
collect.stream().forEach(System.out::println);
System.out.println("===============sorted================");
// 升序
list.stream().sorted(Comparator.comparing(类::属性一));
// 降序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()));
// 多字段排序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二));
System.out.println("===============sorted定制排序================");
// concat组合,将两个流合并成一个流
Stream<String> stringStream = Stream.of("李鸿章");
Stream<String> stringStream2 = Stream.of("张之洞");
Stream<String> concat = Stream.concat(stringStream, stringStream2);
// 李鸿章,张之洞
concat.forEach(System.out::println);
System.out.println("============concat==================");
/**
* 终结方法
*/
// 循环,虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的。
// 经过测试,并行流是不保证元素顺序消费,单行流是保证的,并行流需要保证的话可以使用forEachOrdered()
arrayStream = Stream.of(array);
arrayStream.forEach(System.out::println);
System.out.println("=============forEach=================");
Stream<Integer> integerStream = Stream.iterate(0, (x) -> (x + 1));
// 0,1,2,3,4
integerStream.limit(5).forEach(System.out::println);
System.out.println("-----------------------------------------------");
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
// 3,5,4,2,1
// forEach在并行流中不能保证元素顺序消费
list.stream().parallel().forEach(System.out::println);
// 1,2,3,4,5
list.stream().parallel().forEachOrdered(System.out::print);
// allMatch 用于检测是否全部都满足指定的参数行为,如果全部满足则返回true
// anyMatch 用于检测是否存在一个或多个满足指定的参数行为,如果满足则返回true
// noneMatch 用于检测是否不存在满足指定的参数行为,如果不存在则返回true
// t
arrayStream = Stream.of("周周","周爸","周妈");
boolean allMatch = arrayStream.allMatch(item -> item.startsWith("周"));
arrayStream = Stream.of("周周","周爸","周妈");
// t
boolean anyMatch = arrayStream.anyMatch(item -> item.startsWith("周"));
// f
arrayStream = Stream.of("周周","周爸","周妈");
boolean noneMatch = arrayStream.noneMatch(item -> item.startsWith("周"));
System.out.println(allMatch);
System.out.println(anyMatch);
System.out.println(noneMatch);
System.out.println("========================allMatch==============================");
// findFirst 用于返回满足条件的第一个元素
// findAny 不一定返回第一个,而是返回任意一个
arrayStream = Stream.of("周周","周爸","周妈");
String s = arrayStream.findFirst().get();
arrayStream = Stream.of("周周","周爸","周妈");
String s2 = arrayStream.findAny().get();
System.out.println("====================findFirst========================");
// 计算个数counting
List<String> list = new ArrayList<>();
// 显然,都是0
long COUNT1 = list.size(); //简化版本
long COUNT2 = list.stream().count(); //简化版本
long COUNT3 = list.stream().collect(Collectors.counting()); //原始版本
System.out.println("==========================counting==========================");
// 3
// 这儿实际上就是简化了下面的比较器,a-b是升序,b-a是降序
// 但是这么表示的话,要max有什么用,所以括号中的参数是可以省略的
Stream<Integer> bestStream = Stream.of(1, 2, 3);
Integer max = bestStream.max((a, b) -> a - b).get();
// 1
Stream<Integer> bestStream2 = Stream.of(1, 2, 3);
Integer min = bestStream2.min(Comparator.comparing(Integer::new)).get();
System.out.println("============max min=================");
// mapToInt、mapToLong、mapToDouble
// 6
Integer sum = Arrays.asList(1,2,3).stream().mapToInt(Integer::new).sum();
System.out.println("===================mapToInt===================");
// 平均值averageInt、averageLong、averageDouble
// 2.0
Double avg = Arrays.asList(1,2,3).stream().collect(Collectors.averagingInt(a->a));
// double averageAge = list.stream().collect(Collectors.averagingDouble(User2::getAge));
System.out.println("==================averageInt======================");
// 一次性查询元素个数、总和、最大值、最小值和平均值summarizingInt、summarizingLong、summarizingDouble
// 通过IntSummaryStatistics的getXXX()可以获取到以上的值
IntSummaryStatistics summaryStatistics = Arrays.asList(1,2,3).stream().collect(Collectors.summarizingInt(a->a));
System.out.println("================summarizingInt、summarizingLong、summarizingDouble=====================");
// 字符串拼接joining
// 1,2,3
String collect1 = Arrays.asList("1", "2", "3").stream().collect(Collectors.joining(","));
System.out.println("===========================joining============================");
// 分组groupingBy 如将user根据学校分组、先按学校分再按年龄分、每个大学的user人数、每个大学不同年龄的人数
// Map<String, List<User2>> collect1 = list.stream()
// .collect(Collectors.groupingBy(User2::getSchool));
// Map<String, Map<Integer, Long>> collect2 = list.stream()
// .collect(Collectors.groupingBy(User2::getSchool,Collectors.groupingBy(User2::getAge, Collectors.counting())));
// Map<String, Long> collect3 = list.stream()
// .collect(Collectors.groupingBy(User2::getSchool, Collectors.counting()));
// Map<String, Map<Integer, Map<String, Long>>> collect4 = list.stream()
// .collect(Collectors.groupingBy(User2::getSchool,Collectors.groupingBy(User2::getAge, Collectors.groupingBy(User2::getName,Collectors.counting()))));
// 分区partitioningBy 分区可以看做是分组的一种特殊情况,在分区中key只有两种情况:true或false,目的是将待分区集合按照条件一分为二
// Map<Boolean, List<User2>> collect5 = list.stream()
// .collect(Collectors.partitioningBy(user -> "清华大学".equals(user.getSchool())));
//
//
// reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。
// 这是一个最终操作,允许通过指定的函数来将 stream 中的多个元素规约为一个元素,
// 规越后的结果是通过 Optional 接口表示的。
// 归约 reduce 现在我的目标不是返回行一个新的集合,而是希望对经过参数化操作后的集合进进一步的运算,那么我们可用对集合实施归约操作。
// Integer ages1 = list.stream()
// .filter(student -> "清华大学".equals(student.getSchool())).mapToInt(User2::getAge).sum();
// Integer ages2 = list.stream()
// .filter(student -> "清华大学".equals(student.getSchool())).map(User2::getAge).reduce(0,(a,c)->a+c);
// Integer ages3 = list.stream()
// .filter(student -> "清华大学".equals(student.getSchool())).map(User2::getAge).reduce(0,Integer::sum);
// Integer ages4 = list.stream()
// .filter(student -> "清华大学".equals(student.getSchool())).map(User2::getAge).reduce(Integer::sum).get();
/**
* 聚合操作
*/
List<Integer> list = Arrays.asList(1,2,3);
// 6
System.out.println(list.stream().reduce(0, Integer::sum));
// 对 list 元素求和,然后加上 10000
// 10006
System.out.println(list.stream().reduce(10000, Integer::sum));
// 12-1-2-3=6
System.out.println(list.stream().reduce(12, (a, b) -> a - b));
// 12/1/2/3=2
System.out.println(list.stream().reduce(12, (a, b) -> a / b));
// 12*1*2*3=72
System.out.println(list.stream().reduce(12, (a, b) -> a * b));
// 最大和最小
System.out.println(list.stream().reduce(0, Integer::min));
System.out.println(list.stream().reduce(0, Integer::max));
// collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
/*
收集 前面利用collect(Collectors.toList())是一个简单的收集操作,是对处理结果的封装,对应的还有toSet、toMap,这
些方法均来自于java.util.stream.Collectors,我们可以称之为收集器。
收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,
这些收集器广义上均基于Collectors.reducing()实现。
*/
/**
* 待探索方法
*/
// parallelStream: 返回并行数据流, 集合作为其源
// peek、parallel、sequential、unordered
// toArray
}
方便的并行处理
Java 8 中不仅提供了方便的一些流操作(比如过滤、排序之类的),更重要的是对于并行处理有很好的支持,只需要加上 .parallel()
就行了!例如我们使用下面程序来说明一下多线程流操作的方便和快捷,并且与单线程做了一下对比
COPYpublic class StreamParallelDemo {
/** 总数 */
private static int total = 100_000_000;
public static void main(String[] args) {
System.out.println(String.format("本计算机的核数:%d", Runtime.getRuntime().availableProcessors()));
// 产生1000w个随机数(1 ~ 100),组成列表
Random random = new Random();
List<Integer> list = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
list.add(random.nextInt(100));
}
long prevTime = getCurrentTime();
list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("单线程计算耗时:%d", getCurrentTime() - prevTime));
prevTime = getCurrentTime();
// 只需要加上 .parallel() 就行了
list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("多线程计算耗时:%d", getCurrentTime() - prevTime));
}
private static long getCurrentTime() {
return System.currentTimeMillis();
}
}
以上程序分别使用了单线程流和多线程流计算了一千万个随机数的和,输出如下:
本计算机的核数:8
655028378
单线程计算耗时:4159
655028378
多线程计算耗时:540
并行流的内部使用了默认的 ForkJoinPool 分支/合并框架,它的默认线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors() 得到的(当然我们也可以全局设置这个值)。我们也不再去过度的操心加锁线程安全等一系列问题
部分内容转载自:
https://blog.csdn.net/weixin_40294256/article/details/126338618
https://www.jianshu.com/p/e28bf4e2096e
https://www.cnblogs.com/wmyskxz/p/13527583.html
https://zhuanlan.zhihu.com/p/357504623
https://www.cnblogs.com/kuanglongblogs/p/11230250.html
之前看到过这样一则故事,说是一名大学生在北京毕业之后,选择在北京就业,但是几年过去了也不见往家里寄钱,过年过节也从未回过家,直到有一天他的同乡同学,在北京一个昏暗的地下室里找到他,整个人蜷缩在一张小床上,看到这一幕的同学感到很悲凉,二十六七的年纪,本应该是风华正茂锐气十足的阳光青年,此时却在昏暗的地下室里睡大觉,这不就是一种人生路上的黑暗吗?当问道都混成这样了,为什么不回家乡去发展时,那名大学生猛地从床上坐起来大吼道:我既然出来了,就死都不会回去。满眼红血色的大学生表情狰狞的看着同学,随后便一头扎到被子里,撕心裂肺的痛哭起来。
https://new.qq.com/omn/20220323/20220323A03K4H00.html?pgv_ref=aio2015&ptlang=2052