1、使用object==null的例子

2、null带来的问题

3、其他语言中null的处理(替代)

4、Java8的Optional类

  4.1 这样做有什么好处呢?

  4.2 引入Optional类的目的

  4.3 null与Optional.empty()

  4.4 使用Optional

  4.5 使用Optional域,该域无法序列化

  4.6 应用

参考文献

1、使用object==null的例子

例1

pulbic String getCarInsuranceName(Person person){
if(person != null){
Car car = person.getCar();
if(car != null){
Insurance insurance = car.getInsurance();
if(insurance != null){
return insurance.getName();
}
}
}
return "UNKNOWN";
}

可以发现这样写比较繁琐,每当某个变量可能为null时,都要添加if块来判断,增加了代码的缩进级别,扩展性和可读性都很差,且万一忘记判断某个变量,依然出现NPE。

例2

public String getCarInsuranceName(Person person){
if(person == null){
return "unknown";
}
Car car = person.getCar();
if(car == null){
return "unknown";
}
Insurance insurance = car.getInsurance();
if(insurance == null){
return "unknown";
}
return insurance.getName();
}

这种方式避免深层嵌套的if块,但是每一个null检查点都增加一个退出点,难以维护,且为null时,返回的字符串“unknown”重复出现。同样的,当忘记检查某个可能为null的属性时,会出现NPE。

2、null带来的问题

NPE是目前Java程序开发种最典型的异常;

会使代码膨胀,深度嵌套的null检查,代码的可读性差;

null自身是毫无意义的,null没有任何语义;

破坏了Java的哲学,Java一直试图避免让程序员意识到指针,唯一的例外就是null指针;

null在Java的类型系统上成了例外,null不属于任何类型,即它可以赋值给任意引用类型,当这个变量被传递到系统的另一个部分后,将无法获知这个null变量最初的赋值到底什么类型。

3、其他语言中null的处理(替代)

Groovy:安全导航操作符(safe navigation operator, 标记为?)

e.g. def carInsuranceName = person?.car?.insurance?.name 当调用链中的变量遇到null时将null引用沿着调用链传递下去,返回一个null。

函数式语言:

Haskell:Maybe类型,其本质上是对optional值的封装。Maybe类型的变量可以是指定类型的值,也可以什么都不是。没有null引用的概念。

Scala:Option[T],既可以包含类型为T的变量,也可以不包含该变量,显式调用Option类型的available操作,检查该变量是否有值,即变相的“null”检查。

4、Java8的Optional类

Java8引入Optional类,在从null到Optional的迁移中,需要反思的是:如何在你的域模型中使用Optional值。

我们根据上面例子的类关系可知,Person的Car变量存在null情况,因此不能直接声明为Car,改为Optional<Car>。

4.1 这样做有什么好处呢?

使用Optional<Car>声明变量,清楚的表明了这里的变量缺失是允许的,而使用Car类型,可能将变量赋值为null,就只能依赖业务模型的理解,判断一个null是否属于该变量的有效范畴。

我们根据Optioanl类重新定义Person/Car/Insurance的数据模型:

public class Person{
private Optional<Car> car;
public Optional<Car> getCar() {return car;}
}
public class Car{
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance(){return insurance;}
}
public class Insurance{
private String name; // 保险公司必须有名字
public String getName(){return name;}
}

当获取insurance公司名称发生NPE,就能非常确定出错的原因,不需要为其添加null的检查,因为null的检查只会掩盖问题,并未真正地修复问题。insurance公司必须有名字,当公司没有名称,就需要查看出了什么问题,而不应该再添加一段代码,将这个问题隐藏。

4.2 引入Optional类的目的

代码中始终如一使用Optional,能非常清晰地节点出变量值的缺失是结构上的问题还是算法上或是数据中的问题。

引入Optional并不是要消除每一个null引用,而是帮助设计更普适的API,看见签名就能了解是否接受一个Optional的值,这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值。

即看见Optional<Car>就可以知道该变量car可能为null。

4.3 null与Optional.empty()

尝试解引用一个null,一定会触发NPE,而使用Optional.empty()就没事,其为Optioanl类的一个有效对象,多种场景都能调用,非常有用。

