Lambda表达式
Lambda 表达式将函数式编程引入了 Java,Lambda 允许把函数作为一个方法的参数。
函数式编程:Java中,万物皆对象,编写程序时站在有什么对象的角度考虑对象能做什么事。而最终做的事情才是我们最关心的,至于什么对象来做倒是无所谓了,只要事情做好就行,所以在 Java8 中重新思考了某些特定情况下面向函数编程的好处。
函数式编程就是让我们由原来的传递对象变为传递一种行为。
面向对象的束缚:在面向对象的世界中,如果要传递一种行为,必须把这个行为封装到某各类中,然后创建类的对象,再把对象传递出去。
1 |
|
语法
- 传递的函数必须是 @FunctionalInterface(函数式接口) 注解的接口的方法
- 一定要覆盖接口中的唯一的抽象方法
- 方法中固定的形参
特殊情况:该方法的形参只有1个时,可以省略圆括号,该方法只有一行代码时,可以省略花括号和 return 关键字。
需求:对集合中的整数对象排序
1 | public void testSort() { |
内置的函数式接口
java.util.function
函数式接口 | 参数类型 | 返回类型 | 说明 |
---|---|---|---|
Consumer |
T | void | 对类型为 T 的对象操作,方法:void accept(T t) |
Supplier |
无 | T | 返回类型为 T 的对象,方法 T get(); 可用作工厂 |
Function<T, R> 函数处理接口 | T | R | 对类型为 T 的对象操作,并返回结果是 R 类型的对象,方法 R apply(T t) |
Predicate |
T | boolean | 判断类型为 T 的对象是否满足条件,并返回 boolean 值,方法 boolean test(T t) |
- 消费型函数接口:Consumer
有参数无返回 - 供给型函数接口:Supplier
无参数有返回 - 函数处理型接口:Function<T, R> 传入 T 型对象,返回 R 类型对象
- 断言型接口: Predicate
对 T 类型对象断言,返回 boolean 类型结果
接口的默认方法与静态方法
使用 default 关键字,提供默认的实现,所有实现这个接口的类都会接受默认方法的实现,除非子类提供自己的实现。
Java8 之前,如果发现接口中的功能不够,需要在接口中增加新的方法,会出现重大问题,接口中增加一个抽象方法,所有实现类都要跟着一起改动,这样对 Java 整个体系牵连较大。 Java8 提出了接口中可以有非抽象的默认方法,表示抽象方法的默认实现,跟注解中的属性给予默认值是一样的。
这项特性是为了兼容之前接口的特征,在升级时避免改动所有的实现类。
当实现类实现了2个接口,且两个接口中有相同的默认方法时,实现类必须覆盖该方法。
接口中的静态方法,取代工具类。直接在接口中使用 default 静态方法。
1 | public interface Stream { |
方法引用
foreach 实现 接受一个 @FunctionalInterface 函数式接口匿名对象
1 | default void forEach(Consumer<? super T> action) { |
- 对象的引用::实例方法名 (System.out::println)
- 类名 :: 静态方法名 (Supplier
s = Math::random;) - 类名 :: 实例方法名 (Function<String, Integer> f = String::length;)
- 类名 :: new (构造器引用) (Supplier
sp = Date::new;) - 类型[] :: new (数组引用) (Function<Integer, String[]) f2 = String[]::new;)
1 | public void testMethodRef() { |
记住一个点:但凡方法引用,分为几种类型,有参/无参,有返回值/无返回值,简写规则:该方法的形参只有1个时,可以省略圆括号,该方法只有一行代码时,可以省略花括号和 return 关键字。如果方法形参和方法内的实参一致时,可以写成 xx::方法名
Stream API
Java8 的 Stream 是对数据处理的一种抽象描述,就像工厂中的流水生产。在程序中,通过 Stream 提供的操作,对数据进行加工处理,如:过滤、查找、排序、统计等。
Stream 是一套工具,提供了对数据的各种便捷操作,提高操作数据的效率(并行流)
1 | public void testStream() { |
Stream -> 数据处理操作的源生成的元素序列。
- 元素序列 就像集合一样,可以访问特定元素类型的一组有序值,因为集合是数据结构,以特定的时间/控件复杂度存储和访问元素(ArrayList、linkList)。流的目的在于计算,比如 filter、sorted 和 map,集合 -> 存储, 流 -> 计算。
- 源,流会使用一个提供数据的源,如集合、数组或者输入/输出资源。有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
- 数据处理操作,类似数据库的操作,流的数据处理操作:filter、map、reduce、find、match、sort等,流操作可以顺序执行也可以并行执行。
流操作有两个特点
- 流水线,很多流本身会返回一个流,这样多个操作就可以链接起来
- 内部迭代,与使用迭代器外部迭代的集合不同,流的迭代操作是背后进行的
所有功能一起执行,在一次迭代中,流水线上会执行全部功能。在执行终端操作的时候,功能才会运行起来。
Stream 的三种迭代方式
1 |
|
Stream 的操作分类:
- 中间操作:拼接流水线,返回值为 Stream 的大都是中间操作,中间操作支持链式调用,并且 Lambda 代码会延迟执行
- 终端操作(结束操作 terminal operations) 返回值不为 Stream 的为终端操作(立即求值),终端操作不支持链式调用,会实际触发计算后流会关闭,不能继续使用。
Stream 并发和并行
并发,多个任务共享时间段(CPU 切换执行),
并行,多个任务发生在同一时刻(必须多核 CPU),同时执行。
任务可以并行化处理,数据也可以并行化处理。数据并行化处理是指将数据分成快,为每块数据分配单独的处理单元,也就是并行流。Hadoop 最常用的方式,让数据同时在多台机器上做处理。最后把结果汇总。
Java8 以前,对数据(集合、数组)中的若干元素做并发操作,十分繁琐。 Stream 做了很好的支持,其 API 中可以声明通过 parallel() 与 sequential() 在并行流与串行流之间切换。
并行流 CPU 使用率72,串行流39
1 | /** |
Optional
针对空指针异常,推出的技术。
Optional 类是一个可以为 null 的容器对象,如果值存在则 isPresent() 方法会返回 true,调用 get() 方法返回该对象。
Optional 是个容器:保存类型 T 的值,或者保存 null,Optional 可以使得代码不用显式的进行控制检测。
Optional 引入很好的解决空指针异常,也就是说我们在写业务代码时,只要专注于处理业务的逻辑,不需要关心对象是否为 null。
用法
获取 Optional 的静态方法:static<T> Optional<T> ofNullable(T value)
如果为空,返回Optional 描述的指定值,否则返回空的 Optional
操作 Optional 的实例方法:
void ifPresent(Consumer<? super T> comsumer)
如果值存在则使用该值调用 consumer,否则不做任何事情boolean isPresent()
如果值妇女在,方法返回 true,否则 falseT get()
返回值,否则爆出异常T orElse(T other)
如果存在该值,返回值,否则返回 other<U> Optional<U> map(Function<? super T, ? extends U> mapper)
如果存在该值,提供映射方法,如果返回非 null ,返回一个 Optional 描述结果(把一种类型的 Optional 转换成另一种类型的 Optional,为空就不会变)<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果该值存在,返回包含的值,否则爆出由 Supplier 继承的异常。
1 |
|
工具类设计
Optional 完成 StringUtil
StringUtil.java
1 | /** |
疑问,为何空串可以被 null 置换?为何 s.trim() 不会报空指针异常?
看源码:Optional.filter 方法:
Optional.filter 方法底层如下,会做 isPresent()
处理,因此无需担心 predicate 使用 value 时会有空指针异常。而空串为何会被 null 置换,因为 filter 过滤不合格的值,会直接返回 empty()。empty() 就是存了 null 的 Optional。
1 | public Optional<T> filter(Predicate<? super T> predicate) { |
Base64 加密算法
Base64 算法 用于传输二进制数据。可以把二进制数据转换为字符数据,可以用于在 HTTP 环境下传递较长的标识信息。
1 | Base64.Encoder.encode(byte[] src); |