背景

NPE问题,100%的Java程序员都碰到,并且曾经是心中的痛。

1965年英国TonyHoare引入了Null引用,后续的设计语言包括Java都保持了这种设计。

一个例子

业务模型

Person 有车一族, 有Car字段,

Car 车,每个车都有购买保险, 有Insurance字段;

Insurance 保险,每个保险都有名字 有name字段;

需求:获取某个Person对象的购买保险的名称;


常规编程

    public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}

检查式编程

 public String getCarInsuranceName_check(Person person) {
if (Objects.nonNull(person)) {
final Car car = person.getCar();
if (Objects.nonNull(car)) {
final Insurance insurance = car.getInsurance();
if (Objects.nonNull(insurance)) {
return insurance.getName();
}
}
}
return "unkown";
}

防御式编程

public String getCarInsuranceName_protect(Person person) {
if (Objects.isNull(person)) {
return "unkown";
}
final Car car = person.getCar();
if (Objects.isNull(car)) {
return "unkown";
}
final Insurance insurance = car.getInsurance();
if (Objects.isNull(insurance)) {
return "unkown";
}
return insurance.getName();
}

对比一下缺点:

编程方法 缺点
常规编程 NPE问题
检查式编程 1.可读性不好,多层if嵌套; 2.扩展性不好,需要熟悉全流程,否则不知道应该在哪个if中扩展,极易出错;
防御式编程 1. 维护困难,4个不同的退出点,极易出错,容易遗漏检查项目

NPE的痛点

  1. java程序中出现最多的Exception;没有之一;
  2. 使得代码量膨胀混乱,对象的空判断充斥在代码中,但是却没有实际的业务意义;
  3. 类型系统的一个后门,实际上不属于任何类型,也可以说是任何类型;
  4. 本身无意义,标识对缺失值的建模,也破坏了java中弱化指针的理念。

java8中对缺失值的建模对象是Optional,可以基于它解决NPE的痛点,设计更好的API

Optional

领域模型的建模进化

  1. Person , 含有一个Optional car字段,一个人可能有车,也可能没有车;
  2. Car, 包含有一个Optional insurance字段, 一台车可能买了保险,也可能没有买保险;
  3. Insurance , 保险公司必定有名字所有,他有一个字段 name;

构造方法

构造方法 说明 备注
Optional.empty() 一定是空的对象 跟null有区别,是一个单例对象
Optional.of(T t) 一定是不空的对象 如果给了null值会立刻抛出NPE
Optioanl.ofNullable(T t) 允许为空的对象放在里面 使用值之前需要做检查

map方法-对象中提取和转换值

可以把Optional看成一种单元素的Stream, Map,即把其中的元素按照一定规则转换为其它类型或者进行其它运算后的值,如果没有元素,则啥也不做。

下面的代码是等同的。

public class Test {
public static final String UNKNOWN = "unknown";
/**
* 传统方法
* @param insurance
* @return
*/
public static String getInsuranceName(Insurance insurance){
if (Objects.isNull(insurance)){
return UNKNOWN;
}
return insurance.getName();
}
/**
* map的方式提取
* @param insurance
* @return
*/
public static String getInsuranceNameOp(Insurance insurance){
return Optional.ofNullable(insurance).map(Insurance::getName).orElse(UNKNOWN);
}
}

flatMap方法 - 转换为Optional对象输出;

类似于Stream的flatMap方法,把元素切割或者组合成另外一个流输出。

    public static String getInsuranceName(Person person) {
return Optional.ofNullable(person)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName).orElse(UNKNOWN);
}

默认值设置方法(5种适合不同的场景)

默认值方法 说明 场景
or(Supplier) 为空则延迟构造一个Optional对象 可以采用延迟的方式,对接某些代码来产生默认值
orElse(T t) 为空则采用默认值 直接,简单
orElseGet(Supplier sp) 为空则通过函数返回 延迟返回,可以对接某些代码逻辑
orElseThrow() 为空则跑出异常 默认是NoSuchElementException
orElseThrow(Supplier sp) 为空则跑出自定义异常 异常类型可以自定义

使用值(获取或者消费)

主要分两种场景,直接获取值, 采用get()方法;

里面有值,则消费, ifPresent(Consumer c)

消费或者获取方法 说明 场景
get() 获取Optional中的值,如果没有值,会抛出异常 确认里面有值才会调用该防范
ifPresent(Consumer c) 有值则执行自定义代码段,消费该值 流式编程,有值继续处理逻辑
ifPresentOrElse(Consumer c , Runnable r) 如果有值,则消费,没有值,进行另外的处理 有值或者没有值都进行处理java9才有

多个Optional进行运算

通过使用flatMap,map可以做到,方法里执行的已经做好了对empty的情况进行处理。

实例如下:



    public static String getCheapestPrizeIsuranceNameOp(Person person, Car car) {
return Optional.ofNullable(person)
.flatMap(p -> Optional.ofNullable(car).map(c -> getCheapest(p, c)))
.orElse(UNKNOWN);
} public static String getCheapestPrizeIsuranceName(Person person, Car car) {
if (Objects.nonNull(person) && Objects.nonNull(car)) {
return getCheapest(person, car);
}
return UNKNOWN;
} /**
* 模拟得到最便宜的保险
*
* @param person 人
* @param car 车
* @return 最便宜的车险名称
*/
private static String getCheapest(Person person, Car car) {
return "pinan";
}

