Java 8 函数式编程读书笔记
第一章 简介
- Java 8 增加 Lambda 表达式来支持对大型数据的并发操作 - 核实一下
- 面向对象式对数据进行抽象,函数式编程时对行为进行抽象
第二章 Lambda 表达式
以 Swing 为例,传统做法中监听事件需要如下代码:
1 | button.addActionListener( |
当我们使用 lambda 表达式时可以简写为 button.addActionListener(event -> System.out.println("button clicked"));
Lambda 表达式中的类型都是由编译器推断出来的,但你也可以显示的声明参数类型。
Lambda 表达式引用的是值,而不是变量。
接口 | 参数 | 返回类型 | 示例 |
---|---|---|---|
Predicate<T> | T | boolean | 唱片是否发行 |
Consumer<T> | T | void | 输出一个值 |
Function<T, R> | T | R | 获取Artist 对象的名字 |
Supplier<T> | None | T | 工厂方法 |
UnaryOperator<T> | T | T | 逻辑非(!) |
BinaryOperator<T> | (T, T) | T | 求连个数的乘积(*) |
在复杂的情况下需要指定泛型类型才能使编译通过
1 | // 如果省略掉 <Long> 编译报错:Operator'+'cannotbeappliedtojava.lang.Object,java.lang.Object. |
Predicate 可用于检测对象是否符合要求
1 | // 检测字符串是否以 J 开头 |
Consumer 对传入的参数做操作,没有返回值,例如可以用它实现打印,断言等操作
1 | ret.forEach(System.out::print); |
Function 对传入的对象操作并放回结果
1 | List<String> ret = names.stream().map(String::toUpperCase).collect(Collectors.toList()); |
Supplier 可以帮你生产数据, 但是只能使用应用于无参的 constructor,不支持传入参数
1 | Supplier<User> userSupplier = User::new; |
第三章 流
1 | // for 处理集合模板 |
内部迭代:通过 Steam 对集合类进行复杂操作
1 | // filter:只保留通过某项测试的对象,整个过程被分为两步,过滤和计算 |
filter 中的表达式是惰性求值方法,count 是及早求职方法,惰性求值并不会真正执行
1 | // 此实例中并不会在控制台打印文字 |
如果返回值是 Stream 则为 惰性求值;如果返回值是另一个值或为空则是 及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。
常用的流操作
操作 | 用途 | 示例 |
---|---|---|
collect | 生成集合 | Stream.of(“a”, “b”, “c”).collect(Collectors.toList()); |
map | 类型转换 | Stream.of(“a”).map(string -> string.toUpperCase()).collect(Collectors.toList()); |
filter | 检查过滤 | Stream.of(“a”, “12b”).filter(val -> isDigit(val.charAt(0))).(Collectors.toList()); |
flatMap | 拼接多个 Stream | Stream.of(asList(1, 2), asList(3, 4)).flatMap(numbers -> numbers.stream()).collect(toList()); |
max/min | 最值 | tracks.stream().min(Comparator.comparing(track -> track.getLength())).get(); |
reduce | 提供计算功能 | Stream.of(1,2,3).reduce(0, (acc, ele) - > acc + ele); |
第四章 类库
日志优化
1 | Logger logger = new Logger(); |
使用 Lambda 优化
1 | Logger logger = new Logger(); |
Supplier -> get(), Predicate -> test, Function -> apply.
如果可以的话,在流中尽量使用对基本类型的操作,而不是封装类型。 mapToInt 之类的操作还提供了很多简便操作得到最值和平均值。
Optional是一个新设计的数据类新来替换 null 值。 使用它有两个目的:
- Optional 对象鼓励程序员适时检测变量是否为空,以避免代码缺陷
- 将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单的很多
第五章 高级集合类和收集器
方法引用
语法, artist -> artist.getName() 等价于 Artist::getName, 标准语法为 Classname::methodName. 由此,新建 Artist 对象的代码可以由 (name, nationality) -> new Artist(name, nationality) 简化为 Artist::new, 类似的可以通过 String[]::new 创建新的数组。
stream.collect()
可以生成你想要的集合形式。例如:stream.collect(toCollection(TreeSet::new));
partitioningBy
收集器可用于分流, 与之类似的还有 groupingBy
关键字。
1 | public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) { |
字符串流操作示例:
1 | String ret = artists.steam().map(Artist::getName).collect(Collectors.joining(",", "[", "]")); |
查询并加入 map 的简化操作:
1 | // before |
通过 forEach 简化 map 的统计操作:
1 | // before |
第六章 数据并行化
并行化:同一任务拆分,多核执行
并发化:单核多任务
实现上只需要在调用方法时将 .stream()
改为 .parallelStream()
就行了。但是并不是并行了就快,取决于处理量等其他因素。
影响因素:数据大小, 源数据结构, 装箱, 核的数量, 单元处理开销, 底层还是使用了 fork/join 的模式。
数据结构并行性能:ArrayList, 数组, IntStream.range > HashSet, Treeset > LinkedList, Streams.iterate, BufferedReader.lines
为 array 赋初值:
1 | int[] a = new int[100]; |
第七章 测试,调试和重构
ThreadLocal 优化:
1 | // before |
可以使用 peek 进行流的调试
第八章 设计和架构的原则
列举了 Lambda 和 设计模式, DSL 的结合的例子,和我看这本书的初衷有点远了,先跳过。
第九章 使用 Lambda 表达式编写并发程序
使用 Vertx 框架结合 Lambda 的知识点,实现一个聊天室,跳过。但是它的这个框架我倒是感觉很有意思,灵感是从 NodeJS 那边来的,支持并发。
工作中遇到的一些例子
如果集合包含范型信息,在没有指定具体的范型类的时候,调用 lambda 会报编译错误
当使用注释掉的语句代替现有的语句时就会报编译错误:Non-static method cannot be referenced from a static contex
1 |
|
找了一下网上的解释,虽然有重现和解决方案,但是对它的底层原因并没有很清楚的解释,以后如果有机会再深入了解 lambda 的语法的话,可以再看看
PS: 个人感觉应该是在没有指定 type 的时候,类型判断有问题
从 List 中抽取属性组成新的集合
List of BeanProperty, BeanProperty 有 getName()
方法,如何通过 lambda 函数抽取
1 | List<String> ret = list.stream().map(BeanProperty::getName).collect(Collectors.toList()); |
Map -> Map 转化
Map<String, List<Obj>> 对 list 中的值进行修改,案例简化为 Map<String, List<String>> 将 list 中的 String 转化为大写
第一步先熟悉 list -> list 转化方式
1 | List<String> test = Arrays.asList("a", "c"); |
熟悉 map 转化方式并结合 list 转化
1 | Map<String, List<String>> origin = new HashMap<>(); |
Collectors.toMap() 怎么使用
函数定义:
1 | public static <T, K, U> |
简单理解就是两个参数分别是两个计算式,得到 key 和 value 的对应的值
1 | List<Class<? extends Number>> list = Arrays.asList(Integer.class, Double.class, Long.class); |
Function.identity()
等价于 x -> x
x -> 0
等价于设置常量