Stream流
1、Stream例子
需求:
创建一个集合,存储多个字符串元素,完成:
- 把所有以”张“开头的元素存储到新的集合中。
- 把”张“开头,且长度为3的元素存储的新的集合中。
- 打印最终结果。
不使用Stream:
ArrayList<String> list = new ArrayList<String>() {{
add("张无忌");
add("周芷若");
add("赵敏");
add("张强");
add("张三丰");
}};
// 1、把所有以”张“开头的元素存储到新的集合中。
ArrayList<String> list2 = new ArrayList<String>();
for (String s : list) {
if (s.startsWith("张")) {
list2.add(s);
}
}
// 2、把”张“开头,且长度为3的元素存储的新的集合中。
ArrayList<String> list3 = new ArrayList<String>();
for (String s : list2) {
if (s.length() == 3) {
list3.add(s);
}
}
// 3、输出结果
list3.forEach(System.out::println);
使用Stream:
ArrayList<String> list = new ArrayList<String>() {{
add("张无忌");
add("周芷若");
add("赵敏");
add("张强");
add("张三丰");
}};
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
分析:
可见,Stream流极大简化了代码,使代码十分简洁。
2、流的思想
可以把流理解成流水线,每一步都对数据进行一定的操作。
上面流的例子中,就类似于工程流水线,做了如下操作:
3、流的作用
结合lambda表达式,简化对流、数组的操作。
4、流的使用
使用步骤:
- 先得到一条Stream流、并将数据放上去。
- 使用Stream流中的API对数据进行各种操作(过滤、转换、统计、打印等)。
其中各种API被分为:
**中间方法:**方法返回值还是Stream流,也就是说中间方法操作完后,还可以调用其他API。
**终结方法:**最后一步,调用完毕之后,不能再调用其他API了。
a、获取Stream流
获取方式 | 方法名 | 说明 |
---|---|---|
单列集合 | default Stream stream() | Collection中默认方法 |
双列集合 | 无 | 无法直接使用stream流,需要使用keySet、values、entrySet转为Set后再使用Set的流操作。 |
数组 | public static Stream stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | public static Stream of(T…values) | Stream接口中的静态方法 |
单列集合:
ArrayList<Integer> list = new ArrayList<>();// 创建单列集合
Collections.addAll(list, 1, 2, 3, 4, 5);// 添加数据
Stream<Integer> stream = list.stream();// 获取单列集合的stream流
双列集合:
HashMap<String,Integer> map = new HashMap<String,Integer>(){
{
put("aaa",111);
put("bbb",111);
put("ccc",111);
put("ddd",111);
}
};// 创建双列集合并添加数据
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); //获取键值对流
Stream<String> keyStream = map.keySet().stream();// 获取键的流
Stream<Integer> valueStream = map.values().stream();// 获取值的流
数组:
int[] arr1 = {1, 2, 3, 4}; // 创建基础数据类型数组
String[] arr2 = {"a","b","c"}; // 创建引用数据类型数组
IntStream intStream = Arrays.stream(arr1); // 创建基础数据类型数组的流
Stream<String> stringStream = Arrays.stream(arr2);// 创建引用类型数组的流
零散数据:
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); //注意,基础数据类型自动会装箱为引用数据类型
Stream<String> stringStream1 = Stream.of("a", "b", "c");// 引用数据类型流
Stream.of()源码:
可见,源码中传入的是一个泛型的可变参数,也就是说可以传递数组。
但是要注意,这里可以传递引用数据类型的数组,不可以传递基础数据类型数组。
因为会将基础数据类型数组整体当作一个参数,并不自动装箱。
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
// 基础数据类型数组使用Stream.of()方法构造流
int[] arr1 = {1, 2, 3, 4};
Stream.of(arr1).forEach(System.out::println); //这里会直接输出arr1的地址,而不会一次输出值
// 意味着基础数据类型会被当作一个整体,并不进行自动装箱。
b、Stream中间方法
中间方法都是对流做一定操作后返回操作后的流。
因为返回值还是流,所以可以继续进行流操作,因此叫做中间方法。
常见方法:
方法 | 作用 |
---|---|
Stream filter(Predicate<? super T> predicate) | 过滤,根据传入的方法返回值过滤 |
Stream limit(long maxSize) | 截取前指定参数个数的数据 |
Stream skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
Stream distinct() | 元素去重,依赖hasCode() 和equals() 方法 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流,如果两个流类型不同则会转换为二者的共同父类 |
Stream map(Function<T,R> mapper) | 返回由给定函数应用于此流的元素的结果组成的流,即转换流中的元素 |
注意:
- 中间方法都是对流进行一定操作后返回新的流,原来的流就不存在了,建议使用链式编程。
这里说的原来流不存在指的是:- 将流1赋值给变量
stream1
。 - 操作
stream1
后将新的流赋值给变量stream2
。 - 此时再次操作
stream1
就会报错:stream has already bean operated or closed
- 将流1赋值给变量
- 修改集合生成的流里的数据,不会影响集合或数组原来的数据。
1.filter():过滤
方法名:Stream<T> filter(Predicate<? super T> predicate)
Predicate:是一个函数式接口,需要实现boolean test(T t);
方法。
该方法的返回值为true则留下当前元素,否则过滤掉。
因此,可以使用lambda去实现这个函数式接口。
2.limit()和skip():
这两个方法中传递的参数和索引无关,都是多少个。
比如limit(3)指的是只取前三个元素,而不是索引为3的元素前所有元素。
3.distinct():去重
底层使用HashSet存储元素,而HashSet依赖hasCode()
和equals()
方法进行去重。
4.concat():合并
合并两个流的时候,尽可能让两个流类型一致,不然合并后就是两个流的共同父类,这样就会使得子类特有方法无法使用。
5.map():转换
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
// 需求:打印每个人的年龄
// 分析:就是将字符串转换为了数值类型
/**
* 这个函数式接口有两个泛型:
* 第一个是传入参数的泛型,在本例中为String。
* 第二个是返回值参数的泛型,在本例中为Integer。
* 需要实现方法apply(),其参数就是第一个泛型,返回值类型就是第二个泛型
* 注意:泛型不能为基础数据类型
*/
list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
String[] split = s.split("-");
String result = split[1];
return Integer.parseInt(result);
}
// 在map()方法后,新流的类型就已经变成了Integer
}).forEach(System.out::println);
// 上述代码使用lambda就可以简写为:
list.stream().map(s -> Integer.parseInt(s.split("-")[1])).forEach(System.out::println);
c、Stream终结方法
终结方法的返回值不再是流,因此无法继续进行流操作。
常见方法:
方法 | 作用 |
---|---|
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中数据,放到数组中。 |
collect(Collector collector) | 收集流中数据,放到集合中。 |
1.forEach():遍历
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
// 需求:打印每个人的年龄
// 分析:就是将字符串转换为了数值类型
/**
* Consumer泛型:表示流中数据的类型。
* 函数式接口需实现方法action()参数 s:依次表示流里的每一个数据
*/
list.stream().forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s.split("-")[1]);
}
});
// 上述代码使用lambda就可以简写为:
list.stream().forEach(s -> System.out.println(s.split("-")[1]));
2.count():统计
该方法主要用来统计流过滤后的数据个数,返回值为long类型的整数。
3.toArray():收集到数组
//重载了两个同名方法
// 1.无参数:返回一个包含此流的元素的数组(Object类型)
Object[] toArray();
// 2.返回一个包含此流元素的数组,使用提供的 generator 函数来分配返回的数组
// 以及分区执行或调整大小可能需要的任何其他数组。
<A> A[] toArray(IntFunction<A[]> generator);
带参数toArray():
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
/**
* 泛型:? extends Object[]:指的是继承至Object类型的数组,本例中为字符串数组
* 函数式方法apply()参数:流中数据的个数,要跟流中个数一致
* 返回值:具体类型的数组
* 方法体:创建数组
* toArray()方法作用就是:创建一个指定类型的数组。
* 底层实现:会依次得到流里的每一个数据,并把数据放到数组中。
*/
list.stream().toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
// 使用lambda简写
list.stream().toArray(value -> new String[value]);
4.collect():收集到集合
/*
collect(Collector collector)收集流中的数据,放到集合中 (List Set Map)
注意点:
如果我们要收集到Map集合当中,键不能重复,否则会报错
*/
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
//收集List集合当中
//需求:
//我要把所有的男性收集起来
List<String> newList1 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
//收集Set集合当中
//需求:
//我要把所有的男性收集起来
Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toSet());
//System.out.println(newList2);
//收集Map集合当中
//谁作为键,谁作为值.
//我要把所有的男性收集起来
//键:姓名。 值:年龄
Map<String, Integer> map = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
/*
* toMap : 参数一表示键的生成规则
* 参数二表示值的生成规则
*
* 参数一:
* Function泛型一:表示流中每一个数据的类型
* 泛型二:表示Map集合中键的数据类型
*
* 方法apply形参:依次表示流里面的每一个数据
* 方法体:生成键的代码
* 返回值:已经生成的键
*
*
* 参数二:
* Function泛型一:表示流中每一个数据的类型
* 泛型二:表示Map集合中值的数据类型
*
* 方法apply形参:依次表示流里面的每一个数据
* 方法体:生成值的代码
* 返回值:已经生成的值
*
* */
.collect(Collectors.toMap(
new Function<String, String>() {
@Override
public String apply(String s) {
//张无忌-男-15
return s.split("-")[0];
}
},
new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}));
Map<String, Integer> map2 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(
s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])));