作者:程序员小杰
什么是 Stream
Stream
翻译称为 “流”,是 Java8 的新特性之一。 Stream
将要处理的元素看作是流,这时可以借助 Stream API
对流中的元素进行中间操作,比如:筛选、排序、排序等。
特点
- 不是数据结构,不会保存数据,只会操作数据。
- 不会改变数据源,它会将操作后的数据保存到另外一个对象中。
- 延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
三个步骤
- 1、创建Stream: 从数据源中获取一个流,这个数据源可以是集合、数组。
- 2、中间操作:操作链,可以对流进行数据处理。
- 3、终止操作:执行终止操作,才会执行中间操作的操作链,终端操作结束后流无法再次使用,会产生一个新的结果。
接下来就介绍一下三个步骤的基本用法。
具体用法
1、流的常用创建方法
- 1.1 使用
Collection
系列集合提供的stream()
和parallelStream()
方法
public static void main(String[] args) {
// 使用Collection系列集合提供的 stream() 和 parallelStream() 方法
ArrayList<String> list = new ArrayList<>();
//返回一个顺序流
Stream<String> stream = list.stream();
//把顺序流转换成并行流
Stream<String> parallel = list.stream().parallel();
//返回一个并行流
Stream<String> stringStream = list.parallelStream();
}
复制代码
- 1.2 使用
Arrays
的静态方法stream()
可以获取数组流
String[] strs = new String[10];
Stream<String> stream2 = Arrays.stream(strs);
复制代码
- 1.3 调用
Stream
类静态方法of()
Stream<String> aa = Stream.of("aa", "bbb", "c");
复制代码
- 1.4 使用静态方法
Stream.iterate()
和Stream.generate()
创建无限流
//迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
//0 2 4 6 8
iterate.limit(5).forEach(System.out::println);
//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
//84 28 71
generate.limit(3).forEach(System.out::println);
复制代码
可以看到创建 Stream
流的方式有很多。但不论是哪种创建方式,Stream
不会保存数据,它只会对数据进行计算。
2、流的中间操作
中间操作可以分为两大类:
- 有状态操作:需要等上一步操作完之后,拿到全部元素后才可操作,如 sorted
- 无状态操作:该操作的数据不受上一步操作的影响,如 filter map
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,不然中间操作不会执行任何处理!
2.1-筛选与切片
List<Integer> integers = Arrays.asList(
1,2,3,4,5,6,7,8,9,10,2,5,7,9,17
);
//流的中间操作
@Test
public void test1() {
/**
* 1.筛选与切片
* filter:过滤流中的某些元素
* limit(n):获取n个元素
* skip(n):跳过n元素,若流中元素不足n个,则返回一个空流,配合limit(n)可实现分页
* distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
*/
// 创建流
Stream<Integer> stream = integers.stream();
// 中间操作
Stream<Integer> integersStream = stream
.filter((t) -> t > 5) //6 7 8 9 10 7 9 17
.distinct() //6 7 8 9 10 17
.skip(2) //8 9 10 17
.limit(3); //8 9 10
integersStream.forEach(System.out::println);
}
复制代码
2.2 映射
1、map
- 1、将每个元素转换为大写
@Test
public void test2() {
/**
* 1.映 射
* map(Function f):接收一个函数作为参数,该函数会被应用到每个
* 元素上,并将其映射成一个新的元素。
*/
// 将每个元素转换为大写
List<String> list = Arrays.asList("a,a=","b,b=","cc=","d,d=");
List<String> collect = list.stream()
.map(str -> str.toUpperCase()).collect(Collectors.toList());
// str -> str.toUpperCase() 可以简写为 String::toUpperCase
System.out.println(collect);
//结果:[A,A=, B,B=, CC=, D,D=]
}
复制代码
- 2、提取员工名称
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.55),
new Employee("王五",50,6666.66),
new Employee("赵六",16,3333.33),
new Employee("田七",8,7777.77)
);
//提取员工名称
List<String> collect1 = employees.stream()
//.map(employee -> employee.getName())
//类::实例方法名
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(collect1);
结果:[张三, 李四, 王五, 赵六, 田七]
复制代码
2、mapToInt
@Test
public void test3() {
/**
* mapToInt((ToIntFunction f)
* 接收一个函数作为参数,该函数会被应用到每个元素上,
* 产生一个新的 IntStream
*/
//输出每个元素的长度
List<String> list = Arrays.asList("a,a","b,b","cc","d,d");
IntStream intStream = list.stream()
.mapToInt(str -> {
return str.length();
});
intStream.forEach(System.out::println);
}
结果:
3
3
2
3
复制代码
mapToDouble
、mapToLong
其余两种就不在介绍。
3、flatMap
@Test
public void test4() {
/**
* flatMap(Function f)
* 接收一个函数作为参数,将流中的每个值都换成另一个流,
* 然后把所有流连接成一个流
*/
// 将每个元素根据,进行分割
List<String> list = Arrays.asList("a,a=","b,b=","cc=","d,d=");
Stream<String> stringStream = list.stream()
.flatMap(str -> {
String[] split = str.split(",");
Stream<String> stream = Arrays.stream(split);
return stream;
});
List<String> collect = stringStream.collect(Collectors.toList());
System.out.println(collect);
}
结果:[a, a=, b, b=, cc=, d, d=]
复制代码
2.3 排序
1、sorted
@Test
public void test5() {
/**
* sorted()
* 产生一个新流,其中按自然顺序排序
*/
List<String> list = Arrays.asList("bb","cc","ee","aa");
list.stream().sorted().forEach(System.out::println);
}
结果:
aa
bb
cc
ee
复制代码
2、sorted(Comparator com)
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.55),
new Employee("王五",50,6666.66),
new Employee("赵六",15,3333.33),
new Employee("田七",18,7777.77)
);
@Test
public void test6() {
/**
* sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
*/
//如果年龄一致,就按照姓名排序,如果不一致,就按照年龄升序排序
employees.stream().sorted((e1,e2)->{
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else{
return e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
}
结果:
name='赵六', age=15, salary=3333.33
name='张三', age=18, salary=9999.99
name='田七', age=18, salary=7777.77
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
复制代码
3、流的终止操作
- 终端操作会从流水线中产生结果。其结果可以是任何不是流的值,例
如:List、Integer,String。
- 流进行了终止操作后,不能再次使用。
1、匹配和查找
allMatch
判断数据流中所有元素是否与 aa 相等,全部相等则返回true
List<String> list = Arrays.asList("aa","bb","cc","dd");
@Test
public void test1(){
boolean aa = list.stream()
.allMatch(obj -> obj.equals("aa"));
System.out.println(aa);
}
结果:false
复制代码
接下来将 List 的值全部修改为 “aa”
List<String> list = Arrays.asList("aa","aa","aa","aa");
@Test
public void test1(){
boolean aa = list.stream()
.allMatch(obj -> obj.equals("aa"));
System.out.println(aa);
}
结果:true
复制代码
anyMatch
判断数据流中所有元素是否有一个与 aa 相等,相等则返回 true
List<String> list = Arrays.asList("aa","bb","cc","dd");
@Test
public void test2(){
boolean aa = list.stream()
.anyMatch(obj -> obj.equals("aa"));
System.out.println(aa);
}
结果:true
复制代码
noneMatch
判断数据流中所有元素是否全部与 aa1 相等,一个都没有匹配成功则返回 true。
List<String> list = Arrays.asList("aa","bb","cc","dd");
@Test
public void test3(){
boolean aa = list.stream()
.noneMatch(obj -> obj.equals("aa1"));
System.out.println(aa);
}
结果:true
复制代码
findFirst
返回第一个元素
List<String> list = Arrays.asList("aa","bb","cc","dd");
@Test
public void test4(){
Optional<String> first = list.stream()
.findFirst();
System.out.println(first.get());
}
结果:aa
复制代码
findAny
返回当前流中的任意元素
List<String> list = Arrays.asList("bb","aa","cc","dd","d","c","da");
@Test
public void test5(){
Optional<String> first = list.stream()
.findAny();
System.out.println(first.get());
}
结果:bb
复制代码
由于是顺序流所以每次返回的都是第一个元素。使用并行流:
@Test
public void test5(){
Optional<String> first = list.parallelStream()
.findAny();
System.out.println(first.get());
}
结果:d
复制代码
count
返回流中元素总数
List<Integer> numberList = Arrays.asList(1,23,33,4,5,66,21);
@Test
public void test6(){
long count = numberList.stream()
.count();
System.out.println(count);
}
结果:7
复制代码
max(Comparator c)
返回流中最大值
@Test
public void test7(){
Optional<Integer> max = numberList.stream()
.max(Integer::compareTo);
System.out.println(max.get());
}
结果:66
复制代码
min(Comparator c)
返回流中最小值
@Test
public void test8(){
Optional<Integer> max = numberList.stream()
.min(Integer::compareTo);
System.out.println(max.get());
}
结果:1
复制代码
forEach(Consumer c)
内部迭代(使用 Collection
接口需要用户去做迭代,称为外部迭代)。
@Test
public void test5() {
/**
* sorted()
* 产生一个新流,其中按自然顺序排序
*/
List<String> list = Arrays.asList("bb","cc","ee","aa");
list.stream().sorted().forEach(System.out::println);
}
复制代码
4、规约
-
T reduce(T i, BinaryOperator<T> a)
:可以将流中元素反复结合起来,得到一个值。返回 T
List<Integer> numberList2 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
@Test
public void test9(){
Integer reduce = numberList2.stream()
.reduce(0, (x1, x2) -> x1 + x2);
System.out.println(reduce);
}
结果:55
复制代码
第一个参数是起始值,如果起始值为10呢?
@Test
public void test9(){
Integer reduce = numberList2.stream()
.reduce(10, (x1, x2) -> x1 + x2);
System.out.println(reduce);
}
结果:65
复制代码
第一次执行时,会将 i
(也就是 10 ) 的值作为 a
函数的第一个参数,第二个参数为流中元素的第一个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第二个元素;依次类推。
-
Optional<T> reduce(BinaryOperator<T> a)
:可以将流中元素反复结合起来,得到一个值,返回Optional<T>
。
@Test
public void test10(){
Optional<Integer> reduce = numberList2.stream()
.reduce(Integer::sum);
System.out.println(reduce.get());
结果:55
}
复制代码
流程与第一个方法是一致的,只是第一次执行时,第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素。
5、收集
Collector
接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map中)。 另外, Collectors
类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
toList
- 获取到员工的姓名并放入List集合中
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.55),
new Employee("王五",50,6666.66),
new Employee("赵六",15,3333.33),
new Employee("田七",18,7777.77)
);
@Test
public void test14(){
List<String> collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
结果:
张三
李四
王五
赵六
田七
复制代码
toCollection
- 获取到员工的姓名并放入
HashSet
集合中
@Test
public void test15(){
HashSet<String> hs = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
hs.forEach(System.out::println);
}
复制代码
counting、summingDouble、averagingDouble、maxBy
分别为:计数、求和、平均值、最值
@Test
public void test16(){
//元素的个数
Long l = employees.stream()
.map(Employee::getName)
.collect(Collectors.counting());
System.out.println("个数" + l);
//求和
Double sum = employees.stream()
.collect(Collectors.summingDouble(Employee::getAge));
System.out.println("求和" + sum);
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getAge));
System.out.println("平均值" + avg);
//获得年龄最大的员工信息
Optional<Employee> collect = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())));
System.out.println("获得年龄最大的员工信息" + collect.get());
}
结果:
个数5
求和139.0
平均值27.8
获得年龄最大的员工信息name='王五', age=50, salary=6666.66
复制代码
summarizingInt
收集流中 Integer
属性的统计值。如:平均值
@Test
public void test19(){
IntSummaryStatistics collect = employees.stream()
.collect(Collectors.summarizingInt(Employee::getAge));
System.out.println("个数" + collect.getCount());
System.out.println("求和" + collect.getSum());
System.out.println("最大值" + collect.getMax());
System.out.println("平均值" + collect.getAverage());
System.out.println("最小值" + collect.getMin());
}
结果:
个数5
求和139
最大值50
平均值27.8
最小值15
复制代码
groupingBy
将 stream 根据某个值分为多个Map
//根据年龄进行分组
@Test
public void test17(){
Map<Integer, List<Employee>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getAge));
for(Map.Entry<Integer,List<Employee>> map : collect.entrySet()){
Integer key = map.getKey();
List<Employee> value = map.getValue();
for (Employee employee : value) {
System.out.println("key=" +key + ",value = "+employee);
}
}
}
结果:
key=50,value = name='王五', age=50, salary=6666.66
key=18,value = name='张三', age=18, salary=9999.99
key=18,value = name='田七', age=18, salary=7777.77
key=38,value = name='李四', age=38, salary=5555.55
key=15,value = name='赵六', age=15, salary=3333.33
复制代码
- partitioningBy:将 stream 按条件分为两个 Map,
true
或false
,例如:薪资是否大于7000
@Test
public void test18(){
Map<Boolean, List<Employee>> collect = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 7000));
for(Map.Entry<Boolean,List<Employee>> map : collect.entrySet()){
Boolean key = map.getKey();
List<Employee> value = map.getValue();
for (Employee employee : value) {
System.out.println("key=" +key + ",value = "+employee);
}
}
}
结果:
key=false,value = name='李四', age=38, salary=5555.55
key=false,value = name='王五', age=50, salary=6666.66
key=false,value = name='赵六', age=15, salary=3333.33
key=true,value = name='张三', age=18, salary=9999.99
key=true,value = name='田七', age=18, salary=7777.77
复制代码
joining
连接流中每个字符串
@Test
public void test20(){
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",","这些人:","他们好棒"));
System.out.println("str:" + collect);
}
结果:
str:这些人:张三,李四,王五,赵六,田七他们好棒