空指针是我们最常见也最讨厌的异常,写过 Java 程序的同学,一般都遇到过 NullPointerException :)

初识null

详细可以参考【jdk 1.6 Java.lang.Null.Pointer.Exception

—— 为了不抛出这个异常,我们便会写如下的代码:

SysUser user = getUserById(id);
if (user != null) {
String username = user.getUsername();
System.out.println("Username is: " + username); // 使用 username
}

但是很多时候,我们可能会忘记写 if (user != null) —— 如果在开发阶段就发现那还好,但是如果在开发阶段没有测试到问题,等到上线却出了 NullPointerException ... 画面太美,我不敢继续想下去。

Optional 的引入

为了解决这种尴尬的处境,JDK 终于在 Java8 的时候加入了 Optional 类。用于避免空指针的出现,也无需在写大量的if(obj!=null)这样的判断了,前提是你得将数据用Optional装着,它就是一个包裹着对象的容器。

Optional 的 javadoc 介绍:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

这是一个可以包含或者不包含非 null 值的容器。如果值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象。

JDK 提供三个静态方法来构造一个 Optional:

  • 1.Optional.of(T value)

该方法通过一个非 null 的 value 来构造一个 Optional,返回的 Optional 包含了 value 这个值。对于该方法,传入的参数一定不能为 null,否则便会抛出 NullPointerException。

  • 2.Optional.ofNullable(T value)

该方法和 of 方法的区别在于,传入的参数可以为 null

—— 但是前面 javadoc 不是说 Optional 只能包含非 null 值吗?我们可以看看 ofNullable 方法的源码:

public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

进行三目运算,判断传入的参数是否为 null,如果为 null 的话,返回的就是 Optional.empty()。

  • 3.Optional.empty()

该方法用来构造一个空的 Optional,即该 Optional 中不包含值 —— 其实底层实现还是 如果 Optional 中的 value 为 null 则该 Optional 为不包含值的状态,然后在 API 层面将 Optional 表现的不能包含 null 值,使得 Optional 只存在 包含值 和 不包含值 两种状态。

private static final Optional<?> EMPTY = new Optional<>();

private final T value;

private Optional(){
this.value = null;
}

前面 javadoc 也有提到,Optional 的 isPresent() 方法用来判断是否包含值,get() 用来获取 Optional 包含的值 —— 值得注意的是,如果值不存在,即在一个Optional.empty 上调用 get() 方法的话,将会抛出 NoSuchElementException 异常。

我们假设 getSysUserById 已经是个客观存在的不能改变的方法,那么利用 isPresent 和 get 两个方法,我们现在能写出下面的代码:

Optional<SysUser> user = Optional.ofNullable(getSysUserById(id));
if (user.isPresent()) {
String username = user.get().getUsername();
System.out.println("Username is: " + username); // 使用 username
}

好像看着代码是优美了点

—— 但是事实上这与之前判断 null 值的代码没有本质的区别,反而用 Optional 去封装 value,增加了代码量。所以我们来看看 Optional 还提供了哪些方法,让我们更好的(以正确的姿势)使用 Optional。

Optional 提供的方法

1.ifPresent
public void ifPresent(Consumer<? super T> consumer) {
if (value != null) {
consumer.accept(value);
}
}

如果 Optional 中有值,则对该值调用 consumer.accept,否则什么也不做。 所以对于上面的例子,我们可以修改为:

Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
2.orElse
public T orElse(T other) {
return value != null ? value : other;
}

如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。

User user = Optional
.ofNullable(getUserById(id))
.orElse(new User(0, "Unknown")); System.out.println("Username is: " + user.getUsername());
3.orElseGet
public T orElseGet(Supplier<? extends T> ither) {
return value != null ? value : other.get();
}

orElseGet 与 orElse 方法的区别在于,orElseGet 方法传入的参数为一个 Supplier 接口的实现 —— 当 Optional 中有值的时候,返回值;当 Optional 中没有值的时候,返回从该 Supplier 获得的值。

User user = Optional
.ofNullable(getUserById(id))
.orElseGet(() -> new User(0, "Unknown")); System.out.println("Username is: " + user.getUsername());
4.orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

