详解Java中Stream流的用法和原理
作者:会飞的喵喵
最近编码的时候用到了Stream这个东西,以前也用过,但是对它没有一个系统的认知,在好奇心的驱动下还是决定花一些时间去系统地学一学,不了解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及以上的版本。)
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("------------------------------"); }
- 这些集合不能添加,不能删除,不能修改,如果修改会抛异常:
public static void main(String[] args) { List<String> list = List.of("张三","李四","王五"); list.remove(0); }
结果:
- 这里的Set集合中不能有相同的值,Map不能有相同的key,否则抛异常。
public static void main(String[] args) { Set<Integer> set = Set.of(1,1,2,3,4); }
Map.of()
里的参数最多传 10 个键值对,源码里of()方法参数最多的也就10对。
那为什么不用可变参数呢?因为可变参数只能有一个,并且要在参数列表的末尾。如果想要存超过10个的键值对,我们可以利用数组->可变参数
:
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及以上:
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 为什么叫流?可以通过下面的案例来引入这个概念。
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接口中的静态方法 |
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
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
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
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
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
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)); }
- 将每个元素转换为另一种类型或形式
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
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
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
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
中:
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
中:
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不一样。
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); }
结果:
以上就是详解Java中Stream流的用法和原理的详细内容,更多关于Java Stream流的资料请关注脚本之家其它相关文章!