Optional

java 的 NPE(Null Pointer Exception)所谓的空指针异常搞的头昏脑涨, 有大佬说过 “防止 NPE,是程序员的基本修养。” 但是修养归修养,也是我们程序员最头疼的问题之一,那么我们今天就要尽可能的利用Java8的新特性Optional来尽量简化代码同时高效处理 NPE(Null Pointer Exception 空指针异常)

认识Optional并简单使用

简单来说,Opitonal 类就是 Java 提供的为了解决大家平时判断对象是否为空用 会用 null!=obj 这样的方式存在的判断,从而令人头疼导致 NPE(Null Pointer Exception 空指针异常),同时 Optional 的存在可以让代码更加简单,可读性跟高,代码写起来更高效.

正常代码,判断对象是否为空

Admin person=new Admin();
if (null==admin){
return "admin为null";
}
return person;

当我们使用Optional判断对象是否为空时:

//一、Optional判断对象是否为空
Admin admin = new Admin();
Optional<Admin> admin1 = Optional.ofNullable(admin);

神奇的Optional类

Optional类内部

首先我们先打开 Optional 的内部, 去一探究竟 先把几个创建 Optional 对象的方法提取出来:

【这些方法很重要一定要看懂哦,后面都会使用到的】

public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
//我们可以看到两个构造方格都是private 私有的
//说明 我们没办法在外面去new出来Optional对象
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
//这个静态方法大致 是创建出一个包装值为空的一个对象因为没有任何参数赋值
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//这个静态方法大致 是创建出一个包装值非空的一个对象 因为做了赋值
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//这个静态方法大致是 如果参数value为空,则创建空对象,如果不为空,则创建有参对象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
}

再做一个简单的实例展示 与上面对应:

// 1、创建一个包装对象值为空的Optional对象
Optional<String> optEmpty = Optional.empty(); // 2、创建包装对象值非空的Optional对象(使用of方法一定要保证对象非空,否则会抛异常)
Optional<String> optOf = Optional.of("optional"); // 3、创建包装对象值允许为空也可以不为空的Optional对象
Optional<String> optOfNullable1 = Optional.ofNullable(null);
Optional<String> optOfNullable2 = Optional.ofNullable("optional");

我们关于创建 Optional 对象的内部方法大致分析完毕 接下来也正式的进入 Optional 的学习与使用中。

Optional类常用的方法

Optional.get() 方法【返回对象的值】

get()方法源码:

public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

由此我们可以看到get()方法返回的是一个Optional实例值,

也就是说,源码中如果value的值不为空就会返回value,如果为空,则会直接抛出一个异常 "No value present"

测试实例代码:

Admin newAdmin = new Admin();
newAdmin.setName("get方法获取对象值");
Admin Nadmin = Optional.ofNullable(newAdmin).get(); 返回数据:
Nadmin=Admin(id=null, loginName=null, password=null, email=null, name=get方法获取对象值, mobile=null, departmentId=null, registerDate=null, lastLoginDate=null, status=null, delFlag=null)

Optional.isPresent()方法【判读是否为空】

isPresent()方法源码:

public Boolean isPresent() {
return value != null;
}

从源码上我们可以看到 isPresent方法返回的是一个true/false值,如果判断的对象不为空着返回false,为空着返回true

测试实例代码:

Admin admin3 = new Admin();
admin3.setName("isPresent方法判断是否为空");
Optional.ofNullable(admin3).ifPresent(p -> p.setName(""));

如果admin对象不为空,则会执行ifPresent方法中的函数,将对象中的name值修改为空字符串。如果对象为空则不会执行函数,并且不会抛空指针异常,optional中已经对NPE(非空验证)

Optional.filter() 方法 【过滤对象】

filter() 方法源码展示:

public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
//如果为空直接返回this
if (!isPresent())
return this; else
//判断返回本身还是空Optional
return predicate.test(value) ? this : empty();
}

接受一个对象,然后对他进行条件过滤,如果条件符合则返回 Optional 对象本身,如果不符合则返回空 Optional

测试代码实例:

Admin admin4 = new Admin();
admin4.setName("filter方法,根据条件过滤对象");
Optional<Admin> adminfilter = Optional.ofNullable(admin4).filter(p -> p.getName().equals("filter方法,根据条件过滤对象"));

Optional.map() 方法 [对象进行二次包装]

map() 方法将对应 Funcation 函数式接口中的对象,进行二次运算,封装成新的对象然后返回在 Optional 中 源码:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
//如果为空返回自己
if (!isPresent())
return empty();
else {
//否则返回用方法修饰过的Optional
return Optional.ofNullable(mapper.apply(value));
}
}

测试代码用例:

 Admin admin5 = new Admin();
