5、强大的SteamAPI
- Java8两个重要的改变,第一个是Lambda表达式,另一个则是StreamAPI
- Stream API(java.util.stream)把真正的函数式编程风格引入到java中。这是目前为止对Java类库最好的补充,因为StreamAPI可以极大提供Java程序员的生产力,效率高
- Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查找。也可以使用StreamAPI来执行操作,简言之,StreamAPI提供了一种高效且易于使用的处理数据的方式
5.1、为什么使用StreamAPI
关系型数据库是把过滤在sql语句中体现了,但是有时候需要在java层面来过滤数据(例如:非关系数据库,把所有的数据都查询出来,然后需要java层面来过滤)
Stream和Collection集合的区别
Collection是一种静态的内存数据结构,而stream是有关计算的。前者主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算
5.2、Stream简介
Stream是数据渠道,用户操作数据源(集合、数组等)所生成的元素序列,集合是数据的容器,Stream是操作数据的。
下面有几点是在使用Stream流需要注意的。
- Stream本身不会存储元素
- Stream不会改变源对象。相反,它会返回一个持有结果的新Stream实例
- Stream操作时延迟执行的。这意味着Stream流的操作会等到程序需要结果的时候才执行
5.3、Stream的操作步骤
-
步骤一:创建Stream
通过一个数据源(例如:数组,集合等)获取一个流对象
-
步骤二:中间操作
一个中间操作链(就是一系列的对数据源的操作),对数据源的数据进行相关处理
-
步骤三:终止操作(终端操作)
一旦执行终止操作,才执行中间操作链(延时执行的体现),并产生结果。一旦执行了终止操作,Stream实例就不能使用了
5.3.1、创建Stream对象的四种方式
-
方式一:通过集合
java8中的Collection接口被扩展,提供了两个获取流对象(Stream)的方法
- default Stream<E> stream();返回一个顺序流
- default Stream<E> parallelStream();返回一个并行流
并行流&串行流
- 并行流:把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。与串行流相比,并行流可以很大程度上提高程序的执行效率,java8中将并行进行了优化,我们可以很容易的对数据进行并行处理,StreamAPI可以通过parallel()和sequential()两个方法在并行流与串行流之间进行切换
-
方式二:通过数组
java8中的Arrays的静态方法stream()可以获取数组流。而且还提供了基本数据类型的创建Stream的重载方法
- static <T> Stream<T> Stream(T[] arrys);返回一个数组流
- public static IntStream stream(int[] array);
- public static LongStream stream(long[] array);
- public static DoubleStream stream(double[] array);
-
方式三:通过Stream的of()方法
可以调用Stream类静态方法of(),通过显示值创建一个流。它可以接受任意数量的参数
- public static <T> Stream<T> of(T…values);返回一个流
-
方式四:创建无限流
可以使用静态方法Stream.iteratr()和Stream.generate()两个方法创建无限流(无限流就是可以创建无限个元素对应的Stream实例对象)
-
public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f) //迭代
说明:UnaryOperator<T>是一个函数式接口,是Function<T>函数是接口的子接口,
-
public static<T> Stream]<T> generate(Supplier<T> s) //生成
//无限流可以帮助我们生成一些数据,下面的案例如果没有使用limit()中间操作和System.out::println终止操作 //案例:遍历前10个偶数 //下面用到了终止操作和中间操作 第一个参数是种子,这里表示从0开始 //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f) Stream.interate(0,t->t+2).limit(10).forEach(System.out::println); //public static<T> Stream<T> generate(Supplier<T> sup) Stream.generate(Math::random).limit(10).forEach(System.out::println);
-
5.3.2、Stream的中间操作
Stream流的中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而是在终止操作时一次性全部处理,这种行为可以称为“惰性求值”,下面介一些常用的中之操作
1.筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数目 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
2.映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值多换成另一个流,然后把所有流连接成一个流 |
案例:
//flatMap(Function f)一个接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流(是用于将嵌套的Stream)
//将字符串中的多个字符构成的集合转换为对应的Stream的示例
public void fromStringStream(String str){
ArrayList<Character> list=new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream(); //什么意思呢? 就是将一个str流分成很多个Character流,然后将这些Character流合成一个流
3.排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
5.3.3、Stream的终止操作
- 终止操作会从流的流水线生产结果。其结果可以是任何不是流的值,例如:List,Integer ,甚至是void
- 流进行了终止操作后,不能再次使用
1.Stream匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中的元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中的最小值 |
forEach(Consumer c) | 内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代。相反,Stream API使用内部迭代——它帮你把迭代做完了) |
案例:
//allMatch(Predicate p) 作用:检查是否匹配所有元素
//练习:是否所有的员工的年龄多大于18
boolean allMatch=employees.stream().allMatch(e->e.getAge()>18)
//noneMatch(Predicate p) 作用:检查是否没有匹配的元素
//练习:是否存在员工姓“雷”
employees.stream().noneMatch(e->e.getName().startsWith("雷"));
//findFirst() 作用:返回第一个元素
//注意这里的Optinal类是一个容器,会在下面讲到
Optional<Employee> employee=employees.stream().findFirst();
2.归约
方法 | 描述 |
---|---|
reduce(T iden,BinartOperator b) | 可以将流中元素反复结合起来,得到一个值。返回T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回Optional<T> |
map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名
@Slf4j
public class StreamReduceTest {
//测试map_reduce模式
@Test
public void ReduceTest(){
List list=new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(ThreadLocalRandom.current().nextInt(1000));
}
//使用中间操作map返回的是串行流
Stream stream = list.stream();
log.info("正在完成中间操作map:");
Stream streamString = stream.map(el1 -> {
//将整型流变化为了字符串流
return el1 + "map处理";
});
log.info("正在完成归约操作:");
//使用reduce(归约)操作
Optional reduce = streamString.reduce((el1, el2) -> {
return el1 + "\n" + el2;
});
//得到Optional中的数据
log.info("使用map_reduce得到的结果是:");
reduce.ifPresent(System.out::println);
}
}
3.收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
-
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、set、Map),Collector的类结果图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5brrOk4u-1680098645078)(null)]
-
另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器示例,具体方法与实现如下表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeCLpkql-1680098645293)(null)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQ3HWO4M-1680098644089)(null)]
6、Optional类
**总结:**Optional类就是一个为避免程序中出现空指针异常的工具类(包装类)。
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。
Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在 Optional可以更好的表达这个概念。并且可以避免空指针异常,该类使用final修饰
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional提供的了很多的方法可以帮助我们进行控制检测。
-
创建Optional类对象的方法
- Optional.of(T t) :创建一个Optional实例,t必须非空
- Optional.empty() :创建一个空的Optional实例
- Optional.ofNullable(T t ): 创建一个Optional实例,t可以为null
-
判断Optional容器中是否包含对象:
-
boolean isPresent() : 判断是否包含对象
-
void ifPresent(Consumer<? extends T> other):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它,如果为null,不执行任何操作
-
public Optional filter(Predicate<? super T> predicate); 可以对optional包装的数据进行一些特殊的判断,根据自定条件返回true和false,但是这个返回的boolean值会像基本数类型的包装类一样把boolean转换为一个Optional的对象。(下面是关于filter的源码)
public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } //这里给出了Predicate的部分代码 @FunctionalInterface public interface Predicate<T> { boolean test(T t); }
-
-
获取Optional容器的对象
-
T get() :如果调用对象包含值,返回该值,否则抛异常
-
T orElse(T other) :如果有值则将其返回,否则返回指定的other对象
-
TorElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
-
TorElseThrow(Supplier<? extends X>exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
案例:
//下面示例的Boy和Girl是一个POJO类 //没有注意空指针的写法 public String getGirlName(Boy boy){ return boy.getGirl().getName(); } @Test public void test03(){ Boy boy=new Boy(); //这里Boy存在一个传入一个Girl示例的构造方法 这种情况很容其出现空指针异常,例如boy并没没有设置Girl属性 String girlName=getGirlName(boy); } //优化以后的getGirlName()方法 //原始避免空指针的写法: public String getGirlName1(Boy boy){ if(boy!=null){ Girl girl=boy.getGirl(); if(girl!=null){ return girl.getName(); } } return null; } //使用Optinal类避免空指针的写法: public String getGirlName2(Boy boy){ //这里不能用of()方法,因为传进来的参数本来可能就是null Optional<Boy> boyOptional=Optional.ofNullable(boy); //调用Optional 类的检查空指针的方法orElse(T other); Boy boy=boyOptional.orElse(new Boy(new Girl("FrairyHome"))); //下面的操作一定可以避免空指针 Girl g= boy.getGirl(); //但是这里Girl实例对象可能为null, Optional<Girl> GirlOptinal= Optional.ofNullable(g); Girl girl01 = GirlOptional.orElse(new Girl("FairyHome")); return girl01.getName(); }
-