Optional.empty()的定义如下

/**
* Returns an empty {@code Optional} instance. No value is present for this
* Optional.
*
* @apiNote Though it may be tempting to do so, avoid testing if an object
* is empty by comparing with {@code ==} against instances returned by
* {@code Option.empty()}. There is no guarantee that it is a singleton.
* Instead, use {@link #isPresent()}.
*
* @param <T> Type of the non-existent value
* @return an empty {@code Optional}
*/
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

EMPTY的定义如下:

/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();

4.4 使用Optional

(1) 使用map从Optional对象中提取和转换值

// 原先写法
String name = null;
if (insurance != null){
name = insurance.getName();
} // 使用Optional
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

stream和map方法:

(2)使用flatMap方法连接Optional对象

// 原先写法
public String getCarInsuranceName(Person person){
return person.getCar().getInsurance().getName();
} // 使用Optional.map会报错
/*因为Person的类型是Optional<Computer>,调用map方法是正常的,
但是,getCar()方法返回的是一个Optional<Car>对象,
即这个map操作后得到的类型是Optional<Optional<Car>>.
对一个Optional对象调用getInsurance ()是非法的。*/
public String getCarInsuranceName(Optional<Person> person){
return person.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("UNKNOWN");
} // 使用Optional.flatMap
public String getCarInsuranceName(Optional<Person> person){
return person.flatMap (Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("UNKNOWN");
}

stream和flatMap方法比较:

flatMap方法连接Optional对象过程:

(3)map和flatMap方法源码

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value)); // Optional.ofNullable()会返回一个Optional<>
}
} //如果调用的mapper返回已经是Optional,则调用该mapper后,flatMap不会再添加Optional
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value)); //返回参数的类型
}
}

Objects类的requireNonNull方法:

public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}

4.5 使用Optional域,该域无法序列化

在域模型中使用Optional,该域无法序列化,因为Optional没有实现Serializable接口。

Java语言的架构师Brain Goetz明确陈述过:Optional的设计初衷仅仅是要支持能返回Optional对象的语法。

Optional类设计时就没有特别考虑将其作为类的字段使用,所以它并未实现Serializable接口。

若一定要序列化域,替代方案:

public class Person{
private Car car;
public Optional<Car> getCarOptional(){
return Optional.ofNullable(car);
}
}

4.6 应用

(1)用Optional封装可能为null的值

Object value = map.get("key");
↓↓
Optional<Object> value = Optional.ofNullable(map.get("key"));

(2)异常

Integer.parseInt(String) --> NumberFormatException
↓↓
public static Optional<Integer> stringToInt(String s){
try{
return Optional.of(Integer.parseInt(s));
} catch(NumberFormatException e){
return Optional.empty();
}
}

(3)基础类型的Optonal对象

Optional也提供了基础类型--OptionalInt, OptionalLong, OptionalDouble,但是不推荐使用,因为基础类型的Optional不支持map、flatMap以及filter方法。

(4)整合

Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "true");
props.setProperty("c", "-3");

需求:从这些属性中读取一个值,该值的含义是时间,单位s,所以必须≥0.

public int readDuration(Properties props, String name){
String value = props.getProperty(name);
if (value != null){
try{
int i = Integer.parseInt(value);
if (i > 0){
return i;
}
}catch(NumberFormatException nfe){}
}
return 0;
} ↓↓ public int readDuration(Properties props, String name){
return Optional.ifNullable(props.getProperty(name))
.flatMap(OptionalUtil::stringToInt) // ②中的方法
.filter(i -> i > 0)
.orElse(0);
}

参考文献:《Java8实战》(英)Urma, R.G. (意)Fusco, M. (英)Mycroft, A,著;陆明刚 劳佳译. 北京:人民邮电出版社,2016.4

官网:https://www.oracle.com/technical-resources/articles/java/java8-optional.html

