Lambda表达式,函数式接口,方法引用与构造器引用,Stream API,Optional类
1. 介绍
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本
Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以 来最具革命性的版本。Java 8为Java语言、编译器、类库、开发 工具与JVM带来了大量新特性
速度更快
代码更少(增加了新的语法:Lambda 表达式)
强大的 Stream API:
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率
parallel() 与 sequential() 在并行流与顺序流之间进行切换
便于并行
最大化减少空指针异常:Optional
Nashorn引擎,允许在JVM上运行JS应用
2. Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码
(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
在Java 8中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符 或箭头操作符。它将 Lambda 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的参数列表
- 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。
2.1. Lambda语法
无参,无返回值
1
Runnable ri = ()->{System.out.println("Hello Lambda!");};
一个参数,但是没有返回值
1
Consumer<String> con = (String str)->{System.out.println(str);};
数据类型可以省略 - 类型推断
Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”
1
Consumer<String> con = (str)->{System.out.println(str);};
需要一个参数时,参数的小括号可以省略
1
Consumer<String> con = str->{System.out.println(str);};
需要两个或以上的参数,多条执行语句,并且可以有返回值
1
2
3
4Comparator<Integer> com = (x,y)->{
System.out.println("实现函数式接口方法!");
return Integer.compare(x,y);
}体只有一条语句时,return 与大括号若有,都可以省略
1
Comparator<Integer> com = (x,y)->Integer.compare(x,y);
2.2. Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
2.2.1. 性能浪费的日志案例
日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。 一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
1 | public class Demo01Logger { |
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进 行字符串拼接。
如: LOGGER.debug(“变量{}的取值为{}。”, “os”, “macOS”) ,其中的大括号 {} 为占位 符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但Lambda可以做到更好
使用Lambda必然需要一个函数式接口:
1 |
|
然后对 log 方法进行改造:
1 | public class Demo02LoggerLambda { |
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
证明Lambda的延迟,下面的代码可以通过结果进行验证
1 | public class Demo03LoggerDelay { |
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的
2.3. 使用Lambda作为参数和返回值
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。
例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。
1 | public class Demo04Runnable { |
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。
1 | import java.util.Arrays; |
其中直接return一个Lambda表达式即可。
3. 函数式接口概念
3.1. 如何理解函数式接口
- Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP) 编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在 Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型—函数式接口。
- 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是 Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
- 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
3.2. 什么是函数式(Functional)接口
函数式接口 : 有且仅有一个抽象方法的接口。
即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda
语法糖
指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。
只要确保接口中有且仅有一个抽象方法即可
1 | 修饰符 interface 接口名称 { |
由于接口当中抽象方法的 public abstract 是可以省略的,
所以定义一个函数式接口很简单
1 | public interface MyFunctionalInterface { |
3.3. @FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上:
1 |
|
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
3.4. 常用函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是最简单的几个接口及使用示例。
3.4.1. 核心接口
函数式接口 | 参数类型 | 返回类型 | 抽象方法 | 描述 |
---|---|---|---|---|
Consumer<T> |
T | void | void accpet(T t) | 处理一个T类型的值 |
Supplier<T> |
无 | T | T get() | 提供一个T类型的值 |
Function<T,R> |
T | R | R apply(T t) | T类型对象应用操作,结果是R类 |
Predicate<T> |
T | boolean | boolean test(T t) | 判断T类型是否满足某约束 |
3.4.2. 其他接口
函数式接口 | 参数类型 | 返回类型 | 方法 | 用途 |
---|---|---|---|---|
BiFunction<T,U,R> |
T, U | R | R apply(T t, U u) | 对类型为T, U参数应用操作,返回R类型的结果 |
UnaryOperator<T> (Function子接口) |
T | T | T apply(T t) | 对类型为T的对象进行一元运算,并返回T类型的结果 |
BinaryOperator<T> (BiFunction 子接口) |
T, T | T | T apply(T t1, T t2) | 对类型为T的对象进行二元运算,并返回T类型的结果 |
BiConsumer<T,U> |
T, U | void | void accept(T t, U u) | 对类型为T, U参数应用操作 |
BiPredicate<T,U> |
T,U | boolean | boolean test(T t,U u) | 判断T,U类型是否满足某约束 |
ToIntFunction<T> ,ToLongFunction<T> ,ToDoubleFunction<T> |
T | int,long double | int applyAsInt(T value) long applyAsLong(T value) double applyAsDouble(T value) |
分别计算int、long、double值的函数 |
IntFunction<R> ,LongFunction<R> ,DoubleFunction<R> |
int,long double | R | R apply(int value) R apply(long value) R apply(double value) |
参数分别为int、long、double 类型的函数 |
3.4.3. Supplier接口 - T get()
java.util.function.Supplier<T>
接口仅包含一个无参的方法: T get()
。
用来获取一个泛型参数指定类型的对 象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象 数据。
1 | import java.util.function.Supplier; |
3.4.3.1. 求数组元素最大值
1 | public class Demo02Test { |
3.4.4. Consumer接口
java.util.function.Consumer<T>
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。
3.4.4.1. void accept(T t)
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。
基本使用如:
1 | import java.util.function.Consumer; |
当然,更好的写法是使用方法引用。
3.4.4.2. Consumer andThen(Consumer)
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。
1 | default Consumer<T> andThen(Consumer<? super T> after) { |
java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。
例如两个步骤组 合的情况:
1 | import java.util.function.Consumer; |
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的 组合。
3.4.4.3. 格式化打印信息
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓 名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实 例,将两个 Consumer 接口按照顺序“拼接”到一起。
1 | public static void main(String[] args) { |
解答
1 | import java.util.function.Consumer; |
3.4.5. Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用
java.util.function.Predicate<T>
接口。
3.4.5.1. boolean test(T t)
用于条件判断的场景
1 | import java.util.function.Predicate; |
条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。
3.4.5.2. Predicate and(Predicate)
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实 现“并且”的效果时,可以使用default方法 and
1 | default Predicate<T> and(Predicate<? super T> other) { |
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么: import java.util.function.Predicate;
1 | public class Demo16PredicateAnd { |
3.4.5.3. Predicate or(Predicate)“或”
or 实现逻辑关系中的“或”
1 | default Predicate<T> or(Predicate<? super T> other) { |
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称
1 | import java.util.function.Predicate; |
3.4.5.4. Predicate negate(Predicate)“非”
1 | default Predicate<T> negate() { |
从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前
调用 negate 方法,正如 and 和 or 方法一样:
1 | import java.util.function.Predicate; |
3.4.5.5. 集合信息筛选
数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件: 1. 必须为女生; 2. 姓名为4个字。
1 | public class DemoPredicate { |
解答
1 | import java.util.ArrayList; |
3.4.6. Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
3.4.6.1. R apply(T t)
根据类型T的参数获取类型R的结果。 使用的场景例如:将 String 类型转换为 Integer 类型。
1 | import java.util.function.Function; |
当然,最好是通过方法引用的写法。
3.4.6.2. Function andThen(Function)
1 | default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { |
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
1 | import java.util.function.Function; |
第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一 起。
请注意,Function的前置条件泛型和后置条件泛型可以相同。
3.4.6.3. 自定义函数模型拼接
请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为: String str = “赵丽颖,20”;
- 将字符串截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为int类型的数字;
- 将上一步的int数字累加100,得到结果int数字。
解答
1 | import java.util.function.Function; |
4. 方法引用与构造器引用
4.1. 方法引用
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向 一个方法,可以认为是Lambda表达式的一个语法糖。
- 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
- 格式:使用操作符 “::” 将类(或对象) 与方法名分隔开来。
- 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
4.1.1. 类::实例方法名
1 | Comparator<Integer> com = (x,y)->Integer.compare(x,y); |
当函数式接口方法的第一个参数是需要引用方法的调用者,且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
1 |
|
4.2. 构造器引用 class::new
与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。
n->new MyClass(n)
->MyClass::new
1 | class MyClass{ |
4.3. 数组引用 type[]::new
n->new Integer[n]
-> Integer[]::new
1 |
|
5. Stream
5.1. 定义
Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等
和以前的Collection操作不同, Stream操作还有两个基础的特征:
1. Pipelining
中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
2. 内部迭代
以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)
数据转换
执行操作获取想要的结果
每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
注意:
1. Stream 自己不会存储元素。
2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
5.2. 获取Stream方法
5.2.1. 通过集合Collection.stream()
default Stream<E> stream()
: 返回一个顺序流default Stream<E> parallelStream()
: 返回一个并行流
1 | import java.util.stream; |
5.2.2. 通过数组Arrays.stream(T[])
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
public static <T> Stream<T> stream(T[] array)
public static IntStream stream(int[] array, int startInclusive, int endExclusive)
重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)
1 |
|
5.2.3. 通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个 流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values)
: 返回一个流
1 | Stream<Integer> s=Stream.of(1,2,3,4,5); |
5.2.4. 创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
它的值包含种子,在种子上调用f产生的值,在前一个元素上调用f产生的值
生成
public static<T> Stream<T> generate(Supplier<T> s)
它的值通过反复调用函数s而构建
1 |
|
5.2.5. 正则表达式中的流
splitAsStream() 按照则表达式来分割一个CharSequence对象,分割为一个个单词
1 | Stream<String> stream = Pattern.compile("\\D").splitAsStream("1,2,3,4"); |
5.3. Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
5.3.1. 筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补 |
peek(Consumer) | 它与当前流的元素相同,在获取每个元素时,会将其传递给action |
5.3.2. 映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流,每个结果为一个流 |
代码:
1 |
|
5.3.3. 排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
5.4. Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
流进行了终止操作后,不能再次使用。
5.4.1. 匹配与查找
方法 | 描述 | 返回值 |
---|---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 | boolean |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 | boolean |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 ` | boolean |
findFirst() | 返回第一个元素 | Optional |
findAny() | 返回当前流中的任意元素 | Optional |
count() | 返回流中元素总数 | long |
max(Comparator c) | 返回流中最大值 | Optional |
min(Comparator c) | 返回流中最小值 | Optional |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了) | void |
5.4.2. 归约
方法 | 描述 |
---|---|
Optional< T> reduce(BinaryOperator< T> accumulator); | 可以将流中元素通过BinaryOperator结合起来,返回Optional< T> |
T reduce(T identity, BinaryOperator< T> accumulator); | 可以将流中元素从identity开始通过BinaryOperator结合起来,得到一个值。返回T类型 |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
BinaryOperator接口有抽象方法R apply(T t, U u);
1 |
|
案例:
1 | List<Integer> list = new ArrayList<>(); |
5.4.3. 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:
5.4.3.1. Collectors 实用类静态方法
方法 | 返回类型 | 作用 |
toList | List<T> | 把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList()); | ||
toSet | Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); | ||
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
Collection<Employee> emps= list.stream().collect(Collectors.toCollection(ArrayList::new)); | ||
counting | Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting()); | ||
summingInt | Integer | 对流中元素的整数属性求和 |
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | ||
averagingInt | Double | 计算流中元素Integer属性的平均值 |
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | ||
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如:平均值 |
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); | ||
joining | String | 连接流中每个字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); | ||
maxBy | Optional<T> | 根据比较器选择最大值 |
Optional |
||
minBy | Optional<T> | 根据比较器选择最小值 |
Optional |
||
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); | ||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转 换函数 |
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List<T>> | 根据某属性值对流分组,属性为K 结果为V |
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | ||
partitioningBy | Map<Boolean, List<T>> | 根据true或false进行分区 |
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
IntStream,LongStream,DoubleStream
基本数据类型 | 基本数据流 |
---|---|
byte | IntStream |
short | IntStream |
int | IntStream |
long | LongStream |
float | DoubleStream |
double | DoubleStream |
char | IntStream |
boolean | IntStream |
5.4.4. 创建IntStream流
5.4.4.1. IntStream of(int… values)
1 | IntStream stream = IntStream.of(1, 2, 3, 4); |
5.4.4.2. Arrays.stream(int[] array)
1 | int[] value = new int[]{1,2,3,4}; |
5.4.4.3. IntStream range(from,to)
生成0-9,10个元素
1 | IntStream range = IntStream.range(0, 10); |
5.4.4.4. IntStream rangeClosed(from,to)
生成0-10,11个元素
1 | IntStream intStream = IntStream.rangeClosed(0, 10); |
5.4.4.5. mapToXXX
对象流转为基本数据流
1 | public interface Stream<T> extends BaseStream<T, Stream<T>> { |
案例:取字符流中的字符长度作为整数流
1 | List<String> list = new ArrayList<>(); |
5.4.5. IntStream基本方法
5.4.5.1. int[] toArray()
返回基本数据类型
1 | int[] ints = IntStream.rangeClosed(0, 10).toArray(); |
5.4.5.2. OptionalInt findFirst()
返回第一个元素
1 | List<String> list = new ArrayList<>(); |
5.4.5.3. OptionalInt max()
返回最大值
1 | List<String> list = new ArrayList<>(); |
5.4.5.4. OptionalInt min()
返回最小值
1 | List<String> list = new ArrayList<>(); |
5.4.5.5. OptionalDouble average()
平均值
1 | List<String> list = new ArrayList<>(); |
5.4.5.6. summaryStatistics
可同时返回最大值max,最小值min,平均值average,总和sum,总数count
1 | IntSummaryStatistics statistics = toInt.summaryStatistics(); |
5.4.5.7. Stream< Integer> boxed()
转化为包装器对象流
1 | Stream<Integer> boxed = toInt.boxed(); |
5.5. 并行流
只要在终结方法执行时,流处于并行模式,所有中间结果流操作都将被并行化
并行运行时,要让其返回结果与顺序执行时返回的结果相同
1 | //用Collection.parallelStream()获取并行流 |
6. Optional类
- 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。 以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类, Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代 码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional<T>
类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。- Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在 则isPresent()方法会返回true,调用get()方法会返回该对象。
6.1. 常用方法
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
6.1.1. 创建Optional类对象的方法
6.1.1.1. Optional.of(T value)
创建一个 Optional 实例,value必须非空; value为null,会抛出NullPointerException对象
1 | public static <T> Optional<T> of(T value) { |
6.1.1.2. Optional.empty()
创建一个空的 Optional 实例
1 | private static final Optional<?> EMPTY = new Optional<>(); |
6.1.1.3. Optional.ofNullable(T value)
value可以为null
1 | public static <T> Optional<T> ofNullable(T value) { |
6.1.2. 判断Optional容器中是否包含对象
6.1.2.1. boolean isPresent()
判断是否包含对象
1 | public boolean isPresent() { |
6.1.2.2. void ifPresent(Consumer<? super T> consumer)
如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
1 |
|
6.1.3. 获取Optional容器的对象
6.1.3.1. T get()
如果调用对象包含值,返回该值,否则抛异常NoSuchElementException
1 | public T get() { |
6.1.3.2. T orElse(T other)
如果有值则将其返回,否则返回指定的other对象。
1 | public T orElse(T other) { |
6.1.3.3. T orElseGet(Supplier<? extends T> other)
如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
1 | public T orElseGet(Supplier<? extends T> other) { |
6.1.3.4. T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
1 | public <X extends Throwable> T orElseThrow( |
6.2. 案例
1 |
|