本文主要是带你认识Stream Api 原理,理解Stream Api使用,并学会从多种数据源生成Stream,以操作数据集;同时带你快速理解和学会Stream API中 Filter、skip、limit、map、flatMap、Find、reduce、match等方法的使用。
一、利用Stream Api 提供的接口和方法,生成一个流
要想在开发中利用Stream的高效特性处理数据,我们就要先生成一个流,《java8 in action》中提到Stream Api为我们提供了集中生成流的方法:
现在我们就举例看看如何从集合、序列值、数组、文件、生成函数来创建一个Stream 流,代码和解析都在里面。
package com.aigov.java8_newfeatures.stream;
import lombok.Data;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author : aigoV
* @date :2019/10/23
**/
public class CreateStream {
//使用Stream Api 从集合生成流
private static Stream<String> createStreamFromCollection(){
List<String> list = Arrays.asList("A", "I", "G", "O", "V");
return list.stream();
}
//使用Stream Api 从一个显示的Value(它的元素可是任意类型,数量也可以是任意个)生成一个Stream
private static Stream<String> createStreamFromValue(){
return Stream.of("Java 8 ", "Stream ", "In ", "Action");
}
//使用Stream Api 从一个数组创建一个Stream 这里是个int型Stream
private static IntStream createStreamFromArrays(){
int[] intArray = {1,2,3,4,5};
return Arrays.stream(intArray);
}
/**
* 使用Stream Api 从一个文件生成一个Stream
* Java8中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。
* java.nio.file.Files中的很多静态方法都会返回一个流。如:Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
*/
private static Stream<String> createStreamFromFile(){
Path path = Paths.get("E:\\DevelopmentSoftware\\cptmcp\\aigovProject\\java8_newfeatures\\src\\main\\java\\com\\aigov\\java8_newfeatures\\stream\\SimpleStream.java");
Stream<String> filesStream = null;
try {
filesStream = Files.lines(path);//Files.lines得到一个流,其中的每个元素都是给定文件中的一行
} catch (IOException e) {
e.printStackTrace();
}
return filesStream;
}
/**
* Stream API提供了两个静态方法来从函数生成无限流:Stream.iterate()和Stream.generate()。
*
* 用Stream.iterate生成一个无限流:
* iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上UnaryOperator<t>类型的
* Lambda。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。
* 流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。
* iterate是顺序执行的,因为结果取决于前一次计算,且iterate是无限执行下去的,及其产生的流也是无界的。
* 所以我们需要使用limit方法来显式限制流的大小,这里只选择了前5个偶数。
*/
private static Stream<Integer> createStreamFromIterate(){
return Stream.iterate(0,n->n + 2).limit(5);
}
/**
* 用Stream.generate()生成一个简单的无限流:
*
* Stream.generate()接受一个supplier接口类型的参数,这个参数类型本身是无状态的,
* 即,它不会记录任何可以在之后的计算中使用的值(和Iterate相反),比如下面这个例子:
*/
private static Stream<Double> createStreamFromGenerate(){
return Stream.generate(Math::random).limit(5);
}
/**
* Stream.generate() :
* 我们也可自己实现supplier接口,自己构建一个有状态的supplier类,作为参数传进去。
* 这样的话,我们就是可以实现Iterate相似的作用。
* 但是这样的代码在程序并行执行时是不安全的,所以我们还是推荐iterate方法
*
* 下面我们写一个有状态的案例,对比一下iterate方法
*/
//创建一个obj对象
@Data
static class Obj {
private int id;
private String name;
public Obj(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Obj{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//新建一个Supplier的实现类 使你自己的Supplier对象有可变的状态(记录前一个旧的Obj对象和后来的新的Obj对象)
static class ObjSupplier implements Supplier<Obj> {
private int index = 0;
private Random random = new Random(System.currentTimeMillis());
@Override
public Obj get() {
index = random.nextInt(100);
return new Obj(index, "名称" + index);
}
}
//Generate创建一个对象流
private static Stream<Obj> createObjStreamFromGenerate() {
return Stream.generate(new ObjSupplier()).limit(5);
}
public static void main(String[] args) {
//createStreamFromCollection().forEach(System.out::println);
//createStreamFromValue().map(String::toUpperCase).forEach(System.out::println);
//System.out.println(createStreamFromArrays().sum());//String.sum将数组求和
//createStreamFromFile().forEach(System.out::println);//这会把SimpleStream.java文件里面的代码包括格式全部打印出来
//createStreamFromIterate().forEach(System.out::println);
//createStreamFromGenerate().forEach(System.out::println);
createObjStreamFromGenerate().forEach(System.out::println);
}
}
二、案例详解Stream API中 Filter、skip、limit、map、flatMap、Find、reduce、match等方法的原理与使用
本来打算直接把我写的源码和注释丢上来省事,但注释太多,idea看着还好,直接贴上博客看可能眼花:
下面给出的所有案例解析,都将基于以下基础代码:
package com.aigov.java8_newfeatures.stream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
/**
* @author : aigoV
* @date :2019/10/24
* Stream api:
**/
public class StreamApiMethod {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 6, 6, 7, 8, 1);
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
/**
案例啦。。。
**/
}
}
开始。。。。。。。。。。。。。。。。。。。。。。。
1、Stream().filter()作用与使用:
查看filter Api 我们会发现,它接受的参数是: Predicate<? super T> (这是一个函数,这个函数里面是一个抽象方法 boolean test(T t),该方法返回一个布尔值。) filter方法作用是返回 包含所有符合Predicate函数规则(规则你定)的元素的一个Stream流。下面举个栗子:
list.stream().filter(a -> a % 2 == 0).forEach(System.out::print);//返回流中的偶数
2、stream().distinct()作用与使用
:
distinct方法的作用是去重,并返回去重后的Stream流。举个栗子:
list.stream().distinct().forEach(System.out::print);
3、stream().skip()作用与使用:
skip,字面意思跳过。skip(n)方法,返回一个扔掉了流中前n个元素的流。如果流中元素不足n个,则返回一个空流。举个栗子:
list.stream().skip(3).forEach(System.out::print);
4、stream().limit()作用与使用:
limit(n)返回一个不超过给定长度n的流,举个栗子:
list.stream().limit(2).forEach(System.out::print);
5、关于stream().map() 与stream().flatMap()如何使用,有何区别?
stream().map():接受的参数是Function<? superx T, ? extends R>函数,该函数接受一个泛型T,返回一个R泛型的值。即,map可以让你将一个对象转化成其他的对象,比如integer转为String,小写字母转为大写等,也就是它要创建一个新版本的数据
stream().flatMap():接受的参数是Function<? super T, ? extends Stream<? extends R>>函数,与map不同的是该函数的第二个参数是个Stream。
flatMap()与map()差别在于:
Map()可能生成的是一个数组流或者一个流的列表(多个小流组成的一个大流),而flatMap()则是把map()生成的多个独立的流(数组流)合成一个流。 比如map(xx)返回的结果为流1(元素为2,3),流2(元素为3,6),这种情况下使用flapMap(xx)的结果就是:流(2,3,3,6)。二者结果的区别将决定后续操作的难易。
下面举两个一看就懂的栗子对比说明 stream().map() 与stream().flatMap()的使用和区别:
5.1、stream().map()的基本使用:
list.stream().map(a -> a + 1).forEach(System.out::print);//map操作,把每个元素都+1再输出
List<String> collect = menu.stream().map(m -> m.getName()).collect(toList());//map操作,获得菜单集中的菜名,并生成一个流
System.out.println(collect);
5.2、stream().map()难以将String数组转为一个字符流 即不能这样:{“Goodbye”, “World”} -> {G,o,o,d,b,y,e,W,o,r,l,d}。看下面案例解析:
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String[]> arrayStream = Arrays.stream(arrayOfWords)
//split方法分割每个单词的字符,map得到一个是Stream<String[]> 类型的数组流
.map(s -> s.split(""));
Stream<Stream<String>> streamStream = arrayStream
//想得到一个Stream<String>字符流,所以尝试再一次对上面结果进行map,让每个数组变成一个单独的流;
//结果却是得到了一个流的列表,即,这个流是由多个单独的流组成的,显然这不是我们想要的,我们也无法进行去重。
.map(Arrays::stream);
5.3、stream().flatMap() 可以把map()生成的数组流合成一个流
Stream<String> stringStream = Arrays.stream(arrayOfWords)
.map(m -> m.split(""))
//此处得到的是Stream<String>类型的一个字符流,可以轻易地进行许多后续操作,比如去重
.flatMap(Arrays::stream);
stringStream.distinct().forEach(System.out::print);//结果:GodbyeWrl
Optional 容器类
再说find方法前,我们先大概了解一个类:Optional和它的一些方法,后面会出博文专门解释这个类。
Optional<T> class (java.util.Optional)是一个容器类,代表一个值存在或不存在,比如下面findFirst也可能找不到值
来看看Optional容器的常用方法:
- isPresent():Optional包含有值的时候返回true, 否则返回false。
- ifPresent(Consumer<T> block):会在值存在的时候执行给定的代码块。
- T get():会在值存在时返回值,否则抛出一个NoSuchElement异常。
- T orElse(T other):会在值存在时返回值,否则返回一个默认值。
6、Find
findFirst:作用找到流的第一个元素,它的返回值是一个Optional<T>类
findAny:作用返回当前流中的任意元素,它的返回值是一个Optional<T>类
findFirst与findAny区别:
- 在串行的流中,findAny和findFirst返回的都是第一个对象;
- 而在并行流(parallelStream())中,findAny返回的是最快处理完的那个线程的数据。
- 并行操作中,findAny的效率会比findFirst要快
看下面例子就懂了:
Optional<Integer> first = list.stream().findFirst();
System.out.println(first.get());//输出:1
/**找到list(流)中的任意一个偶数**/
Integer integer = list.stream().filter(t -> t % 2 == 0).findAny().get();//得到是第一个偶数2,和findFist结果一样
System.out.println(integer);
Integer integer1 = list.parallelStream().filter(t -> t % 2 == 0).findAny().get();//得到的是偶数6
System.out.println(integer1);
7、match
allMatch:作用是判断流中的元素是否都能匹配给定的Predicate<? super T> 函数规则,返回Boolean
anyMatch:作用是判断流中是否有一个元素能匹配给定的Predicate<? super T> 函数规则,返回Boolean
noneMatch:确保流中没有任何元素与给定的Predicate<? super T> 函数规则匹配 ,返回Boolean
boolean b = menu.stream().anyMatch(Dish::isVegetarian);//判断流中是否有素食(Vegetarian)
//System.out.println(b);//true
boolean b1 = menu.stream().allMatch(d -> d.getCalories() < 400);//判断是否所有菜的热量都低于400卡路里
//System.out.println(b1);//false
boolean b2 = menu.stream().noneMatch(Dish::isVegetarian);//判断流中是否不能存在素食
//System.out.println(b2);//false
8、reduce
reduce作用是将流中所有元素反复结合起来,进行更复杂的查询,进而得到一个值,比如“计算int集合里所有数字相加的和”或“找出int集合中的最大数字”
reduce接受两个参数:
- 第一个是初始值;
- 第二个是BinaryOperator<T> 函数
图解reduce下有初始值情况下的求和流程:
8.1 有初始值情况下(0也是初始值哦),求和
Integer reduce = list.stream().reduce(0, (s, w) -> s + w);//对list集合元素进行求和
Integer reduce1 = list.stream().reduce(0, Integer::sum);//等同于上式
8.2 无初始值情况下,求和
这里为什么它返回一个Optional<Integer>呢?因为考虑到流中没有任何元素的情况,reduce操作无法返回其和,因为它没有初始值。结果被包裹在一个Optional对象里,以表明和可能不存在,处理结果为null的情况
Optional<Integer> sum = list.stream().reduce(Integer::sum);
8.3 求最大值和最小值
Optional<Integer> max = list.stream().reduce(Integer::max);
Optional<Integer> min = list.stream().reduce(Integer::min);
三、小结
比较常用的一些流操作汇总
java8 新特性解析(更新中):
Java8新特性1:lambda表达式入门–由浅入深,从单发步枪迈向自动步枪
Java8新特性3:Stream1——什么是Stream,Stream的特性,如何使用Stream,Stream与Collection集合的区别