JavaSE -- StreamAPI 详细介绍(上篇)
Stream
提示:学习 Stream 之前建议要对 Lambda 表达式有过学习
简介
可以理解为一条"流水线"对上面的物品不断加工,而对于 Stream来说待加工的数据就是物品,常搭配Lambda 表达式的方式来使用。
简单使用
Stream创建
- 方式1:通过Collection 接口的 stream() 方法创建流
List<String> list = Arrays.asList("aaa", "bb", "cc");
Stream<String> stream = list.stream();
- 使用数组创建流
String arr[] = {"aaa", "bb", "cc"};
Stream<String> stream = Arrays.stream(arr);
- 方式2: 使用 StreamAPI 的静态方法创建流
//1.
//获得固定大小的流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");//2.
//第一个参数,相当于初始值
Stream<Integer> stream = Stream.iterate(2, x -> x + 2);
//该方式创建的流是无限流,可以使用 limit 限流
stream.limit(10).forEach(System.out::println);//3.
// 获得无限流,流里面每个元素都是随机数
Stream<Double> stream = Stream.generate(() -> Math.random());
stream.limit(10).forEach(System.out::println);
- 方式3:Files 类的方法 lines() / list()
- 方式4:合并流
// 将两个流合并成一个流
Stream<String> stream1 = Arrays.stream(new String[]{"aaa", "bbb"});
Stream<String> stream2 = Arrays.stream(new String[]{"ccc", "ddd"});
Stream<String> stream3 = Stream.concat(stream1, stream2);
Stream分类
- 中间操作:每次操作会返回一个新的流,类似于流水线上的加工环节
- 终端操作:终端操作结束后,流就无法使用了,类似于流水线最后的打包收集环节。
Stream特性
- 不存储数据,按照给定规则,对数据进行加工,一般最后会返回一个集合或者值
- 不改变数据源。通常对数据源的数据进行加工后会返回已经新集合,而原来的数据源是不会变的
- 就有延迟执行的特性。只有调用终端操作时,中间操作才开始执行。
Stream和集合的差异
1.核心目标不同:
集合(如 List/Set) | Stream |
---|---|
存储数据:用于数据的存储和随机访问(如 list.get(0) )。 | 处理数据:用于数据的转换、过滤、聚合等操作(如 filter , map , collect )。 |
2.迭代方式不同
集合操作 | Stream 流操作 |
---|---|
外部迭代:通过显式循环(如 for /foreach )遍历元素。 | 内部迭代:通过 forEach 等方法隐式遍历,无需手动控制循环。 |
3. 数据处理模式
集合操作 | Stream 流操作 |
---|---|
命令式编程:通过显式步骤操作数据(如逐个元素修改)。 | 声明式编程:通过组合操作描述“做什么”,而非“如何做”(如 stream.filter(...).map(...) )。 |
Optional类
Optional 类是 JDK8 引进来的,主要是用来解决空指针异常(NullPointerException)的。跟准确的说就简化我们防范空指针异常操作步骤的
Optional的由来
在 JDK8 以前我们为了防范空指针异常通常会很多判断。当我们使用了 Optional类就可以简化这些判断。
比如对于如下代码
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();//这里是上面代码的另一种写法,主要用来防止空指针异常的,可以看出来代码非常冗长不好管理
if (user != null) {Address address = user.getAddress();if (address != null) {Country country = address.getCountry();if (country != null) {String isocode = country.getIsocode();if (isocode != null) {isocode = isocode.toUpperCase();}}}
}
Optional常用的方法
- Optional.empty() :
创建空的 Optional 类,里面没有值,当你要尝试获取时,会爆 NoSuchElementException 的异常
- Optional.of(user) 和 Optional.ofNullable(user) :
创建一个包含值得 Optional 对象,这两个方法的区别在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException。
我们并没有完全摆脱 NullPointerException。因此,你应该明确对不为 null 的时候使用 of()。如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法。
- get():
从 Optional 实例中取回实际值对象的方法,这个方法会在值为 null 的时候抛出异常。
- isPresent():
检查 Optional 对象是否有值的。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式
// 前面方法的综合代码实例
@Test
public void test() {User user = new User("john@gmail.com", "1234");Optional<User> opt = Optional.ofNullable(user);assertTrue(opt.isPresent());assertEquals(user.getEmail(), opt.get().getEmail());
}// 另一种写法关于 isPresent()
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
- orElse()
它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值(默认值)
@Test
public void test() {User user = null;User user2 = new User("anna@gmail.com", "1234");User result = Optional.ofNullable(user).orElse(user2);assertEquals(user2.getEmail(), result.getEmail());
}
- orElseGet()
这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果
@Test
public void test() {User user = null;User user2 = new User("anna@gmail.com", "1234");User result = Optional.ofNullable(user).orElseGet( () -> user2);
}
orElse() 和 orElseGet() 的不同之处
乍一看,这两种方法似乎起着同样的作用。确实当传入的对象为空的时候,执行效果确实一样,但是当传入对象不为空的时候,就会有些许差异,首先看下面代码
@Test
public void test() {User user = new User("john@gmail.com", "1234");User result = Optional.ofNullable(user).orElse(createNewUser());User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
- 这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然会创建 User 对象,而且这对象后面还没有用(浪费)。与之相反,orElseGet() 当传入值不为空是不会执行函数式接口的,也就不会创建对象。
- 在这里看来多创建一个对象好像没什么影响,但是在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
Stream的操作
1. 遍历/查找/匹配操作
1.1 foreach()
遍历操作
//获取一个已经初始化好的集合
List<User> list = init();
list.stream().forEach(System.out::println);
1.2 findFirst()
查找流的第一个元素,返回 Optional
//获取一个已经初始化好的集合
List<User> list = init();
//返回一个Optional类
Optional<User> first = list.stream().findFirst();
//获取Optional里面的值
User user = first.get();
1.3 findAny()
查找任意元素,返回Optional,如果是单一流Stream,返回第一个元素,如果是并行流 parallelStream,返回随机一个元素
public static void main(String[] args) {//获取一个已经初始化好的集合List<User> list = init();//返回一个Optional类Optional<User> first = list.stream().findAny();//获取Optional里面的值User user = first.get();
}
1.4 anyMatch()
只要有一个元素满足给定条件返回 True,否则返回 False
//获取一个已经初始化好的集合
List<User> list = init();
boolean res = list.stream().anyMatch(user -> user.getAge() < 25);
System.out.println(res);
1.5 noneMatch()
当所有元素都不符合条件时。返回 True,否则返回 False
//获取一个已经初始化好的集合
List<User> list = init();
boolean res = list.stream().noneMatch(user -> user.getAge() < 24);
System.out.println(res);
1.6 allMatch()
当流中所有元素都满足条件返回 True,否则返回 False
//获取一个已经初始化好的集合
List<User> list = init();
boolean res = list.stream().allMatch(user -> user.getAge() > 23);
System.out.println(res);
2. 过滤与切片操作
2.1 filter(过滤)
也称为筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到一个新的流当中的操作
//获取一个已经初始化好的集合
List<User> list = init();
//返回一个新的流
Stream<User> userStream = list.stream().filter(user -> user.getAge() > 23);
2.2 切片
从集合中取出一部分相应的元素重新组成一个集合,主要细分为一下三个操作
- limit():只保留集合前几个元素
- skip():跳过集合前几个元素,保留后面的
- distinct():给集合元素去重,注意对于自定义数据类型,要重写equals 和 hashCode 方法
//获取一个已经初始化好的集合
List<User> list = Arrays.asList("aa","bb","cc","cc","dd");
//返回一个新的流
Stream<User> userStream1 = list.stream().limit(2); // "aa", "bb"
Stream<User> userStream2 = list.stream().skip(2); // "cc", "cc", "dd"
Stream<User> userStream3 = list.stream().distinct(); // "aa","bb","cc","dd"
3. 聚合操作
细分为以下三个操作
- max():求集合中元素的最大值
- min():求集合中元素的最小值
- count():统计集合中元素的数量
对于这些操作,有人就会问了,这些操作我们通过 Collections.max() / list.size(),也能实现为什么还要借助Stream呢?
- 因为假如我要你先对集合进行过滤操作,将奇数元素剔除掉,然后在求最大值/元素数量呢,显然借助Stream 可以很轻松做到
List<Integer> list = Arrays.asList(9, 5, 1, 7, 2, 3);
// Collections.max(list); //效果一样
Optional<Integer> max = list.stream().filter(x -> x % 2 == 0).max(Integer::compareTo);
Optional<Integer> min = list.stream().filter(x -> x % 2 == 0).min(Integer::compareTo);
// list.size() //效果一样
long count = list.stream().filter(x -> x % 2 == 0).count();