Stream流操作
一、过滤
估计很多人学习stream流大部分是从filter 方法开始,该方法筛选出满足条件的数据组成一个新的流。我们从示例中感觉一下该方法的作用。
List<Integer> numbers = Arrays.asList(1,4,2,5,3,7,120,60,54,88,88,90,11);List<Integer> list = numbers.stream().filter(i -> i > 18) //筛选出大于18的数据//.distinct().collect(Collectors.toList());System.out.println(list);
打印信息[120, 60, 54, 88, 88, 90]
从打印信息中可看出确认筛选出了满足条件的数据。
二、去重
distinct 方法,将会计算流中元素所生成的hashCode和equals方法,剔除重复的数据示例如下。
List<Integer> numbers = Arrays.asList(1,4,2,5,3,7,120,60,54,88,88,90,11);List<Integer> list = numbers.stream().filter(i -> i > 18).distinct() //去除重复.collect(Collectors.toList());System.out.println(list);打印信息[120, 60, 54, 88, 90]
此代码块与过滤代码块内容基本一样,唯一不同点把distinct() 注释去除,执行distinct()方法。注意跟过滤代码块的打印信息对比,过滤代码块打印信息有重复两个88数据。去重代码块每个打印数据是唯一的。
三、截取
limit(long maxSize)方法,将会按传递过来的长度,截取流数据。示例如下。
List<Integer> numbers = Arrays.asList(1,4,2,5,3,7,120,60,54,88,88,90,11);List<Integer> list = numbers.stream().filter(i -> i > 18).limit(1).collect(Collectors.toList());System.out.println(list);
打印信息[120]
本示例中,满足大于18的元素不止一个,最后只打印了一个数据,表示截取成功。
四、跳过
skip(long n),跳过n个元素后往下执行。代码如下。
List<Integer> numbers = Arrays.asList(1,4,2,5,3,7,120,60,54,88,88,90,11);List<Integer> list = numbers.stream().filter(i -> i > 18)//.skip(2).collect(Collectors.toList());System.out.println(list);注释代码skip(2) 打印信息[120, 60, 54, 88, 88, 90]取消注释执行skip(2)打印信息[54, 88, 88, 90]
代码示例中执行skip(2),跳过2个元素。从打印信息中可看出跳过了120,60两个元素。
五、映射
map 方法,将一个元素转换为另一个类型的元素。接收一个Function 接口(lambda函数)作数据转换逻辑。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class Person {private String name;private int age;public Person(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public static void main(String[] args) {List<Person> persons = Arrays.asList(new Person("张三",22),new Person("李四",50));List<String> names = persons.stream().map(p -> p.getName()).collect(Collectors.toList());System.out.println(names);}}打印信息
[张三, 李四]
从方法中可看出。通过map 方法,将取出name属性生成一个字符串流集合(Person 对象转换(映射)成 String 对象)。
六、多级流转化为平级流
flatMap方法,如果一个流中,包含了流,可以通过该方法将每个元素转化为流。最后将所有的流合并为一个。同map的方法其它还是蛮像的,区别在于flatMap 方法必须强制将元素将化为流。查看方法的签名如下。
从签名中可知,flatMap 的转换函数接口Function,第二个转换后的对象(? extends Stream<? extends R>)可知,必须强制继承Stream,即一定是流的子类。
示例代码如下。
List<List<String>> list = Arrays.asList(Arrays.asList("a"),Arrays.asList("b"),Arrays.asList("c"),Arrays.asList("d"));
System.out.println("list原始数据打印: " + list);
System.out.println("list转换后打印,形成了一个平级流: "+list.stream().flatMap(items -> Arrays.stream(items.toArray(newString[0]))).toList());打印信息
list原始数据打印: [[a], [b], [c], [d]] //原始对象,打印对象里面是多个数组。
list转换后打印,形成了一个平级流: [a, b, c, d] //转换后为平级的数据。
七、流中至少有一个元素满足条件
anyMatch方法匹配是否有元素是否满足要求,如果满足要求,马上停止返回,即只要确定有一个元素满足要求,即停止检查。示例代码如下。
boolean status = Arrays.asList(1,2,3,4,5).stream().anyMatch(i -> {System.out.print(i +"、") ;return i >= 3;});System.out.println();System.out.println(status);打印信息1、2、3、true
从代码中可知,anMatch匹配条件为元素大于等于3的数据。有3,4,5满足条件。只打印信息只打印到3就停止了,即只要有一个元素满足条件,即停止循环,马上返回。打印的返回状态status也为true
八、流中所有元素都满足条件
allMatch 所有的元素都满足条件时才会返回true,不满足条件马上停止循环,返回false。示例代码如下。
boolean status = Arrays.asList(1,2,3,4,5).stream().allMatch(i -> {System.out.print(i +"、") ;return i >= 3;});System.out.println();System.out.println(status);打印信息如下1、false
跟前一个示例基本一致,只是将anyMatch替换为allMatch。
九、流中所有的元素都不满足条件
noneMatch方法所有的元素都不满足条件返回true,与allMatch 方法相对。示例代码如下。
boolean status = Arrays.asList(1,2,3,4,5).stream().noneMatch(i -> {System.out.print(i +"、") ;return i >= 6;});System.out.println();System.out.println(status);打印信息1、2、3、4、5、true
跟前一个示例基本一致,只是将allMatch替换为noneMatch。
十、查找(获取元素)元素
查找元素(返回一个元素)有findAny和findFirst方法,两个方法都返回一个Optional
List<Integer> numbers = new ArrayList<> (Arrays.asList(1,4,3,2,6,5,8,11,9,15,10,12,7)); //创建集合for(int i=0;i<5;i++) {int num = numbers.parallelStream().filter(j -> j > 5).findAny().get(); //获取随机的一个元素。findFirst自测System.out.print(String.format("%d ", num)) ;}打印信息11 11 11 6 6
十一、归约(将流组合成一个元素)
归约,将一个流数据的元素反复处理转化一个元素。reduce函数实现了归约功能。
reduce有三种方法签名
1、Optional
方法签名一,仅接收一个BinaryOperator函数式接口,对元素进行收集归纳,返回Optional对象。示例如下。
List<Integer> numbers = new ArrayList<> (Arrays.asList(1,4,3,2,6,5,8,11,9,15,10,12,7)); //创建集合
System.out.println("累加值: " + numbers.stream().reduce((a,b) -> a += b).get()); //累加
打印信息如下
累加值: 93
2、T reduce(T identity, BinaryOperator
方法签名二、接收两个参数identity初始参数,BinaryOperator函数式接口。返回的元素类型为identity的类型。这一点需要注意。示例如下。
List<Integer> numbers = new ArrayList<> (Arrays.asList(1,4,3,2,6,5,8,11,9,15,10,12,7)); //创建集合
System.out.println("累加值: " + numbers.stream().reduce(1,(a,b) -> a += b));//累加,初始值为1。
打印信息如下
累加值: 94 (比方法签名一多了1.表示identity加入了运算之中)
方法签名三、一般在并发流的情况下才使用该签名方法。接收三个参数identity初始参数。BiFunction<U, ? super T, U> 函数式接口(输入两个参数U和T,返回一个结果U),对元素进行收集归纳。BinaryOperator函数式接口也是BiFunction的特殊体,对BiFunction处理返回的元素进行合并。返回的元素类型为identity的类型。一般情况下使用并行流才会触发执行BinaryOperator操作。示例代码如下。
List<Integer> numbers = new ArrayList<> (Arrays.asList(1,2,3,4)); //创建集合/*******************************并行流处理 start *************************************************/int num = numbers.parallelStream().reduce(1, (a,b) -> {System.out.println(String.format("currentName=%s BiFunction a = %d ,b = %d",Thread.currentThread().getName(), a,b));return a + b;},(a,b) -> {System.out.println(String.format("currentName=%s BinaryOperator a = %d ,b = %d",Thread.currentThread().getName(), a,b));return a + b;});System.out.println(String.format("最终结果 %d", num));/*******************************并行流处理 end *************************************************/打印信息currentName=ForkJoinPool.commonPool-worker-1 BiFunction a = 1 ,b = 2currentName=ForkJoinPool.commonPool-worker-2 BiFunction a = 1 ,b = 4currentName=main BiFunction a = 1 ,b = 3currentName=ForkJoinPool.commonPool-worker-2 BinaryOperator a = 4 ,b = 5currentName=ForkJoinPool.commonPool-worker-3 BiFunction a = 1 ,b = 1currentName=ForkJoinPool.commonPool-worker-3 BinaryOperator a = 2 ,b = 3currentName=ForkJoinPool.commonPool-worker-3 BinaryOperator a = 5 ,b = 9最终结果 14/*******************************普通流处理 start *************************************************/int num = numbers.stream().reduce(1, (a,b) -> {System.out.println(String.format("currentName=%s BiFunction a = %d ,b = %d",Thread.currentThread().getName(), a,b));return a + b;},(a,b) -> {System.out.println(String.format("currentName=%s BinaryOperator a = %d ,b = %d",Thread.currentThread().getName(), a,b));return a + b;});System.out.println(String.format("最终结果 %d", num));/*******************************普通流处理 end *************************************************/打印信息currentName=main BiFunction a = 1 ,b = 1currentName=main BiFunction a = 2 ,b = 2currentName=main BiFunction a = 4 ,b = 3currentName=main BiFunction a = 7 ,b = 4最终结果 11
从并行流和普通流的打印信息来看。普通流直接累加处理了数据,并不调用合并器处理。并行流并行处理,最终调用合并器合并数据。而且在某些情况下,使用并发流和普通有时候流执行获取的结果并不一致。看打印的线程名称 。上面例子中普通流顺序处理数据(1 + 1 = 1 + 2 = 3 + 3= 6 + 4 =11). 并行流处理流程是初始值1 分别与流中的数据相加得到4个数据 (1 + 1=2,1+2=3,1+3=4,1+4=5),再将这四个数据合并(2+3=5+4=9+5=14)。记住,理论上对数据量较大,性能要求较高,使用并行流处理的情况下才使用方法签名三处理数据。使用该签名方法要考虑清楚流程与规范。
这一节,挑选了几个比较有代表性的操作方法做案例讲解。还有不少操作没有讲解。有时间,大家可自行研究。