Java 8 Map 中新增的方法使用记录

得益于 Java 8 的 default 方法特性,Java 8 对 Map 增加了不少实用的默认方法,像 getOrDefault, forEach, replace, replaceAll, putIfAbsent, remove(key, value), computeIfPresent, computeIfAbsent, compute 和merge 方法。另外与 Map 相关的 Map.Entry 也新加了多个版本的 comparingByKey 和 comparingByValue 方法。

为达到熟练运用上述除 getOrDefault 和 forEach 外的其他方法,有必要逐一体验一番,如何调用,返回值以及调用后的效果如何。看看每个方法不至于 Java 8 那么多年还总是  if(map.containsKey(key))... 那样的老套操作。

前注:Map 新增方法对  present 的判断是 map.containsKey(key) && map.get(key) != null,简单就是  map.get(key) != null,也就是即使 key 存在,但对应的值为 null 的话也视为 absent。absent 就是 map.get(key) == null。

不同 Map 实现对 key/value 是否能为 null 有不同的约束, HashMap, LinkedHashMap, key 和 value 都可以为 null 值,TreeMap 的 key 为不能为 null, 但 value 可以为 null, 而 Hashtable, ConcurrentMap 则 key 和 value 都不同为 null。一句话 absent/present 的判断是 map.get(key) 是否为 null。

方法介绍的顺序是它们相对于本人的生疏程度而定的。每个方法介绍主要分两部分,参考实现代码与示例代码执行效果。参考实现代码摘自 JDK 官方的 Map JavaDoc

getOrDefault 方法

本想忽略这个方法的测试,因为涉及到 key 存在,值为 null 的情况。当 key 不存在或相关联的值为 null 时,返回默认值,否则返回实际值。不要认为 key 存在时总是返回 map.get(key) 的值。

参考实现:

阅读全文 >>

JdbcTemplate 易被 Java 8 Lambda 带入的坑

Spring 的 JdbcTemplate 为我们操作数据库提供非常大的便利,不需要显式的管理资源和处理异常。在我们进入到了 Java 8 后,JdbcTemplate 方法中的回调函数可以用 Lambda 表达式进行简化,而本文要说的正是这种 Lambda 简化容易给我们带来的一个 Bug, 这是我在一个实际项目中写的单元测试发现的。

下面就是我们的一个样板代码,在我们的 UserRespository 中有一个方法 findAll() 用于获得所有用户:

阅读全文 >>

Java8 Lambda 表达式与 Checked Exception

当我们在使用 Java 8 的 Lambda 表达式时,表达式内容需要抛出异常,也许还会想当然的让当前方法再往外抛来解决编译问题,如下面的代码

main() 方法抛出 Exception 还是不解决决编译错误,仍然提示 "Unhandled exception: java.io.FileNotFoundException"。

因为我们可能保持着惯性思维,忽略了 Lambda 本身就是一个功能性接口方法的实现,所以把上面的代码还原为匿名类的方式

那么对于上面那种情况应该如何处理呢? 阅读全文 >>

Java 8 可重复注解的理解与应用

Java 8 之前如何重复使用注解

在 Java 8 之前我们不能在一个类型重复使用同一个注解,例如 Spring 的注解 @PropertySource 不能下面那样来引入多个属性文件

@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public class MainApp {}

上面的代码无法在 Java 7 下通过编译,错误是: Duplicate annotation

于是我们在 Java 8 之前想到了一个方案来规避 Duplicate Annotation 的错误: 即声明一个新的 Annotation 来包裹 @PropertySource, 如 @PropertySources

然后使用时两个注解齐上阵 阅读全文 >>

CompletableFuture 的并发性能研究

今天继续探讨 CompletableFuture 的特性,它并发时的性能如何呢?我们知道集合的 stream() 后的操作是序列化进行的,parallelStream()是能够并发执行的,而用 CompletableFuture 可以更灵活的控制并发。

我们先可以对比一下 parallelStream() 与 CompletableFuture 的性能差异

假设一个这样的耗时 1000 毫秒的计算任务

分别用下面两个方法来测试,任务数可以通过参数来控制 阅读全文 >>

理解 CompletableFuture 的任务与回调函数的线程

继续对 CompletableFuture 的学习,本然依然不对它的众多方法的介绍,其实也不容易通过一篇述说完所有 CompletableFuture 的操作。此处重点了解下 CompletableFuture 几类操作时所使用的线程,CompletableFuture 的方法重点在它的静态方法以及实现自 CompletionStage 接口的方法,如果是意图异步化编程,反而自我标榜的 Future 中的方法用的少了。

CompletableFuture 根据任务的主从关系为

  1. 提交任务的方法,如静态方法 supplyAsync(supplier[, executor]),  runAsync(runnable[, executor])
  2. 回调函数,即对任务执行后所作出回应的方法,多数方法了,如 thenRun(action), thenRunAsync(action[, executor]), whenComplete(action), whenCompleteAsync(action[, executor]) 等