Optional类与使用==判断null有什么区别?使用Optional类有什么优势?的更多相关文章

  1. java中List接口的实现类 ArrayList,LinkedList,Vector 的区别 list实现类源码分析

    java面试中经常被问到list常用的类以及内部实现机制,平时开发也经常用到list集合类,因此做一个源码级别的分析和比较之间的差异. 首先看一下List接口的的继承关系: list接口继承Colle ...

  2. CSS3伪类和伪元素的特性和区别尤其是 ::after和::before

    伪类和伪元素的理解 官方解释: 伪类一开始单单只是用来表示一些元素的动态状态,典型的就是链接的各个状态(LVHA).随后CSS2标准扩展了其概念范围,使其成为了所有逻辑上存在但在文档树中却无须标识的“ ...

  3. JAVA8新特性Optional,非空判断

    Optional java 的 NPE(Null Pointer Exception)所谓的空指针异常搞的头昏脑涨, 有大佬说过 "防止 NPE,是程序员的基本修养." 但是修养归 ...

  4. Atitit. null错误的设计 使用Optional来处理null

    Atitit. null错误的设计 使用Optional来处理null 然后,我们再看看null还会引入什么问题. 看看下面这个代码: String address = person.getCount ...

  5. 关于 JavaScript 的 null 和 undefined,判断 null 的真实类型

    null.undefined 博客地址: https://ainyi.com/39 undefined:表示一个变量最原始的状态,而非人为操作的结果 null:表示一个对象被人为的重置为空对象,而非一 ...

  6. java8 Optional优雅非空判断

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

  7. php中函数 isset(), empty(), is_null() 的区别,boolean类型和string类型的false判断

    php中函数 isset(), empty(), is_null() 的区别,boolean类型和string类型的false判断 实际需求:把sphinx返回的结果放到ssdb缓存里,要考虑到sph ...

  8. JS中判断null、undefined与NaN的方法

    写了个 str ="s"++; 然后出现Nan,找了一会. 收集资料如下判断: 1.判断undefined: 代码如下: <span style="font-siz ...

  9. JS中 判断null

    以下是不正确的方法: var exp = null; if (exp == null) { alert("is null"); } exp 为 undefined 时,也会得到与 ...

  10. JS中如何判断null、undefined与NaN

    1.判断undefined: <span style="font-size: small;">var tmp = undefined; if (typeof(tmp)  ...

随机推荐

  1. 使用 FreeSSL 申请免费证书

    官网 https://freessl.cn/ 首先,注册一个账户 然后登录 输入自己的域名,选择第2个"亚洲诚信"(1年),然后点击"创建免费SSL证书"按钮 ...

  2. ubuntu 的 apt 命令

    工作原理 apt 全称 advanced packaging tool 是 ubuntu 下的包管理工具 apt 采用集中式仓储机制来管理软件,有 软件安装包 和 软件安装列表 两部分完成. 使用 a ...

  3. 幻方(4n+2暂时看不懂)

    奇数阶幻方 Siamese方法(Kraitchik 1942年,pp. 148-149)是构造奇数阶幻方的一种方法,说明如下: 把放置在第一行的中间. 顺序将等数放在右上方格中. 当右上方格出界的时候 ...

  4. linux dma

    https://bootlin.com/pub/conferences/2015/elc/ripard-dmaengine/ripard-dmaengine.pdf https://biscuitos ...

  5. BUUCTF-[GXYCTF2019]Ping Ping Ping

    一道命令执行题目    一.基础知识  Linux shell特殊字符(参考链接) [;]作为多个命令语句的分隔符(Command separator [semicolon]). 要在一个语句里面执行 ...

  6. sql offset 优化

    // let groupSql = ` select id,jd_gcj02ll, wd_gcj02ll from ${tablename_qiye} where id between ${size ...

  7. proguard-maven-plugin混淆代码排除方法

    当使用proguard-maven-plugin混淆代码时,如果要排除某个类中某个方法不混淆,务必参数指定全路径类名,否则会不生效.

  8. 监控本机环境生成SQL脚本

    在开发工作中我们客户端连接 测试服务器开发工作,往往很多人操作数据库,如何甄别出自己操作 方法一: 在程序配置节点设置App节点,譬如: <add name="ModelEntitie ...

  9. select控件操作汇总

    1.通过select的text来选中对应的option $("#dddddd option:contains('小型车')").attr("selected", ...

  10. kubectl的vistor模式

    package main import ( "encoding/json" "encoding/xml" "log" ) type Visi ...