内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战
-
筛选和切片
- filter方法
//取出是素菜的菜单 menu.stream() .filter(Dish::isVegetarian) //代表是否是素食,这里条件并不一定是方法调用,只要的你条件能够返回boolean就可以 .collect(Collectors.toList());
- limit方法
//取出菜单的前三个 menu.stream().limit(3) //跟sql中limit一样,不过这个是短路limit,也就是说,它取出了三个后就不再遍历后面的元素了,直接返回 .collect(Collectors.toList());
- distinct方法:去重
menu.stream() .distinct() //去重操作,根据元素的hashCode和equals方法判断是否相等 .collect(Collectors.toList());
- skip方法:跳过指定数量的元素
//取菜单中第三个到第五个 menu.stream() .skip(2) //跳过开头两个元素 .limit(3) //然后截取第三个到第五个 .forEach(System.out::println); //如上可以看出skip和limit是互补的,因为limit没有这样的形式:limit(start,end)
-
映射
- 提到这个概念,就是把你stream里的每个元素都应用到自己指定的表达式之上然后返回,就比如
List<String> list = Arrays.asList("1","2","3"); list.stream() .映射方法(str -> str + "dada") .forEach(System.out::println); //以上为伪代码 //说明的意思就是list中的每个元素:1,2,3都会应用到表达式:str + "dada" //并返回表达式的值 //所以结果就是1dada,2dada,3dada
- map方法
//这里提到的map方法,完全可以替换上面的伪代码:映射方法,上面代码是传入A类型返回A类型的演示 //也就是传入字符串1,2,3返回的也是字符串 //map当然可以传入A类型返回B类型,比如 List<String> list = Arrays.asList("1","2","3"); list.stream() .map(Integer::parseInt) .forEach(System.out::println); //上面就是传入String,返回的是int //当然也可以是返回字符串的长度
-
考虑一个场景,把n个单词的里面的字母去重然后输出
- 就比如["abc","bce"],里面的字母就是["a","b","c","b","c","e"],去重后是:["a","b","c","e"],现在来实现它
List<String> list = Arrays.asList("abc","bce"); List<String[]> collect = list.stream() .map(str -> str.split("")) .distinct() .collect(Collectors.toList()); collect.forEach(System.out::println);
-
- 上面的感觉是对的,拆分,然后去重,但是一点需要注意,当lambda把第一个元素"abc"进行映射操作的时候,split方法返回的是一个String数组对象,这也就能解释为什么List的泛型是String数组了,数组跟数组distinct那肯定是去重不成功的。
- 流的过程是这样的["abc","bce"] -> [{"a","b","c"},{"b","c","e"}],然后拿{"a","b","c"}和{"b","c","e"}去重肯定行不通
- 现在遇到的问题就是:我们希望把abc和bce切分然后合并成一个流,然后进行去重,我们接着实验
-
Array::stream
- Array::stream可以接受一个数组并产生一个流,如下
String[] str = {"abc","bce"}; Stream<String> stream = Arrays.stream(str); stream.forEach(System.out::println); //abc //bce
- 我们在上面的split方法返回的就是数组,我们尝试将split返回的数组映射到此方法上,看看能不能合并成一条流
List<String> list = Arrays.asList("abc","bce"); List<Stream<String>> collect = list.stream() .map(str -> str.split("")) .map(Arrays::stream) .distinct() .collect(Collectors.toList()); //java.util.stream.ReferencePipeline$Head@2aae9190 //java.util.stream.ReferencePipeline$Head@2f333739
- 但是并不尽如人意,因为他返回的泛型是Stream,也就是说,Array::stream方法只是把一个数组转换为一个流,流中的元素是String所以这个流就是这样的Stream,这种情况类似上面遇到的返回的是两个数组,现在返回的List中装入的并不是String,而是两条流,流跟流做去重也是行不通的
-
解决方案
- 现在遇到的问题就是:他们都是将一个list元素单独转化为一条流或者一个数组,我们需要的是转换为流或者数组之后,再将这些返回的数组或者流接为一个流或者数组,就像接水管一样,让他们连起来
- 尝试方法:flatmap
List<String> list = Arrays.asList("abc","bce"); List<String> collect = list.stream() .map(str -> str.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); collect.forEach(System.out::println); //a b c e
-
如上的代码终于是返回了一个LIst,所以结果也是我们期待的达到了去重的效果,那么它的流的处理过程是怎么样的呢?如下
-
传入"abc"
->切分为["a","b","c"]
-
--->flatMap合并["a","b","c","b","c","e"]
->去重
->ok
-
传入"bce"
->切分为["b","c","e"]
- 上面能看懂吗...懒得画图了,第一行和第三行是flatMap前面的split过程,然后是第二行存入flatMap进行合并流
-
- flatMap也就是把n个流合并为一个流
-
小实战
- 要求两个list:["123"],["456"],然后组成元组:(1,4),(1,5),(1,6),(2,4),(2,5)...
List<String> first = Arrays.asList("123"); List<String> second = Arrays.asList("456"); first.stream() .map(str -> str.split("")) .forEach(strs -> { Arrays.stream(strs).forEach(f -> { second.stream() .map(sec -> sec.split("")) .forEach(secs -> Arrays.stream(secs).forEach(s -> System.out.println("("+f+","+s+")"))); }); }); //上面代码是直接输出的,那么下面的就是将元组组合成一个LIst返回了 List<String> first = Arrays.asList("123"); List<String> second = Arrays.asList("456"); List<String> collect = first.stream() .map(str -> str.split("")) .flatMap(strs -> //将strs后的表达式返回的stream都合并为一个 Arrays.stream(strs).flatMap(f -> second.stream() .map(sec -> sec.split("")) .flatMap(secs -> Arrays.stream(secs).map(s -> "(" + f + "," + s + ")"))) ).collect(Collectors.toList()); collect.forEach(System.out::println); //耐心看.....
查找和匹配
- anyMatch:至少匹配一个
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().anyMatch(s -> s.equals("1")); //true
- allMatch:全部匹配
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().allMatch(s -> Integer.parseInt(s) < 5); //true
- noneMatch:全部不匹配
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().noneMatch(s -> Integer.parseInt(s) > 5); //true
- 如上的方法都具有短路效果,意思就是主要遇到一个不匹配条件的元素就立刻返回true或者false
- findAny:找任意一个
List<String> list = Arrays.asList("1","2","3","4");
Optional<String> any = list.stream().findAny();
System.out.println(any.get()); //虽然书上说是任意一个,但是我一直返回的是1
//自己测试的将元素添加到十个,stream依旧是1,但是使用并行流parallelStream,将随机返回,但是依然是区间比较小
- Option以后的帖子会说到的,你可以看一下我相关的帖子,只要记住Option是一个值的容器,因为findAny找的可能是个空列表,所以他可能会返回null,然后将返回的这个值包装在Option中,然后调用get方法就会出现返回的值,如果Option中没有值还get那么就会报错,现在只要知道这点就可以了
- findFirst:找到第一个
List<String> list = Arrays.asList("1","2","3","4");
Optional<String> any = list.stream().findFirst();
System.out.println(any.get()); //1
归约reduce
- 上面的映射和这词提到的归约可以一起使用,类似hadoop中的mr模型
- 元素求和
int[] is = {1,2,3,4,5,6,7,8,9};
Arrays.stream(is).reduce(0,Integer::sum);//45
//以0为起始值,所以如果数组内没有元素,也不至于null,然后依次相加
//0+1=1
//1+2=3
//3+3=6
//6+4=10 ....
int[] is = {1,2,3,4,5,6,7,8,9};
OptionalInt reduce = Arrays.stream(is).reduce(Integer::sum);
System.out.println("reduce = " + reduce.getAsInt());//45
//OptionalInt的出现就是因为它没有初始值进行累加,所以如果数组为空,那么将返回null
//OptionalInt使用方法和Optional一样,只是方法名变了一下,目前只知道这些就好了
- 元素最大值和最小值
//max
int[] is = {1,2,3,4,5,6,7,8,9};
OptionalInt reduce = Arrays.stream(is).reduce(Integer::max);
System.out.println("reduce = " + reduce.getAsInt()); //9
//求最小改为min方法即可,也可以自己实现
OptionalInt reduce = Arrays.stream(is).reduce((a,b) -> a > b ? a : b );
-
归约方法的优势和并行化
- 求和方法:定义一个int变量,然后迭代去加。相比之下reduce将其转换为了内部迭代。而且迭代要去求和并且更新我们的一个int共享变量,这对于并行化来说并不容易实现,如果加入了同步,可能线程切换的开销就已经抵消了并行带来的性能提升。(可变的变量累计器对于并行来说并不好),如上的代码为了实现并行只需要把stream方法改为parallelStream()即可
int[] is = {1,2,3,4,5,6,7,8,9}; int sum = 0; //int共享变量 //这只是单线程的,如果是多线程的话,为了保证sum的正确肯定要sync。 //所以这就是说的共享变量并不适合于并行化 for (int i : is) { sum += i; //更新共享变量 }
-
流操作的有状态和无状态
- 如果你购买了书可以先去看看书的定义,下面是我自己的理解
int[] is = {1,2,3,4,5,6,7,8,9}; Arrays.stream(is) .filter(i -> i > 3) //无状态 .map(i -> i + 1) //无状态 .distinct() //有状态 .max(); //有状态
- 对于上面来说,filter和map只是接收一个元素然后过滤映射一个元素,这个元素处理完就交给下面的方法处理了,自己并不保留这个元素,这样的叫做无状态
- 那distinct和max来说,他不能接收一个处理一个然后再交给下面的方法处理,因为它需要拿到整个元素来去重和去最大值,如果拿到部分他肯定是不能做这些操作的,元素就暂时的保留在了方法中,所以这样的叫做有状态