Stream流简述
Java8以后 API添加了一个新的抽象称为流Stream,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行筛选,排序,统计,打印,聚合等处理。
元素流在管道中经过中间方法(操作完后还可以继续其他操作)的处理,最后由最终方法(操作完后直接结束,不能调用其他方法)得到前面处理的结果。
作用: 结合Lambda表达式,简化集合、数组的相关操作。
Stream使用
1 生成Stream流
使用者 | 方法 | 说明 | 来自类 |
---|---|---|---|
单列集合 | default Stream stream() | 为当前单列集合创建Stream流 | Collection接口默认方法 |
双列集合 | 无 | 无法直接获取,需要先转成单列集合 | 无 |
数组 | static Stream stream(T[] arr) | 为当前数组创建Stream流 | Arrays工具类 |
同类型零散数据 | static Stream of(T... values) | 为当前数据创建Stream流 | Stream接口 |
1.1 单列集合Stream流
stream()
是Collection接口 默认方法 ,该方法不强制被重写。
ArrayList<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
1.2 双列集合Stream流
创建键
的Stream流
HashMap<Integer, String> hm = new HashMap<>();
Set<Integer> keySet = hm.keySet();
Stream<Integer> keyStream = keySet.stream();
创建键值对
的Stream流
HashMap<Integer, String> hm = new HashMap<>();
Set<Map.Entry<Integer, String>> entries = hm.entrySet();
Stream<Map.Entry<Integer, String>> entryStream = entries.stream();
1.3 数组Stream流
String[] arr = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
1.4 同类型零散数据Stream流
int a = 1;
int b = 3;
Stream<Integer> stream = Stream.of(a, 2, b);
注意:
Stream接口中的of方法的形参是一个可变参数,可以传递零散的数据,也可以传递一个数组。但是数组必须是引用数据类型的,如果传递的是基本数据类型,它不会自动装箱,而是会把整个数组当成一个元素添加到Stream流中。
2 中间方法
方法 | 说明 |
---|---|
Stream filter(Predicate<? super T> p) | 筛选(过滤) |
Stream limit(long maxSize) | 获取前maxSize个元素 |
Stream skip(long n) | 跳过前n个元素 |
Stream distinct() | 元素去重(依赖hashCode和equals方法) |
static Stream<T> concat(Stream a, Stream b) | 合并a流和b流为一个流(来自Stream接口) |
Stream map(Function<T, R> mapper) | 用于映射每个元素到对应的结果,可转换流中数据类型 |
注意:
- 中间方法的返回值是新的Stream流,且每个Stream流只能使用一次,所以建议使用链式编程 。
- 修改Stream流中的数据,不会影响原集合或原数组中的数据。
2.1 筛选(过滤)
方法filter(Predicate<? super T> p)
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class StreamDemo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
//创建流并过滤
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//返回true :当前数据留下
//返回false :当前数据舍弃
return s.startsWith("张");
}
}).forEach(new Consumer<String>() {
//遍历
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
}
运行结果: 张三 \n 张五六 \n 张七八
Predicate接口和Consumer接口都是**函数式接口 **,可以使用Lambda表达式简写,如下。
import java.util.ArrayList;
import java.util.Collections;
public class StreamDemo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
}
}
2.2 获取和跳过
方法:
limit(long maxSize)
//获取前maxSize个元素
Stream skip(long n)
//跳过前n个元素
案例: 获取“张五六”
import java.util.ArrayList;
import java.util.Collections;
public class StreamDemo6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
//方法1 :先获取前3个:张三, 李四五, 张五六
// 再去掉前2个:张五六
list.stream().limit(3).skip(2).forEach(s -> System.out.println(s));
//方法2 :先去掉前2个:张五六, 王五, 张七八, 李九
// 再获取前1个:张五六
list.stream().skip(2).limit(1).forEach(s -> System.out.println(s));
}
}
2.3 去重
Stream distinct()
//元素去重
该方法是利用HashSet去重的,依赖hashCode和equals方法。
所以如果是自定义数据类型,需要手动重写hashCode和equals方法。
import java.util.ArrayList;
import java.util.Collections;
public class StreamDemo7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "张三", "张三", "李四五", "张五六", "王五", "张七八", "李九");
System.out.println(list);
//输出结果: [张三, 张三, 张三, 李四五, 张五六, 王五, 张七八, 李九]
list.stream().distinct().forEach(s -> System.out.print(s + ", "));
//输出结果: 张三, 李四五, 张五六, 王五, 张七八, 李九,
}
}
2.4 合并流
static Stream<T> concat(Stream a, Stream b)
//合并a流和b流为一个流
- 该方法来自Stream接口
合并的两个流最好是同数据类型。
如果不是同类型,合并后的流的数据类型就是两个流的父类,会导致无法使用本身特有方法(多态的弊端)。
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Stream;
public class StreamDemo8 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张三", "李四五");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "张五六", "王五", "张七八", "李九");
Stream.concat(list1.stream(), list2.stream())
.forEach(s -> System.out.print(s + ", "));
//输出结果: 张三, 李四五, 张五六, 王五, 张七八, 李九,
}
}
2.5 转换类型
Stream map(Function<T, R> mapper)
//转换流中数据类型
案例: 在字符串中截取年龄
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Function;
public class StreamDemo9 {
public static void main(String[] args) {
//Stream<R> `map(Function<T, R> mapper)` //转换流中数据类型
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-19", "李四五-20", "张五六-18", "王五-22");
/**
* 参数1 :原流的数据类型
* 参数2 :修改后的数据类型
*/
list.stream().map(new Function<String, Integer>() {
/**
*该方法用于转换流中数据类型
* @param s 原流中的每一个元素
* @return 修改数据类型后的元素
*/
@Override
public Integer apply(String s) {
//1.按"-"切割每一个字符串
String[] split = s.split("-");
//2.获取1号索引元素,即年龄。此时是String类型的。
String ageString = split[1];
//3.转换成Integer类型,并返回。
int ageInteger = Integer.parseInt(ageString);
return ageInteger;
}
})
//当map方法执行完毕后,流上的数据变成了Integer类型,所以forEach遍历时的参数是Integer类型。
.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer ageInteger) {
System.out.print(ageInteger + ", ");
//输出结果: 19, 20, 18, 22,
}
});
}
}
Consumer接口和Function接口是函数式接口 ,可以用Lambda简写:
import java.util.ArrayList;
import java.util.Collections;
public class StreamDemo9 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-19", "李四五-20", "张五六-18", "王五-22");
list.stream().map(s -> Integer.parseInt(s.split("-")[1]))
.forEach(s -> System.out.print(s + ", "));
}
}
3 最终方法
方法 | 说明 |
---|---|
void forEach(Consumer action) | 遍历流,以此获取流中元素 |
long count() | 统计流中数据个数 |
Object[] toArray() | 收集流中数据,放到Object类型数组中 |
A[] toArray(IntFunction<A[]> generator) | 收集流中数据,放到指定类型数组中 |
collect(Collector c) | 收集流中数据,放到集合中 |
3.1 统计
import java.util.ArrayList;
import java.util.Collections;
public class StreamDemo10 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
long count = list.stream().count();
System.out.println(count); // 6
}
}
3.2 收集到数组中
空参方法 Object[] toArray()
//收集流中数据,放到Object类型数组中
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class StreamDemo11 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
Object[] objectArr = list.stream().toArray();
System.out.println(Arrays.toString(objectArr)); //[张三, 李四五, 张五六, 王五, 张七八, 李九]
}
}
转成Object类型的数组往往不是我们需要的,所以可以采用带参方法:
A[] toArray(IntFunction<A[]> generator)
//收集流中数据,放到指定类型数组中
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.IntFunction;
public class StreamDemo11 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
/**
* toArray方法
* @param new IntFunction... 仅负责创建一个指定类型的数组
* @return 一个装有流中数据的、指定类型的数组
* 底层 :会依次得到流里每一个数据,并把数据添加到数组中
*/
//IntFunction的泛型:指定类型的数组的类型。
String[] stringArr = list.stream().toArray(new IntFunction<String[]>() {
/**
* apply方法
* @param value 流中数据的个数。要与数组的长度保持一致
* @return 具体类型的数组
* 方法体 :创建数组
*/
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(stringArr));//[张三, 李四五, 张五六, 王五, 张七八, 李九]
}
}
IntFunction接口是函数式接口,可以使用Lambda表达式简写,如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class StreamDemo11 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "李四五", "张五六", "王五", "张七八", "李九");
String[] stringArr = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(stringArr));//[张三, 李四五, 张五六, 王五, 张七八, 李九]
}
}
3.3 收集到List集合中
方法:
collect(Collector c)
//收集流中数据,放到集合中
接口Collectors 中的方法 toList()
//创建一个指定List集合
案例: 将所有的男性添加到List集合
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo12 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-男-19", "李四五-女-18", "张五六-女-23", "王五-男-22", "张七八-女-20", "李九-男-22");
List<String> collectList = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
System.out.println(collectList); //[张三-男-19, 李九-男-22, 王五-男-22]
}
}
3.4 收集到Set集合中
方法:
collect(Collector c)
//收集流中数据,放到集合中
接口Collectors 中的方法 toSet()
//创建一个指定Set集合
案例: 将所有的男性添加到Set集合
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
public class StreamDemo13 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-男-19", "李四五-女-18", "张五六-女-23", "王五-男-22", "张七八-女-20", "李九-男-22");
Set<String> collectSet = list.stream().filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toSet());
System.out.println(collectSet); //[张三-男-19, 李九-男-22, 王五-男-22]
}
}
3.5 收集到Map集合中
方法:
collect(Collector c)
//收集流中数据,放到集合中
接口Collectors 中的方法
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
//创建一个指定Map集合
Map集合的键唯一,所以不能在键的位置添加重复数据,否则会报错。
案例: 将所有的男性添加到Map集合
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class StreamDemo14 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-男-19", "李四五-女-18", "张五六-女-23", "王五-男-22", "张七八-女-20", "李九-男-22");
/**
* 方法 toMap
* 参数1 : 表示键的生成规则
* 参数2 : 表示值的生成规则
*/
Map<String, Integer> collectMap = list.stream()
.collect(Collectors.toMap(
/**
* 参数1详解 :
* Function的泛型1 : 表示流中每个数据的类型
* 泛型2 : 表示Map集合中“键”的数据类型
* 方法 apply
* 参数: 表示流中的每一个数据
* 方法体: 表示生成键的规则
* 返回值: 表示已经生成的键
*/
new Function<String, String>() {
@Override
public String apply(String s) {
//姓名
return s.split("-")[0];
}
}
/**
* 参数2详解 :
* Function的泛型1 : 表示流中每个数据的类型
* 泛型2 : 表示Map集合中“值”的数据类型
* 方法 apply
* 参数: 表示流中的每一个数据
* 方法体: 表示生成值的规则
* 返回值: 表示已经生成的值
*/
, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
//年龄
return Integer.parseInt(s.split("-")[2]);
}
}));
System.out.println(collectMap);
//{张三=19, 李九=22, 张七八=20, 张五六=23, 王五=22, 李四五=18}
}
}
Function接口是函数式接口,可以使用Lambda表达式简写,如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamDemo14 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三-男-19", "李四五-女-18", "张五六-女-23", "王五-男-22", "张七八-女-20", "李九-男-22");
Map<String, Integer> collectMap = list.stream()
.collect(Collectors.toMap(key -> key.split("-")[0], value -> Integer.parseInt(value.split("-")[2])));
System.out.println(collectMap);
//{张三=19, 李九=22, 张七八=20, 张五六=23, 王五=22, 李四五=18}
}
}