orElseThrow 与 orElse 方法的区别在于,orElseThrow 方法当 Optional 中有值的时候,返回值;没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。

User user = Optional
.ofNullable(getUserById(id))
.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户没有找到"));

举一个 orElseThrow 的用途:在 SpringMVC 的控制器中,我们可以配置统一处理各种异常。查询某个实体时,如果数据库中有对应的记录便返回该记录,否则就可以抛出 EntityNotFoundException ,处理 EntityNotFoundException 的方法中我们就给客户端返回Http 状态码 404 和异常对应的信息 —— orElseThrow 完美的适用于这种场景。

@RequestMapping("/{id}")
public SysUser getSysUser(@PathVariable Integer id) {
Optional<SysUser> user = userService.getSysUserById(id);
return user.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户不存在"));
} @ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
5.map
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 为 Optional.empty,则依旧返回 Optional.empty;否则返回一个新的 Optional,该 Optional 包含的是:函数 mapper 在以 value 作为输入时的输出值。

Optional<String> username = Optional
.ofNullable(getUserById(id))
.map(user -> user.getUsername()); System.out.println("Username is: " + username.orElse("Unknown"));

而且我们可以多次使用 map 操作:

Optional<String> username = Optional
.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.map(name -> name.toLowerCase())
.map(name -> name.replace('_', ' ')); System.out.println("Username is: " + username.orElse("Unknown"));
6.flatMap
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));
} }

flatMap 方法与 map 方法的区别在于,map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional。

Optional<String> username = Optional
.ofNullable(getUserById(id))
.flatMap(user -> Optional.of(user.getUsername()))
.flatMap(name -> Optional.of(name.toLowerCase())); System.out.println("Username is: " + username.orElse("Unknown"));

7.filter

public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if(!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}

filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。

Optional<String> username = Optional
.ofNullable(getUserById(id))
.filter(user -> user.getId() < 10)
.map(user -> user.getUsername()); System.out.println("Username is: " + username.orElse("Unknown"));

有了 Optional,我们便可以方便且优雅的在自己的代码中处理 null 值,而不再需要一昧通过容易忘记和麻烦的 if (object != null) 来判断值不为 null。如果你的程序还在使用 Java8 之前的 JDK,可以考虑引入 Google 的 Guava 库 —— 事实上,早在 Java6 的年代,Guava 就提供了 Optional 的实现。

小结

 public class OptionalTest {
public static void main(String[] arg) {
//创建Optional对象,如果参数为空直接抛出异常
Optional<String> str=Optional.of("a"); //获取Optional中的数据,如果不存在,则抛出异常
System.out.println(str.get()); //optional中是否存在数据
System.out.println(str.isPresent()); //获取Optional中的值,如果值不存在,返回参数指定的值
System.out.println(str.orElse("b")); //获取Optional中的值,如果值不存在,返回lambda表达式的结果
System.out.println(str.orElseGet(()->new Date().toString())); //获取Optional中的值,如果值不存在,抛出指定的异常
System.out.println(str.orElseThrow(()->new RuntimeException())); Optional<String> str2=Optional.ofNullable(null); //optional中是否存在数据
System.out.println(str2.isPresent()); //获取Optional中的值,如果值不存在,返回参数指定的值
System.out.println(str2.orElse("b")); //获取Optional中的值,如果值不存在,返回lambda表达式的结果
System.out.println(str2.orElseGet(()->new Date().toString())); //获取Optional中的值,如果值不存在,抛出指定的异常
System.out.println(str2.orElseThrow(()->new RuntimeException()));
}
}

运行结果:

a
true
a
a
a
false
b
Mon May 15 20:22:47 CST 2017
Exception in thread "main" java.lang.RuntimeException
at OptionalTest.lambda$main$3(OptionalTest.java:42)
at OptionalTest$$Lambda$4/931919113.get(Unknown Source)
at java.util.Optional.orElseThrow(Optional.java:290)
at OptionalTest.main(OptionalTest.java:42)

