Java8新增的
Stream
,配合同版本出现的Lambda
,给我们操作集合(Collection)提供了极大的便利。
前言
在项目和学习中各种各样的集合我们都会使用到,对集合的遍历也是经常会遇到的操作,不管是for循环遍历 还是迭代器遍历 多多少少还是会有一定的冗余。使用Stream流可以一定程度避免代码的冗余,但过度使用也会造成代码可读性变差需要平衡
一、Stream是什么?
Stream是
将要处理的元素集合看作一种流,在流的过程中,借助Stream API
对流中的元素进行操作,比如:筛选、排序、聚合等。"流"可以简单理解为流水线, 他的工作就是将传到流水线上的数据做一系列的处理最终得到我们想要的数据,Stream流结合lambda表达式,简化集合与数组的操作,
二、使用步骤
1得到Stream把数据放上去->2使用中间方法对流水线上的数据进行操作->3使用终结方法对流水线上的数据进行操作
1.先得到一条Stream(流水线),把数据放上去
根据不同的数据类型采用不同的处理方式
获取方式 | 方法名 | 说明 |
单列集合 | default Stream<E> stream() | Collection的默认方法 |
双列结合 | 无 | 无法直接使用Stream流(需利用keySet或者entrySet) |
数组 | public static<T> Stream<T> stream(T[] array) | Arrays工具类的静态方法 |
零散数据 | public static<T> Stream<T> of(T...values) | Stream接口中的静态方法 |
2.利用Stream流中的API进行各种操作
一般认为Stream流中的Api分两类
中间方法: 方法调用完还可以调用其他方 法如过滤 转换
终结方法: 流中的最后一步 调用完毕后不能调用其他方法 如统计 打印
简单实例代码如下(终结方法):
package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class StreamDemo2 {
public static void main(String[] args) {
/*
* 1.单列集合获取Stream流
*/
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d", "e");
//获取一条流水线,并把集合中的数据放到流水线上
// Stream<String> stream1 = list.stream();
// //使用终结方打印流水线上的所有数据
// stream1.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// //s:依次表示流水线上的每一个数据
// System.out.println(s);
// }
// });
//上面的打印较为繁琐一般直接链式编程就可以了
list.stream().forEach(s -> System.out.println(s));
//或者
list.stream().forEach(System.out::println);
/*
* 2.双列集合获取Stream流 无法直接获取
*/
//创建双列结合
HashMap<String, Integer> hm = new HashMap<>();
hm.put("aaa", 111);
hm.put("bbb", 222);
hm.put("ccc", 333);
hm.put("ddd", 444);
//获取Stream hm.stream() 无法获取流 需要利用keySet或者entrySet
hm.keySet().stream().forEach(k -> System.out.println(k));
hm.entrySet().stream().forEach(k -> System.out.println(k));
/*
* 3.数组获取Stream流 Arrays.stream()
*/
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Arrays.stream(arr).forEach(a -> System.out.println(a));//基本数据类型
String[] arr1 = {"a", "b", "c"};
Arrays.stream(arr1).forEach(s -> System.out.println(s));//引用数据类型
/*
* 4.零散数据获取stream
*/
Stream.of(1, 2, 3, 4, 5, 6, "a", "b", "c").forEach(s -> System.out.println(s));
//Stream.of()的细节 这个方法的形参是可变参数可以传递零散数据 也可传递数组
//但是数组必须是 引用数组 基本类型数组会变为一个整体放进流中
Stream.of(arr).forEach(a -> System.out.println(a));//[I@378bf509
}
}
中间方法示例
名称 | 说明 |
Stream<T>fliter(Predicate<? super T>predicate) | 过滤 |
Stream<T>limit(long maxSize) | 获取前几个元素 |
Stream<T>skip(long n) | 跳过前几个元素 |
Stream<T>distinct() | 元素去重,依赖(hashCode和equals方法) |
static Stream<T>concat(Stream a,Stream b) | 合并a和b的流为一个流 |
Stream<R>map(Function<T,R> mapper) | 转换流中的数据类型 |
注意:中间方法, 返回新的Stream流, 原来的Stream流只能使用一次, 建议使用链式编程. 修改Stream流中的数据, 不会影响原来集合或者数组中的数据
package stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* 将以 张 开头的 有三个字的名字输出
*/
public class StreamDemo3 {
public static void main(String[] args) {
/*
filter 过滤
limit 获取前几个元素
skip 跳过前几个元素
distinct 元素去重(依赖hashCode和equals方法)
concat 合并a和b两个流为一个
map 转换流中的数据类型
注意:中间方法, 返回新的Stream流, 原来的Stream流只能使用一次, 建议使用链式编程.
修改Stream流中的数据, 不会影响原来集合或者数组中的数据
*/
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
/* 匿名内部类写法
//filter 过滤 把名字以'张'开头的留下 名字大于三个字的留下 其他过滤不要
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//如果返回为true 表示当前数据留下
//如果返回为false 表示舍弃不要
return s.startsWith("张");
}
}).forEach(s -> System.out.println(s));
*/
//lambda表达式写法 但是原来的Stream流只能使用一次, 建议使用链式编程.
// Stream<String> stream1 = list.stream().filter(s -> s.startsWith("张"));//此时stream1已经关闭了
// Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
// Stream<String> stream3 = stream1.filter(s -> s.length() == 3);
// //下面的代码会 报错 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
// stream2.forEach(s -> System.out.println(s));
System.out.println("-------------------将以 张 开头的 有三个字的名字输出------------------------");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
System.out.println("--------------------原来的list不变-----------------------");
//修改Stream流中的数据, 不会影响原来集合或者数组中的数据
System.out.println(list);
System.out.println("------------------limit------------------------");
list.stream().limit(3)//这里的3 是个数 只要前三个 输出: "张无忌","周芷若","赵敏"
.forEach(s -> System.out.println(s));
System.out.println("------------------skip-------------------------");
list.stream().skip(4)//跳过4个 输出"张三丰","张翠山","张良","王二麻子","谢广坤"
.forEach(s -> System.out.println(s));
// 只要第4 5 6 个名字 先skip和先limit都一样
System.out.println("--------------------只要第4 5 6 个名字-----------------------");
list.stream().skip(3).limit(3).forEach(s -> System.out.println(s));
System.out.println("-------------------------------------------");
list.stream().limit(6).skip(3).forEach(s -> System.out.println(s));
System.out.println("===================distinct & concat====================");
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张无忌", "张无忌", "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
System.out.println("--------------------distinct-----------------------");
list1.stream().distinct().forEach(s -> System.out.println(s));//提示:distinct底层利用hashSet去重
System.out.println("------------------concat-------------------------");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "张麻子", "汤师爷", "黄四郎", "武举人");
Stream.concat(list1.stream(), list2.stream()).forEach(s -> System.out.println(s));
System.out.println("------------------map-------------------------");
ArrayList<String> list3 = new ArrayList<>();
Collections.addAll(list3, "张无忌-25", "周芷若-24", "赵敏-23", "张强-18", "张三丰-100", "张翠山-88", "张良-90", "王二麻子-30", "谢广坤-40");
//需求 只获取里面的年龄打印出来
// 下面的第一个参数是流中原来的数据类型 第二个是要转成之后的类型
/*
list3.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
String[] arr = s.split("-");.
return Integer.parseInt(arr[1]);
}
}).forEach(s -> System.out.println(s));
*/
//精简写法
list3.stream().map(s -> Integer.parseInt(s.split("-")[1]))
.forEach(s-> System.out.println(s));
}
}
Stream流的终结方法
名称 | 说明 |
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中的数据 放到数组中 |
collect(Collectior collector) | 收集流中的主句 放到集合中 |
示例:
package stream;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
public class StreamDemo1 {
public static void main(String[] args) {
/*
名称 说明
void forEach(Consumer action) 遍历
long count() 统计
toArray() 收集流中的数据 放到数组中
collect(Collectior collector) 收集流中的主句 放到集合中(List Set Map)
*/
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
System.out.println("---------------------void forEach(Consumer action)-------------------");
list.stream().forEach(s -> System.out.println(s));
System.out.println("---------------------long count()-------------------");
long count = list.stream().count();
System.out.println("count="+count);
System.out.println("--------------------- toArray()-------------------");
Object[] arr1 = list.stream().toArray();
System.out.println(Arrays.toString(arr1));
System.out.println("----------------------------------------");
//IntFunction的泛型: 具体的数组
//apply的形参:流中数据的个数 要跟数组长度保持一致
//apply的返回值: 具体类型的数组
//方法体: 就是创建数组
// toArray方法参数的作用: 负责创建一个指定类型数组
// toArray方法的底层: 会依次得到流里面的每一个数据 并把数据放到/数组当中
// roArray方法的返回值: 是一个装着流里面所有数据的数组
/*
String[] arr = list.stream().toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(arr));
*/
//改为lambda表达式
String[] arr2 = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(arr2));
System.out.println("---------------------collect(Collector collector)-------------------");
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张无忌-男-25", "周芷若-女-24", "赵敏-女-22","赵敏-女-22", "张强-男-21", "张三丰-男-25", "张翠山-男-26", "张良-男-35", "王二麻子-男-22", "谢广坤-男-40");
//将所有男性收集为另一个集合
List<String> newList = list1.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
System.out.println(newList);
System.out.println("---------------------set-------------------");
Set<String> set = list1.stream().filter(s -> "女".equals(s.split("-")[1])).collect(Collectors.toSet());
System.out.println(set);
System.out.println("---------------------map-------------------");
Map<String, Integer> map = list1.stream()
.filter(s -> "男".equals(s.split("-")[1]))
/*
toMap : 参数1表示键的生成规则
参数2表示值得生成规则
参数1:
Function泛型1:表示流中每一个数据的类型
泛型2:表示Map集合中键的数据类型
方法apply形参: 依次表示流中的每一个数据
方法体:生成键的代码
返回值:已经生成的键
参数2:
Function泛型1:表示流中每一个数据的类型
泛型2:表示Map集合中值的数据类型
方法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="+map);
System.out.println("---------------------精简写法-------------------");
Map<String, Integer> map1 = list1.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
System.out.println("map1="+map1);
}
}