《Java8函数式编程》学习笔记 - 4.1

类库

4.1 在代码中使用Lambda表达式

前面讨论了如何编写Lambda表达式,接下来阐述的是如何使用Lambda表达式

//例4-1 使用isDebugEnabled方法降低日志性能开销
Logger logger = new Logger();
if (logger.isDebugEnabled()) {
    logger.debug("Look at this: " + expensiveOperation());
}

直接调用debug方法能够省去记录文本信息,<-省去记录文本信息这句话怎么理解?
但还是会调用expensiveOperation方法,因此用if语句显式判断,可以让程序跑的更快。

如果用lambda表达式,生成一条用作日志信息的字符串。
只有日志级别在调试或以上级别时,才会执行该lambda表达式。

// 例4-2 使用Lambda表达式简化日志代码
Logger logger = new Logger();
logger.debug(() -> "Look at this: " + expensiveOperation());

问:相应的在Logger类中该debug方法应该如何实现?
答:从类库的角度看(从类库的角度看这句话本身怎么理解?)
使用内置的Supplier(生产者)函数接口,它只有一个get方法。
然后通过调用isDebugEnabled判断是否需要记录日志,是否需要调用get方法。

//启用Lambda表达式实现的日志记录器
public void debug(Supplier<String> message) {
    if (isDebugEnabled()) {
        debug(message.get());
    }
}

调用get方法,相当于调用传入的Lambda表达式。
- 这种方式也能和匿名内部类一起工作。
- 如果用户暂时无法升级到Java 8,这种方式可以实现向后兼容。

4.2 基本类型

  • 如果只有一个可能的目标类􀅖 型,由相应函数接口里的参数类型推导得出;
  • 如果有多个可能的目标类型,由最具体的类型推导得出;
  • 如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型。

int和Integer,前者是基本类型,后者是装箱类型。
Java 的泛型是基于对泛型参数类型的擦除
↓针对这句话的理解,可以看下面这篇博客。
Java 泛型,你了解类型擦除吗?
装箱类型是对象,因此在内存中存在额外开销。
比如整型在内存中占四个字节,整型对象却要占用16字节。
将基本类型转换成装箱类型,称为装箱,反之则成为拆箱。两者也都需要额外的开销

为了减小这些性能开销,Stream类的某些方法对基本类型和装箱类型做了区分。
Java8中,仅对整型,长整型,和双浮点型做了特殊处理。

顺便理解了一下,byte类型(一个字节)的范围是-128~127,而不是-127~127的原因。
Java中,为什么byte类型的取值范围为-128~127?
⇒大学里的基础知识,真的是忘得一干二净。

对基本类型做特殊处理的方法,在命名上有明确的规范。

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

这些基本类型都有与之对应的Stream,以基本类型名为前缀,如LongStream。←没找到这个类型
mapToLong的方法返回的不是一个一般的Stream,是一个特殊处理的Stream。
在这个特殊的Stream中,map方法的实现方式也不同。
它接受一个LongUnaryOperator函数,将一个长整型值映射成另一个长整型值。
通过一些高阶函数装箱方法,如mapToObj,也可以从一个基本类型的Stream得到一个装箱后的Stream,如Stream←这段话还是要好好理解。

//例4-4使用summaryStatistics方法统计曲目长度
public static void printTrackLengthStatistics(Album album) {
    IntSummaryStatistics trackLengthStats
        = album.getTracks()
            .mapToInt(track -> track.getLength())
            .summaryStatistics();
    System.out.printf("Max: %d, Min: %d, Ave: %f, Sum: %d",
        trackLengthStats.getMax(),
        trackLengthStats.getMin(),
        trackLengthStats.getAverage(),
        trackLengthStats.getSum());
}

mapToInt,返回每首曲目的长度。
因为该方法返回一个IntStream对象,包含一个summaryStatistics方法,
这个方法能计算出各种各样的统计数据,返回成IntSummaryStatistics类型。

4.3 重载解析

BinaryOperator 是一种特殊的BiFunction 类型,参数的类型和返回值的类型相同。比如, 两个整数相加就是一个BinaryOperator。

java的重载,会自动挑出最具体的类型。 Lambda表达式的类型就是对应的函数接口类型,因此Lambda表达式作为参数传递时,情况也依然如此。

操作时可以重载一个方法,分别接受BianaryOperatorhe该接口的一个子类型作为参数。

//例4-7 另外一个重载方法调用
overloadedMethod((x, y) -> x + y);
//例4-8 两个重载方法可供选择
private interface IntegerBiFunction extends BinaryOperator<Integer> {
}
private void overloadedMethod(BinaryOperator<Integer> Lambda) {
    System.out.print("BinaryOperator");
}
private void overloadedMethod(IntegerBiFunction Lambda) {
    System.out.print("IntegerBinaryOperator");
}
//例4-9重载方法导致的编译错误
overloadedMethod((x) -> true);
private interface IntPredicate {
    public boolean test(int value);
}
private void overloadedMethod(Predicate<Integer> predicate) {
    System.out.print("Predicate");
}
private void overloadedMethod(IntPredicate predicate) {

什么叫“代码异味”:需要调查一下

4.4 @FunctionalInterface

每个用作函数接口的接口都应该添加这个注释。

Java中有一些接口,虽然只有一个方法,但并不是为了使用Lambda表达式来实现的。
比如有些对象内部可能保存着某种状态,使用带有一个方法的接口可能纯属巧合。
java.lang.Comparable 和java.io.Closeable 就属于这样的情况。

如果一个类是可比较,意味着在该类的实例之间存在着某种顺序。
Lambda表达式应该是既没有属性,也没有状态的一个闭包。
如果一个东西既没有属性也没有状态,拿什么比较呢?

同理一个可关闭的独享必须持有某种打开的资源,譬如一个需要关闭的文件句柄。
同样,该接口也不能是一个纯函数。因为关闭资源是更改状态的另一种形式。

和Closeable和Comparable接口

4.5 二进制接口的兼容性

4.6 默认方法

4.7 多重继承

4.8 权衡

4.9 接口的静态方法

4.10 Optional

4.11 要点回顾