示例代码:

 package crazy;

 import java.util.Optional;

 class Company {
private String name;
private Optional<Office> office; public Company(String name, Optional<Office> office) {
this.name = name;
this.office = office;
} public String getName() {
return name;
} public Optional<Office> getOffice() {
return office;
}
} class Office {
private String id;
private Optional<Address> address; public Office(String id, Optional<Address> address) {
this.id = id;
this.address = address;
} public String getId() {
return id;
} public Optional<Address> getAddress() {
return address;
}
} class Address {
private Optional<String> street;
private Optional<String> city; public Address(Optional<String> street, Optional<String> city) {
this.street = street;
this.city = city;
} public Optional<String> getStreet() {
return street;
} public Optional<String> getCity() {
return city;
}
} public class OptionalDemo1 { public static void main(String[] args) {
Optional<Address> address1 = Optional.of(new Address(Optional.ofNullable(null), Optional.of("New York")));
Optional<Office> office1 = Optional.of(new Office("OF1", address1));
Optional<Company> company1 = Optional.of(new Company("Door Never Closed", office1)); // What is the street address of company1?
// In which city company1 is located?
Optional<Office> maybeOffice = company1.flatMap(Company::getOffice);
Optional<Address> maybeAddress = office1.flatMap(Office::getAddress);
Optional<String> maybeStreet = address1.flatMap(Address::getStreet); maybeStreet.ifPresent(System.out::println);
if (maybeStreet.isPresent()) {
System.out.println(maybeStreet.get());
} else {
System.out.println("Street not found.");
} // shorter way
String city = company1.flatMap(Company::getOffice)
.flatMap(Office::getAddress)
.flatMap(Address::getStreet)
.orElse("City is not found."); System.out.println("City: " + city); // only print if city is not null
company1.flatMap(Company::getOffice)
.flatMap(Office::getAddress)
.flatMap(Address::getCity)
.ifPresent(System.out::println); }
}

talk is easy ,show you the code

java8的Optional到底有什么用呢?说起来,它比原来的null值判断有什么优势呢?

它实际上可以看做一个容器,容器里可能有一个非null的值,也可能没有。它带来最大的好处,就是代码在语义上政治正确。

比如我们有个Integer类型的列表类FooList,它有两个方法,一个返回列表的长度,一个返回比传入参数小的,最大的那个值。

在没有Optional的时候,代码如下

public class FooList {

    public Integer size() {
throw new UnsupportedOperationException();
} public Integer maxNumberBelow(Integer upperBound) {
throw new UnsupportedOperationException();
} }

语义上的问题在于,size方法一定会返回一个数字,maxNumberBelow则不会。如果列表是空的,size返回0,maxNumberBelow(100)该返回什么呢?

这时大家就八仙过海,各显神通了。

1 直接返回null。比较常用的一个方法。

2 返回一个不合法的值。比如String里的indexOf方法,会返回一个-1。

3 抛一个异常。比如jpa里的EntityNotFoundException。

抛异常是最不可取的形式,首先不讨论是否应该是受检异常,数据库里没有记录就抛异常好像也不符合异常的定义。数据库里查不到数据挺正常的,怎么会是异常呢?

方法签名没办法有效的标识出它是不是每次都能返回合理的值,也没法标识出它无法返回合理的值时的行为。无法返回合理的值是什么表现?返回了null?返回了一个特殊的值?还是throw了异常?在IDE里调用方法的时候根本看不出来,只能看文档!!!
让开发者好好写文档?不存在的!
让开发者认真看文档?不存在的!

为啥size返回的Integer可以肆无忌惮的使用,而maxNumberBelow返回的Integer就必须和一个奇奇怪怪的值做比较?种族歧视?
让开发者分清楚这些?不存在的!

public class BarList {
public Integer size() {
throw new UnsupportedOperationException();
} public Optional<Integer> maxNumberBelow(Integer upperBound) {
throw new UnsupportedOperationException();
}
}

首先,返回值就能明确区分出,方法是每次返回合理的值还是有条件的返回合理的值。

其次,IDE还能检查出来对Optional对象跳过isPresent直接调用get方法。

欢迎关注:一只阿木木

