简介
Stream 是Java 8 提供用来处理数组、集合等数据的新方式。Stream就像工厂的流水线一样,开发人员可以在流水线上增加对元素的处理操作。
参考资料
概念
Lambda
Lambda 是 Java 8 提供的新特性,开发人员可以通过编写函数式接口,使用Lambda实现函数式编程。Stream中的大量方法使用了函数式接口,因此理解Lambda是理解后续Stream代码的基础。
函数式接口
/**
* 单一未实现的方法接口(函数式接口)
* @FunctionalInterface注解约束接口只能有一个未实现的方法
*/
@FunctionalInterface //标识为函数式接口
interface TestFunctionalInterface{
int add(int b);
}
Lambda
// b 是接口中add方法的参数,与接口方法参数数量相同,类型可以不指定
// -> 指向实现的方法
// {} 接口的实现方法
TestFunctionalInterface testFunc = (b)->{
return b+1;
};
Lambda的使用
package com.studyjava.stream;
public class Java8Lambda {
/**
* 接口中只有单一且需要实现的方法,使用@FunctionInterface注解后,就成了函数式接口
*/
@FunctionalInterface
interface TestFunctionalInterface{
int add(int b);
}
public static void main(String[] args){
//单独声明变量
System.out.println("单独声明变量使用");
TestFunctionalInterface testFunc = (b)->{ //使用Lambda
return b+1;
};
int result = testFunc.add(10);
System.out.println("result=>" + result + "\n");
//使用在方法参数中
System.out.println("在方法参数中使用");
result = addOne((b)->{ return b+1;}); //使用Lambda
System.out.println("result=>" + result + "\n");
//声明变量传递给方法参数使用
System.out.println("声明变量传递给方法参数使用");
TestFunctionalInterface testFunc2 = (b)->{ //使用Lambda
return b+1;
};
result = addOne(testFunc2);
System.out.println("result=>" + result + "\n");
}
public static int addOne(TestFunctionalInterface testFunc){
return testFunc.add(10)+1;
}
}
Stream
如上图,这样就构成了一个Stream(流)。可以看出Stream的组成部分:
- 数据源 - List
- 流动的元素 - List中的元素
- 中间操作 -(map、filter)
- 结束操作 -(collect)
与上图相对应的示例代码:
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Java8Stream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println("before list ===>" + list);
List<Integer> results = list.stream()
.map(item -> Integer.parseInt(item)) //将String转换为Integer
.filter(item -> item < 4) //过滤列表中小于4的
.collect(Collectors.toList()); //收集结果
System.out.println("after list ===>" + results);
}
}
//程序运行结果
/*
before list ===>[1, 2, 3, 4, 5]
after list ===>[1, 2, 3]
*/
PS:代码中map().filter();不是元素先在map方法中执行一遍循环,返回结果后,再到filter中执行一遍循环。这两个方法共用一个循环。
相当于以下代码:
List<Integer> result = new ArrayList<>(); //收集结果 for(String item: list){ Integer int = Integer.parseInt(item); //数据转换 if( item > 4 ){ //数据过滤 result.add(item); } }
就像上图的流水线一样,数组中的元素先到map方法处理,再到filter方法。
数据源
数据源是Stream中要处理元素的来源。数据源可以是集合或数组。
List<String> list = new ArrayList<>();
list.add("1");
list.add("3");
list.add("2");
list.add("5");
list.add("4");
Stream<String> listStream = list.stream(); //list做为数据源
String[] strArr = new String[10];
strArr[0]="1";
strArr[1]="2";
strArr[2]="3";
strArr[3]="4";
strArr[4]="5";
Stream<String> arrStream = Arrays.stream(strArr); //数组做为数据源
中间操作
中间操作:在Stream中,对元素进行处理的操作。如:转换(map)、过滤(filter)、排序(sorted)
特点:
- 设置完中间操作后,会返回一个Stream对象。开发人员可以再次添加中间操作或者是结束操作。
- 设置中间操作时,不会执行中间操作。
中间操作分为:有状态的和无状态
有状态的操作是指在执行操作时,操作内部会进行记录状态。
如:sorted,它需要把列表中所有的元素先收集起来,再排序。完成排序后,再遍历进行输出给下一个中间操作或结束操作
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2);
list.add(5);
list.add(4);
System.out.println("before list ===>" + list);
List<Integer> results = list.stream()
.sorted() //先收集,再排序
.collect(Collectors.toList()); //收集结果
System.out.println("after list ===>" + results);
无状态的操作是指在执行操作时,只关注当前元素本身的处理。
如:map、filter
结束操作
结束操作:最后元素的收集操作。如:collect、reduce、forEach
特点:
- 结束操作,不会返回Stream对象
- 执行结果操作时,才会去执行之前设置好的中间操作
结束操作分为 :短路操作和非短路操作
- 短路操作是指,遍历元素时,遇到满足条件元素,直接返回,不再进行遍历
- 如:allMatch()、 noneMatch() 、findFirst() 、findAny()
- 非短路操作,与短路操作相反,会完成数据源的遍历。
- 如:forEach()、forEachOrdered()、toArray()、reduce()、collect()、max()、min()、count()
并行计算
Stream在默认情况下是串行的,并行计算的开启方式如下:
List<Integer> results = list.stream()
.parallel() //开启并行
.map(item -> Integer.parseInt(item))
.filter(item -> item < 4)
.collect(Collectors.toList());
只需在结束操作前调用parallel方法即可开启并行。
并行计算依靠Java7提供的ForkJoin框架,把一个任务拆分为多个小任务执行,执行完后合并小任务的结果。
使用并行时注意:
- 避免在中间操作中使用外部的状态
- 原因:多线程安全性的根本问题是与其它线程共享的状态。
- 并行执行可能会出现各个线程观察到的状态是不一致的
- 处理基本类型数据,使用包装好的Stream。如:LongStream、IntStream
- 简单的数据处理使用串行比并行要快
- ForkJoin框架需要对任务进行拆分,需要耗时。
- 多线程上下文切换需要时间
测试代码:
package com.studyjava.stream;
import com.sun.deploy.util.StringUtils;
import org.junit.Test;
import java.util.*;
import java.util.stream.BaseStream;
import java.util.stream.LongStream;
public class Java8StreamParallel {
/**
* 在longStream中,使用并行比串行快
*/
@Test
public void longStreamParallel() {
//装箱类并行计算
//并行计算
long time = System.currentTimeMillis();
long sum1 = LongStream.rangeClosed(1, 100000000000l).parallel().map(log -> log * 10).sum();
System.out.println(System.currentTimeMillis() - time);
//串行计算
time = System.currentTimeMillis();
long sum2 = LongStream.rangeClosed(1, 100000000000l).map(log -> log * 10).sum();
System.out.println(System.currentTimeMillis() - time);
System.out.println("sum1 = " + sum1 + " sum2 = " + sum2);
}
/**
* 在List中,简单的数据处理使用并行处理比串行处理要慢
*/
@Test
public void singleParallel() {
//ArrayList并行计算
List<Long> arrayList = new LinkedList<>();
for (long i = 0; i < 1000; i++) {
arrayList.add(i);
}
long time = System.currentTimeMillis();
arrayList.parallelStream()
.map(log ->
log * 10
).reduce(0L, Long::sum);
long end = System.currentTimeMillis() - time;
System.out.println("Parallel end time--->" + end);
time = System.currentTimeMillis();
arrayList.stream()
.map(log ->
log * 10
).reduce(0L, Long::sum);
end = System.currentTimeMillis() - time;
System.out.println("Sequential end time--->" + end);
}
/**
* 在List中,复杂的数据处理使用并行处理比串行处理要快
*/
@Test
public void arrayParallel() {
//ArrayList并行计算
List<Long> arrayList = new LinkedList<>();
for (long i = 0; i < 1000; i++) {
arrayList.add(i);
}
long time = System.currentTimeMillis();
arrayList.parallelStream().map(log -> {
try {
//模拟处理复杂数据处理所需1ms
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return log * 10;
}).reduce(0L, (i, a) -> i + a);
long end = System.currentTimeMillis() - time;
System.out.println("Parallel end time--->" + end);
time = System.currentTimeMillis();
arrayList.stream().map(log -> {
try {
//模拟处理复杂数据处理所需1ms
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return log * 10;
}).reduce(0L, (i, a) -> i + a);
end = System.currentTimeMillis() - time;
System.out.println("Sequential end time--->" + end);
}
}
常用
数据源
- 基本类型包含Stream
- IntStream
- LongStream
- DoubleStream
- 集合类
- List
- Set
- Map
- 数组(需要转换)
- Stream.of()
- 内部调用Arrays.stream()
- Arrays.stream()
- Stream.of()
中间操作
- map
- mapToInt
- mapToLong
- mapToDouble
- filter
- sorted
结果操作
- collect - 收集结果
- collect(Collectors.toList) - 结果转为List类型
- forEach - 遍历
- count - 计数
- sum - 求和 (要转为LongStream才能使用)
示例
package com.studyjava.stream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class Java8Stream {
private static long getCurrentTime() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
//collect - 收集
System.out.println("collect - 收集");
System.out.println("before list ===>" + list);
List<Integer> intList = list.stream()
.map(item -> Integer.parseInt(item)) //转换数据类型
.filter(item -> item < 4) //过滤数据
.collect(Collectors.toList()); //收集结果
System.out.println("after list ===>" + intList + "\n");
/*
执行结果
before list ===>[1, 2, 3, 4, 5]
after list ===>[1, 2, 3]
*/
//count - 计数
System.out.println("count - 计数");
System.out.println("before list ===>" + list);
long count = list.stream()
.map(item -> Integer.parseInt(item)) //转换数据类型
.filter(item -> item < 4) //过滤数据
.count(); //计数
System.out.println("count ===>" + count + "\n");
/*
执行结果
before list ===>[1, 2, 3, 4, 5]
count ===>3
*/
//sum - 求和
System.out.println("sum - 求和");
System.out.println("before list ===>" + list);
long sum = list.stream()
.mapToLong(item -> Long.parseLong(item)) //转换数据类型(mapToLong)
.filter(item -> item < 4) //过滤数据
.sum(); //计数
System.out.println("sum ===>" + sum + "\n");
/*
执行结果
before list ===>[1, 2, 3, 4, 5]
sum ===>6
*/
//forEach - 遍历
System.out.println("forEach - 遍历");
list.stream()
.mapToLong(item -> Integer.parseInt(item)) //转换数据类型
.filter(item -> item < 4) //过滤数据
.forEach(item -> System.out.println(item)); //遍历
/*
执行结果
before list ===>[1, 2, 3, 4, 5]
1
2
3
*/
}
}
总结
- 增强代码逻辑
- 函数式编程:使用Lambda配合Stream,简化代码,突出数据处理逻辑
- 链式调用:数据处理逻辑一节接到一节,线性逻辑符合思考方式
- 延迟执行:Stream只有在调用结束操作时,整个流程才会执行。这意味着,在调用结束操作之前,可以根据业务逻辑拼接不同的中间操作
- 并行计算实现简单
- Stream屏蔽了数据流的遍历步骤,让开发人员专注于元素的处理操作。实现并行计算交给底层代码,开发人员只需要调用底层代码提供的并行计算方法,即可开启并行计算。