Optional<String> adminFlatMap= Optional.ofNullable(admin5).map(m -> Optional.ofNullable(m.getName()).orElse("name为空"));

Optional.orElse() 方法 [为空返回对象]

如果包装对象为空的话,就执行 orElse 方法里的 value,如果非空,则返回写入对象 源码:

public T orElse(T other) {
//如果非空,返回value,如果为空,返回other
return value != null ? value : other;
}

Optional.orElseGet() 方法 [为空返回 Supplier 对象]

这个与 orElse 很相似,入参不一样,入参为 Supplier 对象,为空返回传入对象的. get() 方法,如果非空则返回当前对象 源码:

public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

测试代码实例:

Optional<Supplier<Person>> sup=Optional.ofNullable(Person::new);
//调用get()方法,此时才会调用对象的构造方法,即获得到真正对象
Optional.ofNullable(person).orElseGet(sup.get());

Supplier 对象:

Supplier 也是创建对象的一种方式, 简单来说,Suppiler 是一个接口,是类似 Spring 的懒加载,声明之后并不会占用内存,只有执行了 get() 方法之后,才会调用构造方法创建出对象创建对象的语法的话就是

语法:Supplier supPerson= Person::new

需要使用时supPerson.get()即可

Optional.orElseThrow() 方法 [为空返回异常]

如果对象为空,就抛出自定义的异常,如果不为空则返回当前对象,方便异常的处理:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

测试代码用例:

//简单的一个查询
Member member = memberService.selectByPhone(request.getPhone());
Optional.ofNullable(member).orElseThrow(() -> new ServiceException("没有查询的相关数据"));

相似方法区别

orElse() 和 orElseGet() 和 orElseThrow() 的异同点

方法效果类似,如果对象不为空,则返回对象,如果为空,则返回方法体中的对应参数,所以可以看出这三个方法体中参数是不一样的

  • orElse(T 对象)
  • orElseGet(Supplier 对象)
  • orElseThrow(异常)

orEle()

optional值不存在时,程序执行orElse(),返回执行后的参数,如果optional值存在时,则orElse()则不会再执行。

对于orElse()orElseGet()方法的区别,我们可以通过下面optional值得情况可以看出:

  • optional有值:
import java.util.Arrays;
import java.util.List; public class orElseOrElseGetComparation {
public static void main(String[] args){
List<Integer> list = Arrays.asList(23,1,3);
int myElse = list.stream().reduce(Integer::sum).orElse(get("myElse"));
int myElseGet = list.stream().reduce(Integer::sum).orElseGet(() -> get("myElseGet"));
System.out.println("myElse的值"+myElse);
System.out.println("myElseGet的值"+myElseGet); }
public static int get(String name){
System.out.println(name+"执行了该方法");
return 1;
}
}

结果:

myElse执行了该方法
myElse的值27
myElseGet的值27
  • optinoal为空时:
import java.util.Arrays;
import java.util.List; public class orElseOrElseGetComparation {
public static void main(String[] args){
List<Integer> list = Arrays.asList();
int myElse = list.stream().reduce(Integer::sum).orElse(get("myElse"));
int myElseGet = list.stream().reduce(Integer::sum).orElseGet(() -> get("myElseGet"));
System.out.println("myElse的值"+myElse);
System.out.println("myElseGet的值"+myElseGet); }
public static int get(String name){
System.out.println(name+"执行了该方法");
return 1;
}
}

结果:

myElse执行了该方法
myElseGet执行了该方法
myElse的值1
myElseGet的值1

从上面的执行结果我们可以看出orElse()方法在不论optional是否有值都会执行,在optional为空值的情况下orElseorElseGet都会执行,当optional不为空时,orElseGet不会执行

map()和flatMap()区别

map

map 把数组流中的每一个值,使用所提供的函数执行一遍,一一对应,得到元素个数相同的数组流。

flatMap

flat是扁平的意思。它把数组流中的每一个值,使用所提供的函数执行一遍,一一对应。得到元素相同的数组流。只不过,里面的元素也是一个子数组流。把这些子数组合并成一个数组以后,元素个数大概率会和原数组流的个数不同。

实例

案例:对给定单词列表 ["Hello","World"],你想返回列表["H","e","l","o","W","r","d"]

第一种方案 map

        String[] words = new String[]{"Hello","World"};