Java 中遇到null 和为空的情况,使用Optional来解决。的更多相关文章

  1. JDK8 Java 中遇到null 和为空的情况,使用Optional来解决。

    空指针是我们最常见也最讨厌的异常,写过 Java 程序的同学,一般都遇到过 NullPointerException :) 初识null 详细可以参考[jdk 1.6 Java.lang.Null.P ...

  2. Java中有关Null的9件事

    对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常 (NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误.Java为什么要保留null呢?null出现有一段时间了,并且我 ...

  3. 转!!Java中关于Null的9个解释(Java Null详解)

    对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误.Java为什么要保留null呢?null出现有一段时间了,并且我认 ...

  4. JAVA中String = null 与 String = "" 的区别

    JAVA中String = null 与 String = ""的区别 笔者今天在Debug的时候发现的NPE(NullPointerException),辛辛苦苦地调试了半天,终 ...

  5. 关于Java中的Null

    什么是Java中的Null? null在Java中是一个非常重要的概念,它最初是为了表示缺少某些东西,例如缺少用户.资源或任何东西而发明出来的.但是这也为Java程序员带来了很多麻烦,比如最常见的空指 ...

  6. Java中的Null是什么?

    对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误.Java为什么要保留null呢?null出现有一段时间了,并且我认 ...

  7. Java中有关Null的9件事(转)

    对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误.Java为什么要保留null呢?null出现有一段时间了,并且我认 ...

  8. 关于 Java 中的 Null

    什么是Java中的Null? null在Java中是一个非常重要的概念,它最初是为了表示缺少某些东西,例如缺少用户.资源或任何东西而发明出来的.但是这也为Java程序员带来了很多麻烦,比如最常见的空指 ...

  9. java中对对象进行判空的操作--简洁编码

    java中对对象进行判空的操作 首先来看一下工具StringUtils的判断方法: 一种是org.apache.commons.lang3包下的: 另一种是org.springframework.ut ...

随机推荐

  1. 深入浅出Java Dom4j读取XML

    在以前自己使用的xml较少,只是了解其很强大,现在可算是在DRP中,真正的开始使用它了,以前只是简单的理解xml,xml即可扩展标记语言,简单的使用,具体是什么?怎么用?还是一直让自己期待的. 首先来 ...

  2. 我眼中的Linux设备树(五 根节点)

    五 根节点一个最简单的设备树必须包含根节点,cpus节点,memory节点.根节点的名字及全路径都是"/",至少需要包含model和compatible两个属性.model属性我们 ...

  3. 【翻译】Ext JS 6 Beta发布

    原文:Ext JS 6 Beta is Now Available 概述 Ext JS 6的好处 新的Ext JS功能和工具 需要你的反馈意见 概述 很高兴,Ext JS 6 beta版本现在发布了. ...

  4. Linux 共享内存 详解

    一.什么是共享内存区 共享内存区是最快的可用IPC形式.它允许多个不相关的进程去访问同一部分逻辑内存.如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存区映射到 ...

  5. python的sorted

    读入后,要进行组内排序,按groupseq字段排序后,然后统计前后两个项的个数,累加到全局. sorted函数使用如下: def sortlist(alllist):     sorted_key1_ ...

  6. 获取request参数的工具类

    package cn.edu.hactcm.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOE ...

  7. TortoiseGit安装过程

     运行TortoiseGit-1.7.7.0-32bit.msi,如下图 点击Next, 下一步 点击Next, 下一步 选择ssh客户端, 使用默认, 点击Next, 下一步 选择安装路径, 点 ...

  8. Android MTK平台最完备的开机动画修改教程

    修改手机的开机动画不是什么难事儿. 但修改一款很冷门的"山寨机",就不太好修改第一屏了. 手机是MTK的一款手机,虽然比较贵(价格超过三星Note3),但在我看来跟山寨机木有啥区别 ...

  9. 运行Myeclipse时,如何删除IVM窗口

    windows------>preference------>run/debug------->lauching--------->percpectives,改成never,n ...

  10. javase--day_01

    一.关键字: /*                            关键字:  被java语言复与特定含义的单词.      特点: 组成关键字单词的字母全部小写.   注意: A:goto和c ...