在Presto中有大量Java8新特性的使用,非常值得学习。这些新的特性,为‘优雅编程’提供了大量的实践方式。
1 Optional
这个其实源自Guava的optional类,使用该类可以从代码形式上有效的减少ifnull的判断,使得代码简洁不少,逻辑更加清晰。
1.1 Optional创建
1.1.1 of/ofNullable/empty
1.1.2 判空
ifPresent这个判空要慎用,如果在代码中随便该方法,决定代码逻辑,这和ifnull思维没有本质区别。
正确的用法应该是用 Optional包装对象,使用函数式编程风格进行code
if_else
User user = ...
if (user != null) {
String userName = user.getUserName();
if (userName != null) {
return userName.toUpperCase();
} else {
return null;
}
} else {
return null;
}
Optional
User user = ...
Optional<User> userOpt = Optional.ofNullable(user);
return user.map(User::getUserName)
.map(String::toUpperCase)
.orElse(null);
optional
https://lw900925.github.io/java/java8-optional.html
3 函数式编程
3.1 基本语法
3.1.1 函数接口
Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterface public interface Functional { void method(); }
https://www.ibm.com/developerworks/cn/java/j-java8idioms7/index.html
在java.util.function包下我们可以发现像 Function、Supplier、Consumer、Predicate 等接口。这些接口有一个抽象方法,会被定义的lambda表达式重写。
这四个是按照输入参数和输出参数定义的。关于输入和输出更加详细的个数可以更进一步细分这些定义。
3.1.2 方法引用
其实像filter参数其实是Predicate<? super T> predicate(其他类似),简单理解就是lamba表达式。
双冒号简写有两种情况:
1 perpon.getAge() 没有参数; 则stream perpon的集合的时候 k->k.getAge() 等效于 perpon::getAge
2 Util.process(int a) 有参数静态方法; 则stream 某个集合的时候 k->Util.process(k) 等效于 Util::process
这两种场景都是OK的
filter 后面必须是Boolean
3.1.3 stream
https://leongfeng.github.io/2016/11/18/java8-function-program-learning/
map filter reduce count distinct
这些算子和RDD中算子的用法差不多。
1 filter也是提供一个判断的表达式,这个表达式为true的元素保留,反之,丢弃
2 所有的操作只能延迟计算都是延迟的,惰性计算,遇到action算子的时候才会进行真正的计算。
4 异步并发编程
jdk1.5是通过Future.get或者轮询来获取异步任务的结果,但是不管是那种方式,都是阻塞的。jdk1.8吸收了Google GuavaFuture的ListenableFuture和SettableFuture的特征,还提供了其它强大的功能,让Java拥有了完整的非阻塞编程模型:Future、Promise 和 Callback(在Java8之前,只有无Callback 的Future)。
CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。
CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。
@RequestMapping("/test")
public void test(){
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务线程:" + Thread.currentThread().getId());
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "i am async method";
}).whenComplete((result,except)->{
System.out.println("异步执行完成,执行后续操作" + result);
});
// .thenRun(()-> {
// System.out.println(Thread.currentThread().getId() + "异步完成 开始通知");
// }
//
// );
System.out.println("CompletableFuture" + Thread.currentThread().getId());
System.out.println("###########################");
}
}
Note:
-
CompletableFuture的get方法依然是阻塞的,有些场景我们依然是需要阻塞的,比如顶级调用的返回,get方法依然有着其使用场景
-
异步通知是依靠whenComplete或者thenRun来完成的。
-
thenRun之类的这种方式既可以在执行异步任务的那个线程中执行回调任务,也可以再新开一个线程来完成回调任务。
问题记录:
1 idea提示:Statement lambda can be replaced with expression lambda。
经过Google之后发现这是,https://stackoverflow.com/questions/46238702/statement-lambda-can-be-replaced-with-expression-lambda
CompletableFuture<DataQueryResponse> future = CompletableFuture.supplyAsync(() ->
query.start()
,queryPool);
2 exceptionlly 问题
这个是在当前线程执行的吗
参考资料:
https://www.jianshu.com/p/dff9063e1ab6
https://www.jianshu.com/p/d81cf0beb858
https://www.jianshu.com/p/807e6822292a
http://www.importnew.com/10815.html
http://www.importnew.com/16149.html
9 Copy-On-Write
9.1 思想
Copy-On-Write 这个其实不是8的特性,这个在1.5中添加的。利用了读写分离的思想,保证在读的时候不用加锁,写的时候分离出新的容器(这个过程是要加锁的,不然会有多个分离操作,导致copy出N个副本)。提高了操作效率。
9.2 实现
真正的实现类是CopyOnWriteArrayList和CopyOnWriteArraySet
这种读写分离的一个很明显的体现就是以前在开发的过程中在遍历 collections的时候,如果往collections添加元素会报错
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.zdp.thread.CollectionModifyExceptionTest.main(CollectionModifyExceptionTest.java:17)
但是在COW的机制下,读的是原始的那个collection,写的是copy出来的那个collection,所以这样操作是完全OK的。
9.3 缺点
这个又两个问题,一个是内存占用问题,另一个是数据一致性问题。
参考资料:
https://segmentfault.com/a/1190000014479792
http://ifeve.com/java-copy-on-write/