filter方法 (过滤)

因为Optional中只有一个值,所以这里的filter实际上是判断单个值是不是。

对比代码:

    public static Insurance getPinanInsurance(Person person){
Optional<Insurance> insuranceOptional = Optional.ofNullable(person).map(Person::getCar).map(Car::getInsurance);
if (insuranceOptional.isPresent() && Objects.equals("pinan", insuranceOptional.get().getName())){
return insuranceOptional.get();
}
return null;
} public static Insurance getPinanInsurance_filter(Person person){
return Optional.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.filter(item->Objects.equals(item.getName(),"pinan" ))
.orElse(null);
}

empty方法 (构造一个空的Optional对象)


Optional改造历史代码

封装可能潜在为null的对象


public Object getFromMap(String key){ Map<String,Object> map = new HashMap<>(4);
map.put("a", "aaa");
map.put("b", "bbb");
map.put("c", "ccc"); Object value = map.get(key);
if (Objects.isNull(value)){
throw new NoSuchElementException("不存在key");
}
return value; } public Object getFromMapOp(String key){ Map<String,Object> map = new HashMap<>(4);
map.put("a", "aaa");
map.put("b", "bbb");
map.put("c", "ccc"); Object value = map.get(key); return Optional.ofNullable(value).orElseThrow(()->new NoSuchElementException("不存在key"));
}

发生异常的建模可以替换为Optional对象

这种是建模思想的转变,不一定适用每个人;

 /**
* 如果字符串不是数字,会抛出异常
* @param a 字符串
* @return 数字
*/
public Integer string2Int(String a){
return Integer.parseInt(a);
} /**
* Optional.empty对应异常的情况,后续比较好处理;
* @param a 字符串
* @return 可能转换失败的整数,延迟到使用方去处理
*/
public Optional<Integer> string2Int_op(String a){
try{
return Optional.of(Integer.parseInt(a));
}catch (Exception ex){
return Optional.empty();
}
}

尽量不使用封装的Optional

封装的OptionalInt, OptionalLong ,因为Optional里面只有一个元素,使用封装类没有性能优势,而且缺失了重要的flatMap, map,filter方法;

总的来说,Optional的使用,简化了代码,使得代码可读性和可维护性更好。

最后来个例子:


public Integer getFromProperties(Properties properties, String key) {
String value = properties.getProperty(key);
if (Objects.nonNull(value)) {
try {
Integer integer = Integer.parseInt(value);
if (integer > 0) {
return integer;
}
} catch (Exception ex) {
//无需处理异常
return 0;
}
}
return 0;
} public Integer getFromProperties_op(Properties properties, String key) {
return Optional.ofNullable(properties.getProperty(key))
.map(item -> {
try {
return Integer.parseInt(item);
} catch (Exception ex) {
return 0;
}
})
.orElse(0);
}

Optional源码阅读

一个容器对象,可能有也可能没有非空值,如果值存在,isPresent()返回true,如果没有值,则对象被当成空,isPresent()返回false;
更多的方法依赖于容器中是否含有值,比如orElse(返回一个默认值当没有值)
ifPresent(Consumer c) 是当值存在的时候,执行一个动作; 这是一个基于值的类,使用标识敏感的操作,包含 比较引用的 == , hashcode , synchronization 针对一个Optional对象,可能有无法预料的结果,然后应该避免这类操作。 编写API的注意点:
Optional最初被用来设计为方法的返回值,当明确需要代表没有值的情况。
返回null,可能出错;而返回Optional对象不是一个null对象,它总是指向一个Optional对象实例。
/**
* A container object which may or may not contain a non-{@code null} value.
* If a value is present, {@code isPresent()} returns {@code true}. If no
* value is present, the object is considered <i>empty</i> and
* {@code isPresent()} returns {@code false}.
*
* <p>Additional methods that depend on the presence or absence of a contained
* value are provided, such as {@link #orElse(Object) orElse()}
* (returns a default value if no value is present) and
* {@link #ifPresent(Consumer) ifPresent()} (performs an
* action if a value is present).
*
* <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
* class; use of identity-sensitive operations (including reference equality
* ({@code ==}), identity hash code, or synchronization) on instances of
* {@code Optional} may have unpredictable results and should be avoided.
*
* @apiNote
* {@code Optional} is primarily intended for use as a method return type where
* there is a clear need to represent "no result," and where using {@code null}
* is likely to cause errors. A variable whose type is {@code Optional} should
* never itself be {@code null}; it should always point to an {@code Optional}
* instance.
*
* @param <T> the type of value
* @since 1.8
*/

其它的代码比较简单,模型就是里面含有一个T类型的值,empty()是一个特殊的Optional对象,里面的值是null;

