👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:源码精读——HashMap源码详细解析
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助
Stream流的使用是一种高级的写法,配合函数式编程(lambda表达式),能够极大简化我们程序的编写,有些二十行的代码也可以一行代码就实现,代码看起来也高雅了很多,这也是成为高级程序员的必会技能。
先讲原理,再实现一些例子。
一文精通Stream流+函数式编程
- 引入
- Stream流思想
- 获取Stream流
- Stream流的中间方法
- Stream流终结方法详解
- 收集方法collect
- 练习
- 数字过滤
- 字符串过滤并收集
- 自定义对象过滤并收集
引入
现在实现一个简单的需求:创建一个集合来存储名字,并且输出名字长度为3,且姓为“张”的所有名字。
这里直接用ArrayList,模拟起来还是很简单的,但是当相对还是要写很长的语句,而Stream流和Lambda表达式的集合能够极大地非常简化我们的代码编写,处理集合并输出的语句只需要一句话:
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、单列集合
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、双列集合
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、数组
int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(s->System,out.println(s));
4、零散数据
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)匿名内部类
list.stream().filter(new Predicate<String>() {@Overridepublic boolean test(String s){//返回值为true则表示当前数据留下return s.startWith("张");}
}).forEach(s->System.out.println(s));
(2)lambda表达式:
list.stream().filter(s->s.startWith("张")).forEach(s->System.out.println(s));
2、limit:
//输出前3个
list.stream().limit(3).forEach(s->System.out.println(s));
3、skip:
//跳过前4个
list.stream().skip(4).forEach(s->System.out.println(s));
4、distinct:
list.stream().distinct().forEach(s->System.out.println(s));
distinct的底层是非常复杂的,其核心是使用HashSet
实现去重的
5、concat尽可能让两个stream流中的数据保持一致,不然会将数据类型变成它们的共同父类,导致缺失一些子类的特有功能:
Stream.concat(list1.stream(), list2.stream()).forEach(s->System.out.println(s));
6、map实现流中数据的类型转换,将String类型字符串中的年龄转换成int并输出:
Collections.addAll(list, "一二-12", "布布-14");
(1)匿名内部类:
list.stream().map(new Function<String, Integer>(){//String表示stream中的数据类型,Integer表示要转换的数据类型,不能写int,必须写包装类,默认为Object@Overridepublic 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表达式:
list.stream().map(s->Integer.parseInt(s.split("-")[1])).forEach(s->System.out.println(s));
这个lambda表达式写起来剪枝不要太爽!
Stream流终结方法详解
名称 | 说明 |
---|---|
forEach | 遍历 |
count | 统计 |
toArray | 收集流中数据,放到数组中 |
collect | 收集流中数据,放到集合中 |
1、forEach:
(1)匿名内部类
list.stream().forEach(new Consumer<String>(){@Overridepublic void accept(String s){System.out.println(s)}
});
(2)lambda表达式
list.stream().forEach(s->System.out.println(s));
2、count统计:
long count = list.stream().count();
3、toArray收集int类型数据到String类型数组中
(1)匿名内部类
//注意IntFunction中要传递数组泛型,所以需要传入"String[]"
String[] arr = list.stream().toArray(new new IntFunction<String[]>() {@Overridepublic void apply(int value){//value表示数组的长度,方法体只需要创建一个数组即可,返回的类型要和泛型一致return new String[value];}
});
System.out.println(Arrays.toString(arr));
(2)lambda表达式
String[] arr = list.stream().toArray(value->new String[value]);
System.out.println(Arrays.toString(arr));
collect方法算是比较重要的方法了,在后面详细讲解。
收集方法collect
collect可以将流里面的数据收集到单列集合或双列集合中去的。
收集到List集合:
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)匿名内部类:
//键:姓名 值:年龄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>() {@Overridepublic String apply(String s) {return s.split("-")[0];}},new Function<String, Integer>() {@Overridepublic Integer apply(String s) {return Integer.parseInt(s.split("-")[2]);}}));System.out.println(map);
(2)lambda表达式
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,过滤奇数只留下偶数,并将结果保存起来:
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集合中,姓名为键,年龄为值:
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方法。
代码如下:
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>() {@Overridepublic Actor apply(String s) {return new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]));}}).collect(Collectors.toList());}System.out.println(list);
其中,map里面的匿名内部类还可以转化为lambda表达式:
s->new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))