《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接口