List<String[]> a = Arrays.stream(words)
.map(word -> word.split(""))
.distinct()
.collect(toList());
a.forEach(System.out::print);
代码输出为:[Ljava.lang.String;@12edcd21[Ljava.lang.String;@34c45dca
(返回一个包含两个String[]的list)

这个实现方式是由问题的,传递给map方法的lmbda每个单词生成了一个String[](String列表)。因此,map返回的流实际上是Stream<String[]> 类型的。你真正想要的是用Stream<String>来表示一个字符串。

下方图是上方代码stream的运行流程



第二种方式:flatMap(对流扁平化处理)

          String[] words = new String[]{"Hello","World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
a.forEach(System.out::print); 结果输出:HeloWrd

使用flatMap方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用map(Array::stream)时生成的单个流被合并起来,即扁平化为一个流。

下图是运用flatMapstream运行流程:

实战场景再现

场景 一

service 层中查询一个对象,返回之后判断是否为空并做处理

//查询一个对象
Member member = memberService.selectByIdNo(request.getCertificateNo());
//使用ofNullable加orElseThrow做判断和操作
Optional.ofNullable(member).orElseThrow(() -> new ServiceException("没有查询的相关数据"));

场景 二

我们可以在 dao 接口层中定义返回值时就加上 Optional 例如:我使用的是 jpa,其他也同理

public interface LocationRepository extends JpaRepository<Location, String> {
Optional<Location> findLocationById(String id);
}

然在是 Service

public TerminalVO findById(String id) {
//这个方法在dao层也是用了Optional包装了
Optional<Terminal> terminalOptional = terminalRepository.findById(id);
//直接使用isPresent()判断是否为空
if (terminalOptional.isPresent()) {
//使用get()方法获取对象值
Terminal terminal = terminalOptional.get();
//在实战中,我们已经免去了用set去赋值的繁琐,直接用BeanCopy去赋值
TerminalVO terminalVO = BeanCopyUtils.copyBean(terminal, TerminalVO.class);
//调用dao层方法返回包装后的对象
Optional<Location> location = locationRepository.findLocationById(terminal.getLocationId());
if (location.isPresent()) {
terminalVO.setFullName(location.get().getFullName());
}
return terminalVO;
}
//不要忘记抛出异常
throw new ServiceException("该终端不存在");
}

Optional 使用注意事项

Optional 真么好用,真的可以完全替代 if 判断吗?

我想这肯定是大家使用完之后 Optional 之后可能会产生的想法,答案是否定的

举一个最简单的栗子:

例子 1:

如果我只想判断对象的某一个变量是否为空并且做出判断呢?

Person person=new Person();
person.setName("");
persion.setAge(2);
//普通判断
if(StringUtils.isNotBlank(person.getName())){
//名称不为空执行代码块
}
//使用Optional做判断
Optional.ofNullable(person).map(p -> p.getName()).orElse("name为空");

我觉得这个例子就能很好的说明这个问题,只是一个很简单判断,如果用了 Optional 我们还需要考虑包装值,考虑代码书写,考虑方法调用,虽然只有一行,但是可读性并不好,如果别的程序员去读,我觉得肯定没有 if 看的明显.

jdk1.9 对 Optional 优化(待补充)

首先增加了三个方法:

or()、ifPresentOrElse() 和 stream()

or() orElse 等方法相似,如果对象不为空返回对象,如果为空则返回 or() 方法中预设的值。

ifPresentOrElse() 方法有两个参数:一个Consumer和一个 Runnable。如果对象不为空,会执行 Consumer 的动作,否则运行 Runnable。相比 ifPresent()多了 OrElse 判断。

stream() Optional 转换成 stream,如果有值就返回包含值的 stream,如果没值,就返回空的 stream

最新文章

  1. Windows 10不能拨L2TP协议的VPN
  2. RabbitMQ headers Exchange
  3. 安卓开发_浅谈Android动画(一)
  4. Java中的流
  5. git 命令的学习
  6. HTTP请求415错误 – 不支持的媒体类型(Unsupported media type)
  7. Part 82 to 85 Talking about Generic queue, stack collection class
  8. jQuery 知识积累
  9. jdbc详解(三)
  10. Bitmap、BitmapDrawable、BitmapFactory、Matrix类之间的关系
  11. 网络与WEB 编程
  12. CSS float 属性
  13. sed的N;P用法
  14. unity3d从入门到精通要掌握什么内容
  15. PeopleSoft 后台更新密码
  16. 洛谷P4926 [1007]倍杀测量者(差分约束)
  17. [python]函数返回多个return值
  18. MySQL--(了解)可能会用到的内置函数
  19. 开关电源五种PWM反馈控制模式
  20. vue-client脚手架使用

热门文章

  1. mysql query cache 查询缓存
  2. jsp 记录1 bs/cs
  3. user.ini Operation not permitted
  4. 利用ADB命令强制卸载oppo自带浏览器
  5. js工厂函数
  6. 开源项目bootdo的实战开发笔记
  7. json针对list map set 应用
  8. 对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相
  9. 【jmeter】实现接口关联的两种方式:正则表达式提取器和json提取器
  10. pycharm新建项目时选择virtualenv的说明