This commit is contained in:
2023-03-05 17:53:40 +08:00
parent 9a09b5443a
commit 42d587f331
3 changed files with 282 additions and 15 deletions

View File

@ -0,0 +1,118 @@
已剪辑自: https://blog.kaaass.net/archives/764
Optional 是 Java8 提供的为了解决 null 安全问题的一个 API。善用 Optional 可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。这篇文章是建立在你对 Optional 的用法有一定了解的基础上的,如果你还不太了解 Optional可以先去看看相关教程或者查阅 [Java 文档](http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)。
使用 Optional我们就可以把下面这样的代码进行改写。
```java
public static String getName(User u) {
if (u == null || u.name == null)
return "Unknown";
return u.name;
}
```
不过,千万不要改写成这副样子。
```Java
ublic static String getName(User u) {
Optional<User> user = Optional.ofNullable(u);
if (!user.isPresent())
return "Unknown";
return user.get().name;
}
```
这样改写非但不简洁,而且其操作还是和第一段代码一样。无非就是用 isPresent 方法来替代 u==null。这样的改写并不是 Optional 正确的用法,我们再来改写一次。
```java
public static String getName(User u) {
return Optional.ofNullable(u)
.map(user->user.name)
.orElse("Unknown");
}
```
这样才是正确使用 Optional 的姿势。那么按照这种思路,我们可以安心的进行链式调用,而不是一层层判断了。看一段代码:
```java
public static String getChampionName(Competition comp) throws IllegalArgumentException {
if (comp != null) {
CompResult result = comp.getResult();
if (result != null) {
User champion = result.getChampion();
if (champion != null) {
return champion.getName();
}
}
}
throw new IllegalArgumentException("The value of param comp isn't available.");
}
```
由于种种原因(比如:比赛还没有产生冠军、方法的非正常调用、某个方法的实现里埋藏的大礼包等等),我们并不能开心的一路 comp.getResult().getChampion().getName() 到底。而其他语言比如 kotlin就提供了在语法层面的操作符加持comp?.getResult()?.getChampion()?.getName()。所以讲道理在 Java 里我们怎么办!
让我们看看经过 Optional 加持过后,这些代码会变成什么样子。
```java
public static String getChampionName(Competition comp) throws IllegalArgumentException {
return Optional.ofNullable(comp)
.map(Competition::getResult) // 相当于c -> c.getResult(),下同
.map(CompResult::getChampion)
.map(User::getName)
.orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}
```
这就很舒服了。Optional 给了我们一个真正优雅的 Java 风格的方法来解决 null 安全问题。虽然没有直接提供一个操作符写起来短,但是代码看起来依然很爽很舒服。更何况 ?. 这样的语法好不好看还见仁见智呢。
还有很多不错的使用姿势,比如字符串为空则不打印可以这么写:
```java
string.ifPresent(System.out::println);
//Optional 的魅力还不止于此Optional 还有一些神奇的用法,比如 Optional 可以用来检验参数的合法性。
public void setName(String name) throws IllegalArgumentException {
this.name = Optional.ofNullable(name)
.filter(User::isNameValid)
.orElseThrow(()->new IllegalArgumentException("Invalid username."));
}
```
这样写参数合法性检测,应该足够优雅了吧。
2019-10-13 补充 Optional 的本质,提出若干应用建议。
不过这还没完,上面的两个例子其实还不能完全反应出 Optional 的设计意图。事实上,我们应该更进一步,减少 Optional.ofNullable 的使用。为什么呢?因为 Optional 是被设计成用来代替 null 以表示不确定性的,换句话说,只要一段代码可能产生 null那它就可以返回 Optional。而我们选择用 Optional 代替 null 的原因,是 Optional 提供了一个把**若干依赖前一步结果**的处理结合在一起的途径。举个例子,在我们调用一个网站的登录接口的时候,大概会有以下的步骤:
1. 发送 HTTP 请求,得到返回。
2. (依赖:接口的返回)解析返回,如将 Json 文本形式的返回结果转化为对象形式。
3. (依赖:解析的结果)判断结果是否成功。
4. (依赖:若成功调用的结果)取得鉴权令牌。
5. (依赖:获得的令牌)进行处理。
其中,第 2-5 步的每一个步骤都依赖于前一个步骤,而前一个步骤传递过来的数据都具不确定性(有可能是 null。所以我们可以把它们接受的数据都设计成 Optional。第 1-4 步每一个步骤的结果也具备不确定性,所以我们也把它们的结果设计成 Optional。最后到了第 5 步,我们终于要对一切的结果进行处理了:如果成功获得令牌就存储,失败就提示用户。所以这一步,我们采用如 orElse 之类的方法来**消除**不确定性。于是我们最后的设计就可以是:
1. 结果 String可能是 null **==** **包装** **==>** Optional<String>
2. Optional<String> **==** **解析** **==>** Optional<Json对象>
3. Optional<Json对象> **== Filter** **判断成功** **==>** Optional<Json对象>
4. Optional<Json对象> **==** **取鉴权令牌** **==>** Optional<AuthToken>
5. 对 Optional<AuthToken> 进行处理,消除 Optional
Optional 就像一个处理不确定性的管道,我们在一头丢进一个可能是 null 的东西(接口返回结果),经过层层处理,最后消除不确定性。**Optional 在过程中保留了不确定性,从而把对 null 的处理移到了若干次操作的最后,以减少出现 NPE 错误的可能。**于是Optional 应用的建议也呼之欲出了:
1. 适用于层级处理(依赖上一步操作)的场合。
2. 产生对象的方法若可能返回 null可以用 Optional 包装。
3. 尽可能延后处理 null 的时机,在过程中使用 Optional 保留不确定性。
4. 尽量避免使用 Optional 作为字段类型。
最后说句题外话,这种依赖上一步的操作也叫 Continuation。而 Optional 的这种接受并组合多个 Continuation 的设计风格就是 Continuation-passing styleCPS
# 参考资料
使用 Java8 Optional 的正确姿势 隔叶黄莺 Unmi Blog (https://unmi.cc/proper-ways-of-using-java8-optional/)

View File

@ -131,7 +131,7 @@ public class MDCRunnable implements Runnable {
接着我们需要对main函数里创建的Runnable实现类进行装饰
```
```java
public class Main {
private static final String KEY = "requestId";
@ -140,13 +140,13 @@ public class Main {
public static void main(String[] args) {
*// 入口传入请求ID*
*MDC.put(KEY, UUID.randomUUID().toString());*
*
// 主线程打印日志*
*logger.debug(*"log in main thread");
// 入口传入请求ID
MDC.put(KEY, UUID.randomUUID().toString());
*// 异步线程打印日志用MDCRunnable装饰Runnable*
// 线程打印日志
logger.debug(*"log in main thread");
// 异步线程打印日志用MDCRunnable装饰Runnable
new Thread(new MDCRunnable(new Runnable() {
@Override
public void run() {
@ -154,8 +154,8 @@ public class Main {
}
})).start();
*// 异步线程池打印日志用MDCRunnable装饰Runnable*
*EXECUTOR.execute(*new MDCRunnable(new Runnable() {
// 异步线程池打印日志用MDCRunnable装饰Runnable
EXECUTOR.execute(*new MDCRunnable(new Runnable() {
@Override
public void run() {
logger.debug("log in other thread pool");
@ -163,12 +163,12 @@ public class Main {
}));
EXECUTOR.shutdown();
*// 出口移除请求ID*
*MDC.remove(KEY);*
*
}*
*
}*
// 出口移除请求ID
MDC.remove(KEY);
}
}
```