根据执行方法可分为同步与异步方法,任务都是要被异步执行,所以提交任务的方法都是异步的。而对任务作出回应的方法很多分为两个版本,如

  1. 同步方法,如 thenRun(action), whenComplete(action)
  2. 异步方法,如 thenRunAsync(action[, executor]), whenCompleteAsync(action[, executor]), 异步方法可以传入线程池,否则用默认的

因此所要理解的 CompletableFuture 的线程会涉及到任务与回调函数所使用的线程。 阅读全文 >>

Java 8 CompletableFuture 浅入

Java 1.5 有了 Future, 可谓是跨了一大步,继而 Java 1.8 新加入一个 Future 的实现 CompletableFuture, 从此线程与线程之间可以愉快的对话了。最初两个线程间的协调我采用过 Object 的  wait() 和 notify() , Thread 的 join() 方法,那可算是很低级的 API 了,是否很多 Java 程序都不知道它们的存在,或根本没用过它们。

如果是简单的等待所有线程完成可使用 Java 1.5 的 CountDownLatch, 这里有一篇介绍 CountDownLatch 协调线程, 就是实现的 waitAll(threads) 功能。而 Java 8 的 CompletableFuture 的功能就多去,可简单使用它实现异步方法。虽说 CompletableFuture 实现了 Future 接口,但它多数方法源自于 CompletionStage, 所以还里氏代换,用 Future 来引用 CompletableFuture 实例就很牵强了; 这也是为什么 PlayFramework 自 2.5 开始直接暴露的类型是 CompletionStage 而非其他两个。

顾名思义,CompletableFuture 代表着一个 Future 完成后该干点什么,具体大致有:

  1. Future 完成后执行动作,或求取下一个 Future 的值。then...
  2. 多个 Future 的协调; 同时完成该怎么,其中一个完成该如何。allOf, anyOf

有时候可以把 Future 想像成与线程是一一对应的。 阅读全文 >>

使用 Java8 Optional 的正确姿势

我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代码就开始这么写了

Optional<User> user = ...... 
if (user.isPresent()) {
    return user.getOrders();
} else {
    return Collections.emptyList();
}

那么不得不说我们的思维仍然是在原地踏步, 只是本能的认为它不过是 User 实例的包装, 这与我们之前写成

User user = .....
if (user != null) {
    return user.getOrders();
} else {
    return Collections.emptyList();
}

实质上是没有任何分别. 这就是我们将要讲到的使用好 Java 8 Optional 类型的正确姿势.

在里约奥运之时, 新闻一再提起五星红旗有问题, 可是我怎么看都看不出来有什么问题, 后来才道是小星星膜拜中央的姿势不对. 因此我们千万也别对自己习以为常的事情觉得理所当然, 丝毫不会觉得有何不妥, 换句话说也就是当我们切换到 Java 8 的 Optional 时, 不能继承性的对待过往 null 时的那种思维, 应该掌握好新的, 正确的使用 Java 8 Optional 的正确姿势. 阅读全文 >>

Java 8 的 groupingBy 产生空的 Map 分组

前面一篇 Java 8 的 groupingBy 能否产生空的 Map 分组 是提出来的思考,本篇就是上一篇的答案。

由于在 Java 8 中用 Collectors.groupingBy 对 List 进行分组时每个组里都必须存在元素,也就是

Stream<Person> stream = Stream.of(new Person("Tom", "male"), new Person("Jerry", "male"));
System.out.println(stream.collect(Collectors.groupingBy(person -> person.gender)));

只能得到结果

{male=[Tom, Jerry]}

而无法表示存在其他 gender 的可能性,并且 female=[] 的情况,即想要结果

{male=[Tom, Jerry], female=[]}

如果想得到以上的结果该当如何呢? stream.collect() 接受一个 Collector, Collectors 中只是定义了许多常用的 Collector 实现,如果不够用的话我们可以实现自己的 Collector. 下面就来定义一个 GroupingWithKeys, 它需要实现 java.util.stream.Collector 接口,有五个接口方法. 事成之后我们写 阅读全文 >>

Java 8 的 groupingBy 能否产生空的 Map 分组

我们在 Java 8 之前用 for-loop 对 List 进行分组的时候,可能会要求产生空的分组。例如对 List<Person> 按性能进行分组,即使给定的 List<Person> 中全是 male, 我们也想得到 Map 包含两个 Key

{male=[person1, person2], female=[]}

而不只是

{male=[person1, person2]}

这两种表示略有区别,第一种方式暗示着有另一种可能性。看看 for-loop 如何对 List<Person> 进行分组。

类 Person 代码 阅读全文 >>