所属专栏:Java学习
Stream流是JDK 8引入的一个概念,它提供了一种高效且表达力强的方式来处理数据集合(如List、Set等)或数组。Stream API可以以声明性方式(指定做什么)来处理数据序列。流操作可以被分为两大类:中间操作(Intermediate Operations)和终端操作(Terminal Operations)。
🍁1. Stream流的适用对象
先得到一条Stream流,并把数据放上去,再进行中间操作和终端操作
获取方式 | 方法名 | 说明 |
单列集合 | default Stream<E>stream() | Collection中的默认方法 |
双列集合 | 无 | 无法直接使用Stream流 |
数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | public static <T> Stream<T> of(T...values) | Stream接口中的静态方法 |
先来看单列集合的例子:
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a", "b", "c", "d", "e");
// 获取Stream流,把集合中的数据放到流上
Stream<String> stream1 = arrayList.stream();
//内部类方式打印
stream1.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s);
}
});
System.out.println();
//获取流之后直接通过lambda表达式进行打印
arrayList.stream().forEach(s -> System.out.print(s));
}
}
对于双列集合,需要通过keySet() 或 entrySet() 转化为单列集合,再获取Stream
public class Demo2 {
public static void main(String[] args) {
HashMap<String,Integer> hashMap = new HashMap<>();
hashMap.put("aaa",1);
hashMap.put("bbb",2);
hashMap.put("ccc",3);
hashMap.put("ddd",4);
//双列集合第一种获取Stream流
hashMap.keySet().stream().forEach(s -> System.out.print(s + " "));
System.out.println();
//双列集合第二种获取
hashMap.entrySet().stream().forEach(s -> System.out.print(s + " "));
}
}
数组获取Stream流:
public class Demo3 {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
String[] arr2 = {"a", "b", "c", "d"};
//获取Stream流
Arrays.stream(arr1).forEach(s-> System.out.print(s + " "));
System.out.println();
//数组中是引用型数据类型也可以获取Stream流
Arrays.stream(arr2).forEach(s -> System.out.print(s + " "));
}
}
对于不存储在数组或集合中的零散数据,可以直接通过Stream接口中的静态方法获取
public class Demo4 {
public static void main(String[] args) {
Stream.of(1,2,3,4,5).forEach(s-> System.out.print(s + " "));
System.out.println();
Stream.of("a","b","c").forEach(s-> System.out.print(s + " "));
}
}
🍁2. 中间方法
中间操作:中间操作可以返回流本身,因此可以链式调用多个中间操作,中间操作可以是对流的过滤(如filter
)、映射(如map
)、排序(如sorted
)等
在上面的中间方法时,只会修改Stream流中的数据,不会影响原来集合或数组中的数据,并且原来的流只能使用一次
🍁2.1 filter()
filter 的参数 Predicate 是一个函数式接口 ,所以可以先使用匿名内部类的方式,再用 lambda 表达式
public class Demo5 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "aaa", "abc", "acb", "aa", "bb", "cc");
//fitlter 过滤 把a开头的留下,其余数据不要
arrayList.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//返回值为true,当前数据留下,返回值为false,当前数据舍弃
return s.startsWith("a");
}
}).forEach(s -> System.out.print(s + " "));
System.out.println();
// lambda表达式的形式
arrayList.stream().filter(s -> s.startsWith("a")).forEach(s -> System.out.print(s + " "));
System.out.println();
//把stream流暂时接收的方式
Stream<String> stream1 = arrayList.stream().filter(s -> s.startsWith("a"));
stream1.forEach(s -> System.out.print(s + " "));
}
}
由于Stream流只能用一次,如果之前的流已经被使用过,再次使用就会报错
所以说,由于只能使用一次,再用一个变量取接收也没有什么意义,直接使用链式编程就可以了
并且,使用流之后,原来集合中的元素也不会改变
🍁2.2 limit() 和 skip()
/* limit() 获取前面几个元素
skip() 跳过几个元素*/
arrayList.stream().limit(3).forEach(s -> System.out.print(s + " "));
System.out.println();
arrayList.stream().skip(3).forEach(s -> System.out.print(s + " "));
🍁2.3 distinct()和concat()
distinct()去重是依赖hashCode()和equals()方法的,所以如果是自定义类型,要手动的重写这两个方法
public class Demo6 {
public static void main(String[] args) {
ArrayList<String> arrayList1 = new ArrayList<>();
Collections.addAll(arrayList1, "aaa", "aaa", "acb", "aa", "bb", "cc");
ArrayList<String> arrayList2 = new ArrayList<>();
Collections.addAll(arrayList2,"dd","ee");
//去重
arrayList1.stream().distinct().forEach(s -> System.out.print(s + " "));
System.out.println();
//流的合并
Stream.concat(arrayList1.stream(),arrayList2.stream()).forEach(s -> System.out.print(s + " "));
}
}
🍁2.4 map()
需求:获取集合中的数字部分
首先调用split方法进行分割,得到数字部分之后再转换为整型
public class Demo7 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "aaa-11", "acb-22", "aa-33", "bb-44", "cc-55");
// 获取集合中的数字部分
arrayList.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
//使用split进行分割
String[] arr = s.split("-");
//获取数字部分
String num = arr[1];
//转换为int类型
int ans = Integer.parseInt(num);
return ans;
}
}).forEach(s -> System.out.print(s + " "));
System.out.println();
//lambda表达式
arrayList.stream()
.map(s -> Integer.parseInt(s.split("-")[1]))
.forEach(s -> System.out.print(s + " "));
}
}
🍁3. 终结方法
名称 | 说明 |
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中的数据,放到数组中 |
collect(Collector collector) | 收集流中的数据,放到集合中 |
🍁3.1 forEach()和count()
forEach方法在之前已经演示过了,就是进行遍历的
forEach中的参数Consumer也是一个函数式接口,也可以先使用匿名内部类的方式,再用 lambda 表达式
public class Demo8 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
arrayList.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + " ");
}
});
System.out.println();
arrayList.forEach(s-> System.out.print(s + " "));
System.out.println();
//统计个数
long l = arrayList.stream().count();
System.out.println(l);
}
}
count() 的返回值为long类型的,可以定义一个变量进行接收
🍁3.2 toArray()
toArray()是收集流里面的数据放在数组中
toArray()方法有两种返回类型,一种是Object类型的,另一种是指定类型的,Object类型的比较简单,直接用Object类型的变量来接收就可以了
public class Demo9 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
Object[] array1 = arrayList.stream().toArray();
System.out.println(Arrays.toString(array1));
}
}
接下来看指定类型
toArray方法的参数也是一个函数式接口,还是使用匿名内部类和lambda表达式两种方式演示
public class Demo9 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
//IntFunction的泛型:具体类型的数组
//apply方法的形参:流中数据的个数,返回的数组的长度要一致
//返回值就是具体类型的数组
String[] array2 = arrayList.stream().toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(array2));
//lambda表达式
String[] array = arrayList.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(array));
}
}
IntFunction的泛型是具体类型的数组
apply方法的形参表示流中数据的个数,返回的数组的长度要一致,最后的返回值就是具体类型的数组
🍁3.3 collect()
collect() 方法就是收集流里面的数据放到集合中,下面先来演示收集到List和Set集合中的例子
public class Demo10 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "张三-男-23", "李四-男-21", "王五-女-22", "钱七-女-22");
// 把所有男性收集起来
//收集到List集合中
List<String> list = arrayList.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
//收集到Set集合中
Set<String> set = arrayList.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toSet());
}
}
收集到List和Set集合中的数据的区别还是和List和Set集合的区别一样的,Set集合中不能有重复的元素,如果流中收集的数据存在重复的数据,在收集到Set集合之后就会进行去重
接下来看Map集合,由于Map集合是一个双列集合,所以需要指定键和值的生成规则
这里的生成规则比较复杂
//收集到Map集合中
/**
* toMap : 参数一表示键的生成规则
* 参数二表示值的生成规则
* 参数一:
* Function 泛型一:表示流中的数据类型
* 泛型二:表示Map集合中键的类型
* 方法 apply 形参:表示流中的每一个数据
* 方法体:生成键的代码
* 返回值:已经生成的键
*
*/
Map<String, Integer> map1 = arrayList.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.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(map1);
//lambda表达式
Map<String, Integer> map2 = arrayList.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
System.out.println(map2);
由于Map集合中是不能有重复的键的,所以如果Stream流获取的键中存在了重的元素,就会报错
🍁4. Stream流的作用和使用步骤总结
作用:Stream流就是结合了lambda表达式,简化集合和数组的操作
使用步骤:
1. 获取Stream流对象
2.使用中间方法处理数据
3.使用终结方法处理数据