1、基本类型

Java 中,有一些相伴的类型,比如 int 和 Integer—— 前者是基本类型,后者是装箱类型。基本类型内建在语言和运行环境中,是基本的程序构建模块;而装箱类型属于普通的 Java 类,只不过是对基本类型的一种封装。

将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。对于需要大量数值运算的算法来说,装箱和拆箱的计算开销,以及装箱类型占用的额外内存,会明显减缓程序的运行速度。

为了减小这些性能开销,Stream 类的某些方法对基本类型和装箱类型做了区分。高阶函数 mapToLong 和其他类似函数即为该方面的一个尝试。在 Java 8 中,仅对整型、长整型和双浮点型做了特殊处理,因为它们在数值计算中用得最多,特殊处理后的系统性能提升效果最明显。

如果方法返回类型为基本类型,则在基本类型前加 To,如 ToLongFunction。如果参数是基本类型,则不加前缀只需类型名即可,如 LongFunction。如果高阶函数使用基本类型,则在操作后加后缀 To 再加基本类型,如mapToLong。

2、重载解析

在 Java 中可以重载方法,造成多个方法有相同的方法名,但签名确不一样。这在推断参数类型时会带来问题,因为系统可能会推断出多种类型。这时,javac 会挑出最具体的类型。

Lambda 表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循如下规则:
• 如果只有一个可能的目标类型,由相应函数接口里的参数类型推导得出;
• 如果有多个可能的目标类型,由最具体的类型推导得出;
• 如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型。

3、@FunctionalInterface

该注释会强制 javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac 就会报错。重构代码时,使用它能很容易发现问题。

4、默认方法

接口中这样的方法叫作默认方法,在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。

默认方法示例:forEach 实现方式

  1. default void forEach(Consumer<? super T> action) {
  2. for (T t : this) {
  3. action.accept(t);
  4. }
  5. }

三定律
如果对默认方法的工作原理,特别是在多重继承下的行为还没有把握,如下三条简单的定律可以帮助大家。

  1. 类胜于接口。如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
  2. 子类胜于父类。如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
  3. 没有规则三。如果上面两条规则不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法。

5、Optional

Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。

  1. Optional<String> a = Optional.of("a"); // 创建一个Optional对象
  2. assertEquals("a", a.get()); // 获取Optional中的值
  1. Optional emptyOptional = Optional.empty(); // 创建一个空的Optional对象
  2. Optional alsoEmpty = Optional.ofNullable(null); // 检查其是否有值
  3. assertFalse(emptyOptional.isPresent());

使用 Optional 对象的方式之一是在调用 get() 方法前,先使用 isPresent 检查 Optional对象是否有值。使用 orElse 方法则更简洁,当 Optional 对象为空时,该方法提供了一个备选值。如果计算备选值在计算上太过繁琐,即可使用 orElseGet 方法。该方法接受一个Supplier 对象,只有在 Optional 对象真正为空时才会调用。

  1. assertEquals("b", emptyOptional.orElse("b"));
  2. assertEquals("c", emptyOptional.orElseGet(() -> "c"));