相信兄弟萌在学习时碰到过这样的代码吧:
List<String> list = new ArrayList();
list.stream().forEach(a-> System.out.println(a));
我相信大家初次见到这种代码跟我一样,这 TM 是个啥?能看出来有一个 Lambda 表达式,另外这个 stream 是个啥?
有没有一点像 Steam,游戏下载平台,可以玩吃鸡、CSGO,扯远了,两者毫无关系。
下面我们来揭开 stream 的秘密,Go!
1.集合处理数据的弊端
欸,不是讲 stream 吗?怎么讲到集合的处理数据了。别急,我们从集合处理数据的弊端来引入 stream。
当我们需要对集合中的元素进行操作时,除了必要的添加、删除、获取外,最典型的就是集合遍历。我们来体验集合操作数据的弊端,需求如下:
一个 ArrayList 集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰。
需求:
- 拿到所有姓张的
- 拿到名字长度为 3 个字的
- 打印这些数据
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
//1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<String>(); //{"张无忌","张强","张三丰"}
for (String name : list) {
if(name.startsWith("张")){
zhangList.add(name);
}
}
//2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<String>();//{"张无忌","张三丰"}
for (String name : zhangList) {
if(name.length() == 3){
threeList.add(name);
}
}
//3.打印这些数据 //{"张无忌","张三丰"}
for (String name : threeList) {
System.out.println(name);
}
}
可以看出:每个需求都要遍历一次集合,还要搞一个新集合来装数据,非常麻烦。
2.Stream 流介绍
Stream 流不是一种数据结构,不保存数据,而是对集合中的数据进行加工处理。Stream 流式思想类似于工厂车间的 “生产流水线”。
注意:Stream 流和 IO 流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统 IO 流的固有印象
下面我们来看看借助 Stream 流来改造上面的代码:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
list.stream()
.filter((s)->{
return s.startsWith("张");
})
.filter((s)->{
return s.length() == 3;
})
.forEach((s)->{
System.out.println(s);
});
}
3.获取 Stream 流的两种方式
获取一个流非常简单,有以下两种常用的方式:
- 所有的 Collection 集合都可以通过 .stream( ) 方法来获取流
- 使用 Stream 接口的 .of( ) 静态方法,可以获取流
public static void main(String[] args) {
//方式1:根据Collection获取流
//Collection接口中有一个默认的方法:default Stream<E> stream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
//方式2:Stream中的静态方法of获取流,应用场景:放数组
//static<T> Stream<T> of(T... values) {
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
String[] strs = {"aa","bb","cc"};
Stream<String> stream4 = Stream.of(strs);
//基本类型的数组行不行? 这是不行的,会将整个数组看作一个元素进行操作
int[] arr = {11,22,33};
//这里流操作的类型是 int[],将整个数组看作一个元素来操作
Stream<int[]> stream5 = Stream.of(arr);
}
4.Stream 常用方法和注意事项
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。常见的终结方法有:
- count
- foreach
- anyMatch & allMatch & noneMatch
- findAny & findFirst
- reduce
- max & min聚合方法都是终止操作
非终结方法:又叫函数拼接方法,值返回值类型仍然是 Stream 类型的方法,支持链式调用(除了终结方法外,其与方法均为非终结方法)。
Stream 注意事项
- Stream 只能操作一次
- Stream 方法返回的是新的流
- Stream 不调用终结方法,中间的操作不会执行
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
//1. Stream 只能操作一次
//long count = stream.count(); //执行成功
//long count2 = stream.count();//执行报错
//2. Stream 方法返回的是新的流
//Stream<String> limit = stream.limit(1);
//两个流不是同一个对象
//System.out.println("stream"+stream);
//System.out.println("limit"+limit);
//3.Stream 不调用终结方法,中间的操作不会执行
//这里只有调用了 count()(终结方法),中间的操作才会执行
stream.filter((s)->{
System.out.println(s);
return true;
}).count();
}
5.Stream API
Stream API 能让我们快速完成许多复杂的操作,如:筛选、切片、映射、查找、去除重复、统计、匹配和归约。
由于 Stream API 太多了,我们这里只重点讲几个常见的。
forEach
forEach 用来遍历流中的数据。
@Test
public void testForEach(){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johnson","Steve");
list.stream()
.forEach((s)->{
System.out.println(s);
});
}
count
count 用来统计其中元素的个数,该方法返回一个 long 值代表元素个数。
@Test
public void testCount(){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johnson","Steve");
long count = list.stream().count();
System.out.println(count); //5
}
filter
filter 用于过滤数据,返回符合过滤条件的数据。
@Test
public void testFilter(){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johnson","Steve");
//得到名字长度为4个字的人
list.stream()
.filter((s)->{
return s.length() == 4;
})
.forEach((s)->{
System.out.println(s);
});
}
map
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
@Test
public void testMap(){
Stream<String> original = Stream.of("11", "22", "33");
//Map 可以将一种类型的流转换成另一种类型的流
//将 Stream 流汇总的字符串转成 Integer
Stream<Integer> integerStream = original.map((s) -> {
return Integer.parseInt(s);
});
}
distinct
distinct 用来去除集合中重复的数据。
@Test
public void testDistinct(){
Stream<Integer> stream = Stream.of(22,33,22,11,33);
/*stream.distinct()
.forEach((s)->{
System.out.println(s);
});*/
Stream<String> stream1 = Stream.of("aa", "bb", "aa", "bb", "cc");
stream1.distinct().forEach(s-> System.out.println(s));
}
/**
* distinct 对自定义对象去除重复(重写hashcode和equals,否则不会去重)
*/
@Test
public void testDistinct2(){
Stream<Person> stream = Stream.of(
new Person("貂蝉", 18),
new Person("杨玉环", 20),
new Person("杨玉环", 20),
new Person("西施", 16),
new Person("西施", 16),
new Person("王昭君", 25)
);
stream.distinct().forEach(System.out::println);
}
reduce
如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。
@Test
public void testReduce(){
//T reduce(T identity, BinaryOperator<T> accumulator);
//T identity:默认值
//BinaryOperator<T> accumulator:对数据进行处理的方式
/*Integer reduce = Stream.of(4, 5, 3, 9).reduce(
0, (x, y) -> {
//x=0, y=4
//x=4, y=5
//x=9, y=3
//x=12, y=9
System.out.println("x="+x+", y="+y);
return x + y;
}
);
System.out.println("reduce="+reduce); //21*/
//获取最大值
Integer max = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("最大值:"+max);
}
6.收集 Stream 流中的结果
对流操作完成之后,如果需要将流的结果保存到数据或集合中,可以收集流中的数据。
6.1 收集到集合中
Stream 流提供 collect 方法,其参数需要一个 java.util.stream.Collector<T,A,R> 接口对象来指定收集到哪种集合。java.util.Collectors 类提供一些方法,可以作为 Collector 接口的实例:
//这里转换的 list 是 ArrayList,如果想要转换成特定的 list,需要使用 toCollection 方法
public static <T> Collector<T,?,List<T>> toList( ):转换为 List 集合
//这里转换的 set 是 HashSet,如果需要特别指定 set,那么需要使用 toCollection 方法
public static <T> Collector<T,?,List<T>> toSet( ):转换为 Set 集合
@Test
public void testStreamToCollection(){
Stream<String> stream = Stream.of("aa", "bb", "cc");
//将流中的数据收集到集合中
/*List<String> list = stream.collect(Collectors.toList());
System.out.println("list = "+list);*/ //list = [aa, bb, cc]
/*Set<String> set = stream.collect(Collectors.toSet());
System.out.println("set = "+set); //set = [aa, bb, cc]*/
//收集到指定的集合中ArrayList
//如果想要转换成特定的list,需要使用toCollection方法
LinkedList<String> collect = stream.collect(Collectors.toCollection(LinkedList::new));
}
6.1 收集到数组中
//将流中的数据收集到数组中
@Test
public void testStreamToArray(){
Stream<String> stream = Stream.of("aa", "bb", "cc");
//转成object数组不方便
Object[] objects = stream.toArray();
//String[]
String[] strings = stream.toArray(String[]::new);
for (String string : strings) {
System.out.println("string = "+string);
}
}