最近编码的时候用到了Stream这个东西,以前也用过,但是对它没有一个系统的认知。在好奇心的驱动下还是决定花一些时间去系统地学一学。下面是我看了B站视频(黑马)后的总结,不了解Stream的掘友可以看看本文,节省大家再去看视频的时间。
一、创建不可变的集合
不可变的集合,顾名思义,就是不想让别人修改集合中的内容,也无法修改。如何创建呢?
(一)创建格式
在List、Set、Map接口中,都存在静态的of方法,可以获取一个不可变的集合:
- static List of(E...elements) 创建一个具有指定元素的List集合对象。
- static Set of(E...elements) 创建一个具有指定元素的Set集合对象。
- static <K , V> Map<K,V> of(E...elements) 创建一个具有指定元素的Map集合对象。
(List.of在jdk8中没有,需要jdk9及以上的版本。)
java
public static void main(String[] args) {
List<String> list = List.of("张三","李四","王五");
for(String tmp : list){
System.out.println(tmp);
}
System.out.println("----------------------------");
Set<Integer> set = Set.of(1,2,3,4);
for(int x : set){
System.out.println(x);
}
System.out.println("-----------------------------");
Map<String, Integer> map = Map.of("王五", 1, "李四", 2);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> entry : entries){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("------------------------------");
}
- 这些集合不能添加,不能删除,不能修改,如果修改会抛异常:
java
public static void main(String[] args) {
List<String> list = List.of("张三","李四","王五");
list.remove(0);
}
结果:
- 这里的Set集合中不能有相同的值,Map不能有相同的key,否则抛异常。
java
public static void main(String[] args) {
Set<Integer> set = Set.of(1,1,2,3,4);
}
Map.of()
里的参数最多传 10 个键值对,源码里of()方法参数最多的也就10对。
那为什么不用可变参数呢?因为可变参数只能有一个,并且要在参数列表的末尾。如果想要存超过10个的键值对,我们可以利用数组->可变参数
:
java
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.put(7,"g");
map.put(8,"h");
map.put(9,"i");
map.put(10,"j");
map.put(11,"k");
map.put(12,"l");
//获取所有的键值对
Set<Map.Entry<Integer,String>> entries = map.entrySet();
//把entries变成一个数组
//如果集合的长度 〉数组的长度﹔数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//如果集合的长度〈=数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用原来的数组
Map.Entry[] array = entries.toArray(new Map.Entry[0]);
//生成不可变map集合,ofEntries()的参数是可变参数,可变参数可以使用数组。
Map map1 = Map.ofEntries(array);
}
第二种方式,但是要jdk10及以上:
java
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.put(7,"g");
map.put(8,"h");
map.put(9,"i");
map.put(10,"j");
map.put(11,"k");
map.put(12,"l");
//生成不可变集合
Map<Integer,String> map1 = Map.copyOf(map);
}
(二)不可变集合的作用
- 保证线程安全:不可变集合是一种在创建之后就不再变更的对象,这种特性使得它们天生支持线程安全。
- 防止数据被篡改:不可变集合可以用于封装一些敏感或重要的数据,防止它们被外部程序或不可信的库修改或破坏。
二、Stream 流
(一)Stream 的思想
Stream 为什么叫流?可以通过下面的案例来引入这个概念。
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//筛选姓陈的,并且名字是3个字的人
list.stream().filter(name->name.startsWith("陈"))
.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
}
Stream 就像流水线一样,这个流水线第一道工序就是过滤掉不姓陈的 ,第二道工序过滤掉字的数量不等于3的,最后的工序就是打印。
(二)Stream流的使用步骤
- 得到一条Stream流水线,把数据放上去。
- 利用Stream流中的API进行各种操作。
- 中间方法:方法调用后还可以调用其它方法,如上面的filter。
- 终结方法:最后一步,调用后不能调用其它方法。
1.把数据放到流水线上
获取方式 | 方法名 | 说明 |
---|---|---|
单列集合 | default Stream stream() | collection中的默认方法(由于单列集合实现了Collection接口,可以直接.stream()) |
双列集合(Map等) | 无 | 无法直接使用stream流 |
数组 | public static Stream stream(T[] array) | Arrays工具类中的静态方法 |
零碎数据 | public static Stream stream(T[] array) | Stream接口中的静态方法 |
java
public static void main(String[] args) {
//1.单列集合
List<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","b","b","c","d","e");//添加数据
list.stream().forEach(s -> System.out.println(s));//流水线操作
//2.双列集合
Map<Integer,String> map = new HashMap<>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c");
map.put(4,"d");
map.put(5,"e");
map.put(6,"f");
map.keySet().stream().forEach(s-> System.out.println(s));//方式1:获取key的流水线,利用key与value的关系来操作整体
map.entrySet().stream().forEach(entry-> System.out.println(entry));//方式2:获取键值对的流水线
//3.数组
int[] arr = {1,2,3,4,5,6,7,8};
Arrays.stream(arr).forEach(x-> System.out.println(x));//利用 Arrays 工具类来获取流
String[] strs = {"a","b","c"};
Arrays.stream(strs).forEach(str-> System.out.println(str));
//4.零散的数据
Stream.of("a","b","c","d","e").forEach(x-> System.out.println(x));//利用 Stream 来获取
}
2.中间方法
Stream方法 | 中文解释 | 作用 |
---|---|---|
Stream filter(Predicate<? super T> predicate) | 筛选 | 过滤出符合条件的元素 |
Stream limit(long maxSize) | 限制 | 截取前maxSize个元素 |
Stream skip(long n) | 跳过 | 跳过前n个元素 |
Stream distinct() | 去重 | 去除重复的元素(根据hashCode和equals方法) |
static Stream concat(Stream a, Stream b) | 合并 | 将两个Stream合并为一个 |
Stream map(Function<T, R> mapper) | 映射 | 将每个元素转换为另一种类型或形式 |
注意1:每个中间方法都是返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程。
注意2:修改Stream流中的数据,不会影响原来集合或者数组中的数据。
- filter
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//筛选姓陈的,并且名字是3个字的人
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//如果返回值为true,表示当前数据留下
//如果返回值为false,表示当前数据舍弃不要
return s.startsWith("陈");
}
}).forEach(s -> System.out.println(s));//通过匿名内部类
list.stream().filter(s->s.startsWith("陈")).forEach(s -> System.out.println(s));//通过 lambda
}
- limit
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//截取前 3 个数据
list.stream().limit(3).forEach(s -> System.out.println(s));
}
- skip
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//跳过前 3 个数据
list.stream().skip(3).forEach(s -> System.out.println(s));
}
- distinct
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈大帅");
list.add("陈大帅");
list.add("陈大帅");
list.add("陈小帅");
list.add("陈小帅");
list.add("陈小帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//去重
list.stream().distinct().forEach(s -> System.out.println(s));
}
- concat
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
List<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");
list2.add("c");
//合并,如果类型不相同,类型就会变为共同的父类
Stream.concat(list.stream(),list2.stream()).forEach(s -> System.out.println(s));
}
- 将每个元素转换为另一种类型或形式
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅-20");
list.add("陈小帅-19");
list.add("小红-11");
list.add("李四-12");
list.add("陈四-13");
list.stream().map(new Function<String, Integer>() {
//第一个类型:流中原本的数据类型
// 第二个类型:要转成之后的类型
@Override
public Integer apply(String s) {
//apply的形参s:依次表示流里面的每一个数据
//返回值:表示转换之后的数据
String[] split = s.split("-");
int age = Integer.parseInt(split[1]);
return age;
}
}).forEach(age-> System.out.println(age));//内部类
list.stream().map(s->Integer.parseInt(s.split("-")[1])).forEach(age-> System.out.println(age));//lambda
}
3.终结方法
Stream方法 | 中文解释 | 作用 |
---|---|---|
void forEach(Consumer action) | 遍历 | 对每个元素执行指定的操作 |
long count() | 统计 | 返回元素的个数 |
toArray() | 收集到数组 | 返回一个包含所有元素的数组 |
collect(Collector collector) | 收集到集合 | 返回一个包含所有元素的集合,可以指定集合的类型和特性 |
- forEach
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅-20");
list.add("陈小帅-19");
list.add("小红-11");
list.add("李四-12");
list.add("陈四-13");
// consumer的泛型:表示流中数据的类型
list.stream().forEach(new Consumer<String>() {
//accept方法的形参s:依次表示流里面的每一个数据
@Override
public void accept(String s) {
System.out.println(s);
}
});
list.stream().forEach(s -> System.out.println(s));//lambda
}
- count
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅-20");
list.add("陈小帅-19");
list.add("小红-11");
list.add("李四-12");
list.add("陈四-13");
//统计集合中的个数
long count = list.stream().count();
System.out.println(count);
}
- toArray
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅-20");
list.add("陈小帅-19");
list.add("小红-11");
list.add("李四-12");
list.add("陈四-13");
//方式一:
//收集集合中的数据到数组
Object[] array = list.stream().toArray();
System.out.println(Arrays.toString(array));
//方式二:指定对于类型
//IntFunction的泛型:具体类型的数组
//toArray方法的参数:负责创建一个指定类型的数组
//toArray方法的底层:会依次得到流里面的每一个数据,并把数据放到数组当中
String[] strings = list.stream().toArray(new IntFunction<String[]>() {
/**
*
* @param value 流中数据的个数,要跟数组长度保持一致
* @return 具体类型的数组
*/
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(strings));
//方式三:使用 lambda 表达式
String[] array1 = list.stream().toArray(value -> new String[value]);//lambda
System.out.println(Arrays.toString(array1));
}
- collect
收集到List、Set
中:
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("陈大帅");
list.add("陈大帅");
list.add("陈大帅");
list.add("陈小帅");
list.add("陈小帅");
list.add("陈小帅");
list.add("小红");
list.add("李四");
list.add("陈四");
//收集到list中
List<String> newList = list.stream().filter(s->s.startsWith("陈")).collect(Collectors.toList());
System.out.println(newList);
//收集到Set中,自动去重
Set<String> set = list.stream().filter(s -> s.startsWith("陈")).collect(Collectors.toSet());
System.out.println(set);
}
收集到Map
中:
java
public static void main(String[] args) {
List<String> list2 = new ArrayList<>();
list2.add("陈大帅-20");
list2.add("陈小帅-12");
list2.add("小红-15");
list2.add("李四-15");
list2.add("陈四-38");
//收集到Map中,键:姓名 值:年龄,注意:收集到Map中的时候,流中数据 key 不能重复
Map<String,Integer> map = list2.stream()
.filter(s->s.startsWith("陈"))
/**
* toMap:参数一表示键的生成规则
* 参数二表示值的生成规则
* 参数一:
* Function 泛型一:表示流中数据类型
* 泛型二:表示Map集合中key的数据类型
* 方法 apply 形参:依次表示流里面的每一个数据
* 返回值: Map 的 key
* 参数二:
* Function 泛型一:表示流中数据类型
* 泛型二:表示Map集合中value的数据类型
* 方法 apply 形参:依次表示流里面的每一个数据
* 返回值:Map 的 value
*/
.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("-")[1]);
}
}));
System.out.println(map);
//使用 lambda 表达式
Map<String,Integer> map2 =
list2.stream()
.filter(s->s.startsWith("陈"))
.collect(Collectors.toMap(s->s.split("-")[0],s->Integer.parseInt(s.split("-")[1])));
System.out.println(map2);
}
结果:
如果流中有重复的key,集合到Map时会报错,这跟集合到List不一样。
java
public static void main(String[] args) {
List<String> list2 = new ArrayList<>();
list2.add("陈大帅-20");
list2.add("陈大帅-21");
list2.add("陈小帅-12");
list2.add("小红-15");
list2.add("李四-15");
list2.add("陈四-38");
//使用 lambda 表达式
Map<String,Integer> map2 =
list2.stream()
.filter(s->s.startsWith("陈"))
.collect(Collectors.toMap(s->s.split("-")[0],s->Integer.parseInt(s.split("-")[1])));
System.out.println(map2);
}
结果: