lambda表达式是stream的基础,初学者建议先学习lambda表达式:Java8——Lambda表达式
什么是 Stream
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素系列。
“集合讲的是数据,流讲的是计算”
特点
- 1 . 不是数据结构,不会保存数据。
- 不会改变数据源,它会将操作后的数据保存到另外一个对象中。
-
延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
image.png
-
具体用法
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);
2. 流的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止
操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全
部处理,称为“惰性求值”
2.1-筛选与切片
image.pngList<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 映射
image.png1 map
- 1、将每个元素转换为大写
@Test
public void test2() {
/**
* 1.映 射
* map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
*/
// 将每个元素转换为大写
List<String> list = Arrays.asList("aa","bb","cc","dd");
Stream<String> stringStream = list.stream()
.map(str -> str.toUpperCase());
stringStream.forEach(System.out::print);
//结果:AABBCCDD
}
- 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)
);
//提取员工名称
employees.stream()
//.map(employee -> employee.getName())
//类::实例方法名
.map(Employee::getName)
.forEach(System.out::print);
结果:张三李四王五赵六田七
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
其余两种就不在介绍。
3 flatMap
@Test
public void test4() {
/**
* flatMap(Function f)
* 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
// 将每个元素根据,进行分割
List<String> list = Arrays.asList("aa,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;
});
stringStream.forEach(System.out::println);
}
结果:
aa
a
b
b
cc
d
d
2.3 排序
image.png1 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
4:流的终止操作
- 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例
如:List、Integer,甚至是 void 。 - 流进行了终止操作后,不能再次使用
1、匹配和查找
image.png- 1.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
- 1.2、anyMatch:检查是否至少匹配一个元素
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
- 1.3、noneMatch:检查是否没有
匹配所有
元素
List<String> list = Arrays.asList("aa","bb","cc","dd");
@Test
public void test3(){
boolean aa = list.stream()
.noneMatch(obj -> obj.equals("aa"));
System.out.println(aa);
}
结果:true
- 1.4、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
- 1.5、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
- 1.6、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
- 1.7、max(Comparator c):返回流中最大值
@Test
public void test7(){
Optional<Integer> max = numberList.stream()
.max(Integer::compareTo);
System.out.println(max.get());
}
结果:66
- 1.8、min(Comparator c):返回流中最小值
@Test
public void test8(){
Optional<Integer> max = numberList.stream()
.min(Integer::compareTo);
System.out.println(max.get());
}
结果:1
- 1.9、forEach(Consumer c):内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代)。
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 的值作为 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());
}
上面方法返回Optional,是因为结果可能为空,因为我们可以发现该方法与第一个方法比少了一个参数。流程与第一个方法是一致的,只是第一次执行时,第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素。
-
<U> U reduce(U i,BiFunction<U, ? super T, U> a, BinaryOperator<U> c)
- i: 一个初始化的值;这个初始化的值其类型是泛型U,与reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U类型可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等。
- a: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的。
- c: 其类型是BinaryOperator,支持的是对U类型的对象进行操作。主要是使用在并行计算的场景下;如果Stream是非并行时,该参数实际上是不生效的。
1、非并行
将Stream中所有元素添加到ArrayList中再返回了
ArrayList<Integer> reduce = numberList2.stream().reduce(new ArrayList<Integer>(),
new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
@Override
public ArrayList<Integer> apply(ArrayList<Integer> u, Integer s) {
u.add(s);
return u;
}
}, new BinaryOperator<ArrayList<Integer>>() {
@Override
public ArrayList<Integer> apply(ArrayList<Integer> strings, ArrayList<Integer> strings2) {
strings.add(8888);
return strings;
}
});
System.out.println(reduce);
结果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
第一个参数:new ArrayList<Integer>() 也就是reduce方法的返回值。
第二个参数:new BiFunction,这函数里有三个参数:第一个和第二个是入参,第三个是BiFunction的返回值。
第三个参数:new BinaryOperator,就一个参数,但这个参数的类型需要与第一个参数:new ArrayList<Integer>()保存一致。并在非并行的流中,该参数无效。
并行
当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。
注意由于采用了并行计算,前两个参数与非并行时也有了差异!
举个简单点的例子,计算4+1+2+3的结果,其中4是初始值:
@Test
public void test13(){
List<Integer> numberList3 = Arrays.asList(1,2,3);
Stream<Integer> stream = numberList3.parallelStream();
System.out.println(stream.reduce(4, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}
, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}));
}
结果:18
并行时的计算结果是18,而非并行时的计算结果是10!
为什么会这样?
先分析下非并行时的计算过程;第一步计算4 + 1 = 5,第二步是5 + 2 = 7,第三步是7 + 3 = 10。按非并行的方式来看它是分了三步的,每一步都要依赖前一步的运算结果!那应该是没有办法进行并行计算的啊!可实际上现在并行计算出了结果并且关键其结果与非并行时是不一致的!
那要不就是理解上有问题,要不就是这种方式在并行计算上存在BUG。
暂且认为其不存在BUG,先来看下它是怎么样出这个结果的。猜测初始值4是存储在一个变量result中的;并行计算时,线程之间没有影响,因此每个线程在调用第二个参数BiFunction进行计算时,直接都是使用result值当其第一个参数(由于Stream计算的延迟性,在调用最终方法前,都不会进行实际的运算,因此每个线程取到的result值都是原始的4),因此计算过程现在是这样的:线程1:1 + 4 = 5;线程2:2 + 4 = 6;线程3:3 + 4 = 7;Combiner函数: 5 + 6 + 7 = 18!
通过多种情况的测试,其结果都符合上述推测!
大佬博客:Java8新特性学习-Stream的Reduce及Collect方法详解
5、收集
image.pngCollector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
image.png
image.png
- 获取到员工的姓名并放入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);
}
结果:
张三
李四
王五
赵六
田七
- 获取到员工的姓名并放入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
//根据年龄进行分组
@Test
@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:根据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:这些人:张三,李四,王五,赵六,田七他们好棒
网友评论