👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:源码精读------HashMap源码详细解析
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助
Stream流的使用是一种高级的写法,配合函数式编程(lambda表达式),能够极大简化我们程序的编写,有些二十行的代码也可以一行代码就实现,代码看起来也高雅了很多,这也是成为高级程序员的必会技能。
先讲原理,再实现一些例子。
一文精通Stream流+函数式编程
引入
现在实现一个简单的需求:创建一个集合来存储名字,并且输出名字长度为3,且姓为"张"的所有名字。
这里直接用ArrayList,模拟起来还是很简单的,但是当相对还是要写很长的语句,而Stream流和Lambda表达式的集合能够极大地非常简化我们的代码编写,处理集合并输出的语句只需要一句话:
java
list.stream().filter(name->name.startWith("张")).filter(name->name.length()==3).forEach(name->System.out.println(name));
这代码对一个程序员的诱惑程度别提有多大。
Stream流思想
流:可以视为流水线,学过计算机系统都知道流水线的相关原理,它可以大大提升执行的性能。
如上例子可以分为流水线中的3个子任务:过滤留下"张"姓开头的;过滤留下长度为3的;输出。
对于每一个子任务,Stream流一般都会结合lambda表达式来简化集合、数组的操作。
Stream流的使用步骤:
1、先得到一条Stream流(流水线),并把数据放上去
2、利用Stream流的API进行各种操作:
(1)中间方法:过滤、转换
(2)终结方法:统计、打印
获取Stream流
获取方式 | 方法名 | 说明 |
---|---|---|
单列集合 | stream() | Collection中的默认方法 |
双列集合 | 无 | 无法直接使用是Stream流,需要先通过keySet或entrySet转换成单列集合 |
数组 | stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | of(T...values) | Stream接口中的静态方法 |
1、单列集合
java
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
//Stream<String> stream = list.stream()可以获取流,但一般不这么用,而是直接一路链式编程
list.stream().forEach(s->System.out.println(s));//forEach是终结方法
2、双列集合
java
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);map.put("bbb", 222);
//方法一,把key都拿出来单独做处理
map.keySet().stream().forEach(s->System.out.println(s));
//方法二,获取每个键值对对象
map.entrySet().stream().forEach(s->System,out.println(s));
3、数组
java
int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(s->System,out.println(s));
4、零散数据
java
Stream.of(1,2,3,4,5).forEach(s->System,out.println(s));
Stream.of("a","b","c").forEach(s->System,out.println(s));
在很多时候,都是推荐使用of方法,这是因为of方法形参写法为T...values
,这是一种可变参数的书写形式,所以无论传递零散的数据还是一个数组,都是可以成功处理的。
但是!有一个很关键的点,of方法传入的数组里面的数据必须是引用数据类型
的,如果是基本数据类型,那么只会把这一整个数组当作一个元素,输出的会是地址!
Stream流的中间方法
名称 | 说明 |
---|---|
filter | 过滤 |
limit | 获取前几个元素 |
skip | 跳过前几个元素 |
distinct | 元素去重,依赖hashCode和equals方法 |
concat | 合并a和b两个流为一个流 |
map | 转换流中的数据类型 |
注意:
1、中间方法会返回新的流,原来的Stream流
只能使用一次
,建议使用链式编程2、修改Stream流中的数据,不会影响原来集合或数组中的数据
1、filter:
(1)匿名内部类
java
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s){
//返回值为true则表示当前数据留下
return s.startWith("张");
}
}).forEach(s->System.out.println(s));
(2)lambda表达式:
java
list.stream().filter(s->s.startWith("张")).forEach(s->System.out.println(s));
2、limit:
java
//输出前3个
list.stream().limit(3).forEach(s->System.out.println(s));
3、skip:
java
//跳过前4个
list.stream().skip(4).forEach(s->System.out.println(s));
4、distinct:
java
list.stream().distinct().forEach(s->System.out.println(s));
distinct的底层是非常复杂的,其核心是使用HashSet
实现去重的
5、concat尽可能让两个stream流中的数据保持一致,不然会将数据类型变成它们的共同父类,导致缺失一些子类的特有功能:
java
Stream.concat(list1.stream(), list2.stream()).forEach(s->System.out.println(s));
6、map实现流中数据的类型转换,将String类型字符串中的年龄转换成int并输出:
java
Collections.addAll(list, "一二-12", "布布-14");
(1)匿名内部类:
java
list.stream().map(new Function<String, Integer>(){
//String表示stream中的数据类型,Integer表示要转换的数据类型,不能写int,必须写包装类,默认为Object
@Override
public Integer apply(String s){
//s表示stream流里的每一个数据,返回值为转换后的数据
String[] arr = s.split("-");
String ageString = arr[1];
int age = Integer.parseInt(ageString);
return age;
}
}).forEach(s->System.out.println(s));
(2)lambda表达式:
java
list.stream().map(s->Integer.parseInt(s.split("-")[1]))
.forEach(s->System.out.println(s));
这个lambda表达式写起来剪枝不要太爽!
Stream流终结方法详解
名称 | 说明 |
---|---|
forEach | 遍历 |
count | 统计 |
toArray | 收集流中数据,放到数组中 |
collect | 收集流中数据,放到集合中 |
1、forEach:
(1)匿名内部类
java
list.stream().forEach(new Consumer<String>(){
@Override
public void accept(String s){
System.out.println(s)
}
});
(2)lambda表达式
java
list.stream().forEach(s->System.out.println(s));
2、count统计:
java
long count = list.stream().count();
3、toArray收集int类型数据到String类型数组中
(1)匿名内部类
java
//注意IntFunction中要传递数组泛型,所以需要传入"String[]"
String[] arr = list.stream().toArray(new new IntFunction<String[]>() {
@Override
public void apply(int value){
//value表示数组的长度,方法体只需要创建一个数组即可,返回的类型要和泛型一致
return new String[value];
}
});
System.out.println(Arrays.toString(arr));
(2)lambda表达式
java
String[] arr = list.stream().toArray(value->new String[value]);
System.out.println(Arrays.toString(arr));
collect方法算是比较重要的方法了,在后面详细讲解。
收集方法collect
collect可以将流里面的数据收集到单列集合或双列集合中去的。
收集到List集合:
java
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-15", "小龙女-女-12", "一二-女-4", "布布-男-5");
//把所有的男性收集起来
List<String> newList = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
System.out.println(newList);
同样的,也可以收集到Set中去,单列集合还是比较方便的。
如果要收集到双列集合Map中,就需要指定键和值是哪个字段:
可以打开ToMap的底层源码,可以清楚的看到,调用toMap方法需要指定键和值的规则,其底层会自动的创建一个HashMap对象:
收集到Map集合里面的代码和细节如下所示,需要注意的是,要想收集Map必须保证键是不重复的。
(1)匿名内部类:
java
//键:姓名 值:年龄
Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
/**
* toMap:参数一表示键的生成规则
* 参数二表示值的生成规则
*
* 参数一:
* Function泛型一:流中每一个数据的类型
* 泛型二:Map集合中键的数据类型
* 方法apply形参:依次表示流里面的每一个数据
* 方法体:生成键的代码
* 返回值:已经生成的键
* 也就是说,Function中第一个泛型与apply的形参一致,第二个泛型与apply的返回类型一致
*
* 参数二同理
*/
.collect(Collectors.toMap(
new Function<String, String>() {
@Override
public String apply(String s) {
return s.split("-")[0];
}
},
new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}
));
System.out.println(map);
(2)lambda表达式
java
Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(
s->s.split("-")[0],
s->Integer.parseInt(s.split("-")[2])));
练习
数字过滤
定义一个集合并添加整数1-10,过滤奇数只留下偶数,并将结果保存起来:
java
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list1 = list.stream().filter(s -> (s & 1) == 0).collect(Collectors.toList());
System.out.println(list1);
字符串过滤并收集
创建一个ArrayList集合,并添加以下字符串:
"zhangsan,23"
"lisi,24"
"wangwu,25"
保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值:
java
public static void main(String[] args) {
String s1 = "a", s2 = "b";
//System.out.println(s1.compareTo(s2));
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "zhangsan,23", "lisi,24", "wangwu,25");
//filter也可以转换成Integer类型再去做比较
Map<String, Integer> map = list.stream().filter(s -> "24".compareTo(s.split(",")[1]) <= 0)
.collect(Collectors.toMap(
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1])
));
System.out.println(map);
}
自定义对象过滤并收集
自行去创建一个Actor对象,String类型的name、int类型的age,并且重写toString方法。
代码如下:
java
public static void main(String[] args) {
ArrayList<String> manList = new ArrayList<>();
ArrayList<String> womenList = new ArrayList<>();
Collections.addAll(manList, "坤坤,24", "再多,23", "看一眼,20", "就快,19", "要爆炸,22", "铁山靠,26");
Collections.addAll(womenList, "朵拉,21", "杨幂,30", "杨超越,22", "露娜,25", "妲己,24", "安琪拉,23");
Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3).limit(2);
Stream<String> stream2 = womenList.stream().filter(s->s.startsWith("杨")).skip(1);
List<Actor> list = Stream.concat(stream1, stream2).map(new Function<String, Actor>() {
@Override
public Actor apply(String s) {
return new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]));
}
}).collect(Collectors.toList());
}
System.out.println(list);
其中,map里面的匿名内部类还可以转化为lambda表达式:
java
s->new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))