public final class Optional<T> {
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>(); /**
* If non-null, the value; if null, indicates no value is present
*/
private final T value; /**
* Constructs an empty instance.
*
* @implNote Generally only one empty instance, {@link Optional#EMPTY},
* should exist per VM.
*/
private Optional() {
this.value = null;
}

小结

  1. Optional表示一个可能缺失的对象,API可以依据这个进行建模,但是要注意序列化的问题;可以避免空指针的问题,并且提升代码的可读性和可维护性。
  2. Optional的构造方法有3个,of,ofNullable,empty;
  3. map,flatmap , filter 可以快速的转换和过滤值;
  4. 值缺失的处理方法有3个,orElse, orElseGet, orElseThrow;

原创不易,转载请注明出处。

java8-Optional的引入的更多相关文章

  1. Java8 Optional的简单操作

    我们经常会遇到这种情况:首先判断一个对象是否为null,如果不为null,获取一个对象中的一个属性,如果该属性不为null,又获取该属性的属性,如果该属性的属性不为null,又获取属性的属性的属性: ...

  2. java8 Optional优雅非空判断

    java8 Optional优雅非空判断 import java.util.ArrayList;import java.util.List;import java.util.Optional; pub ...

  3. Java8 Optional && Guava Optional

    Java8 -- Optional boolean isPresent():与obj != null()一样:调用get()前要调用isPresent()检查,不然会报错 Optional的三种构造方 ...

  4. java代码之美(16) ---Java8 Optional

    Java8 Optional 一句话介绍Optional类:使用JDK8的Optional类来防止NullPointerException(空指针异常)问题. 一.前言 在我们开放过程中,碰到的异常中 ...

  5. 【Java8新特性】你知道Java8为什么要引入Lambda表达式吗?

    写在前面 这是一道真实的面试题,一个读者朋友出去面试,面试官竟然问他这样一个问题:你说说Java8中为什么引入Lambda表达式?引入Lambda表达式后有哪些好处呢?还好这个朋友对Java8早有准备 ...

  6. Java8 Optional类

    概述 到目前为止,著名的NullPointerException是导致Java应用程序失败的最常见原因.过去,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guav ...

  7. java8 Optional使用总结

    [前言] java8新特性 java8 函数接口 java8 lambda表达式 Java 8 时间日期使用 java8 推出的Optional的目的就是为了杜绝空指针异常,帮助开发者开发出更优雅的代 ...

  8. 聊一聊Java8 Optional,让你的代码更加优雅

    码农在囧途 随着时间的推移,曾经我们觉得重要的东西,可能在今天看来是如此的浅薄和无知,同理,今天我们放不下,想不开,觉得重要的东西,多年后我们可能也会觉得也就那样,所以,今天的的所有烦恼,忧愁,想不开 ...

  9. 使用 Java8 Optional 的正确姿势(转)

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

  10. java8 Optional正确使用姿势

    Java 8 如何正确使用 Optional import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; ...

随机推荐

  1. windows虚拟机中DNS服务配置

    在linux虚拟机中进行DNS服务配置并进行正向解析反向解析我博客中已经写过,下面 我来介绍一下在windows虚拟机中DNS服务的配置使用. 1.打开一台windows虚拟机中服务器管理器——角色— ...

  2. Python与自然语言处理搭建环境

    参考书籍<Python自然语言处理>,书籍中的版本是Python2和NLTK2,我使用的版本是Python3和NLTK3 实验环境Windows8.1,已有Python3.4,并安装了Nu ...

  3. Android Native Binder,在Native层与App交互数据

    Binder底层是基于C实现的,因此可以作为Native进程与App层交互数据的渠道.其应用场景为:Native Service.Hal驱动设置.应用层JNI服务等. Android 4.4引入SEA ...

  4. RocketMQ 主题扩分片后遇到的坑

    目录 1.案情回顾 1.1 集群现状 1.2.RocketMQ 在线扩容队列 1.3 消息发送 2.问题暴露 3.问题分析 4.问题复盘 消息组接到某项目组反馈,topic 在扩容后出现部分队列无法被 ...

  5. Statistics : Data Distribution

    1.Normal distribution In probability theory, the normal (or Gaussian or Gauss or Laplace–Gauss) dist ...

  6. webpack(四) --css样式及图片打包

    一.CSS样式打包 1. loader简介 由于Webpack打包入口目前只配置了一个index.js文件,那么其他需要被打包的文件都必须通过模块化方式引入该文件才行,而默认情况下,引入的文件必须是j ...

  7. Python之Flask项目开发【入门必学】

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:藤藤菜丶 Flask 安装Flask模块 创建一个Flask项目 运行 ...

  8. python输出日志到文件(每天一个日志)

    import logging from logging.handlers import TimedRotatingFileHandler logger = logging.getLogger('sim ...

  9. 【eclipse】Editor does not contain a main type

    问题现象: eclipse运行java程序的时候弹出对话框:Editor does not contain a main type. 解决方法: 右击 src路径 → Build Path → Use ...

  10. redis(7)--redis应用实战

    问题1:哨兵模式下客户端应该连接哪个redis-server? 问题2:集群模式下为什么会有MOVED error Redis Java客户端介绍 已有的客户端支持 Redis Java客户端有很多的 ...