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 代码 阅读全文 >>

Java 8 返回集合中第一个匹配的元素

在 Java 8 之前如果我们要找到集合中第一个匹配元素,要使用外部循环,如下面方法 findFirstMatch() 如果找到一个大于 3 的数字立即返回它,否则返回 null

  public Integer findFirstMatch() {
    List<Integer> integers = Arrays.asList(1, 4, 2, 5, 6, 3);
    for(int i: integers) {
      if(i > 3) return i;
    }
    return null;
  }

因为在 for 循环中找到第一个大于 3 的数字是 4, 并且立即返回,所以不管集合 integers 再大,也不会遍历整个集合。

注:不要纠结于上面示例方法的实际用途,实际上集体和匹配条件都该通过参数传入方法的,这里只作演示循环。

那么我们来到 Java 8 之后用 Stream API 该如何实现,翻遍了 Stream API, 能过滤元素的操作也就是 filter 方法,于是尝试这样的写法 阅读全文 >>

Java 8 Lambda 捕获外部变量

可能会把捕获外部变量的 Lambda 表达式称为闭包,那么 Java 8 的 Lambda 可以捕获什么变量呢?

  1. 捕获实例或静态变量是没有限制的(可认为是通过 final 类型的局部变量 this 来引用前两者)
  2. 捕获的局部变量必须显式的声明为 final 或实际效果的的 final 类型

回顾一下我们在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为  final,例如下面的代码放在 Java 7 中是编译不过的

java7-annymous-capture-local-variable

Java 7 要求 version 这个局部变量必须是 final  类型的,否则在匿名类中不可引用。 阅读全文 >>

Java 8 Stream 如何还原为集合

由于 Java 对集合的函数式操作并非原生态的,必须对得到的 stream() 进行过滤等操作,之后还是一个 stream(),一般我们最后返回给调用者需还原为相应的集合。这无法与 Scala 的 for ... yield 操作相比。例如下面在使用 Stream API 过滤获得所有大于 3 的数字之后,方法的返回值还应该还原为 List<Integer>, 这个需求非常自然

我们这儿的问题就是如何把上面的 streamOfInteger 转换为 List<Integer>, 有以下几种办法 阅读全文 >>

Java 8 Lambda 写法与简化

Java 8 的 Lambda 表达式的实现方式还是基于已有的字节码指令,由 Lambda 表达式的方法签名结合上下文并经由 SAM 推断出正确的类型来。Java 8 的 Lambda 完整书写格式是

(type parameter1 [type parameter2, ...type parametern]) -> { statements here }

这种标准格式所表示的就是方法签名。

虽不及其他语言的 Lambda 表达式,像 Swift, Scala 可省略参数部分,可用默认参数名 $0, $1, 或 _, 但 Java 8 的 Lambda 还是可以进行酌情简化

  1. 参数类型总是可省略   --     (x, y) -> { x + y; }
  2. 参数为一个时,参数括号可省略  --    x -> { System.out.println(x); }
  3. 语句为一条时,可省略大括号, 并且语句后不要分号 --  x -> System.out.println(x)
  4. 上面更进一步,如果是单条 return 语句,必须把 return 关键字去掉  --  x -> "Hello " + x
  5. 就差一点,参数部分总是不能省,无参必须写成 () -> System.out.println("hi")
  6. Java 8 中若要近似的实现无参数部分写法,那就是方法引用了 -- System.out::println

阅读全文 >>

Java 8 的泛型增强

Java 5 引入了泛型,这是一次重大的改进,从此集合中的东西不需要每次显式的去转型。不过 Java 5 还不具备类型推断的能力,所以声明泛型必须写成

List<String> list = new ArrayList<String>();

一直到 Java 6 也是如此。自 Java 7 起泛型增强为可根据声明类型进行推断,所以 Java 7 中可以这么写

List<String> list = new ArrayList<>();  //<> 中的参数可省略,如果类型参数多, 或多层嵌套时很省事

List<String> list = Collections.emptyList(); //见  Java 泛型 -- 依据声明的变量类型自动推断

Java 8 开始对泛型类型推断又进一步增强:可根据方法上下文进行推断,例如下面的代码在 Java 7 下编译不过

阅读全文 >>