澳门新葡亰平台游戏网站java8新特性之Optional类

摘要:
Optional不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现

NullPointException可以说是所有java程序员都遇到过的一个异常,虽然java从设计之初就力图让程序员脱离指针的苦海,但是指针确实是实际存在的,而java设计者也只能是让指针在java语言中变得更加简单、易用,而不能完全的将其剔除,所以才有了我们日常所见到的关键字null

原创文章,转载请标注出处:《Java基础系列-Optional》

Java8新特性系列

空指针异常是一个运行时异常,对于这一类异常,如果没有明确的处理策略,那么最佳实践在于让程序早点挂掉,但是很多场景下,不是开发人员没有具体的处理策略,而是根本没有意识到空指针异常的存在。当异常真的发生的时候,处理策略也很简单,在存在异常的地方添加一个if语句判定即可,但是这样的应对策略会让我们的程序出现越来越多的null判定,我们知道一个良好的程序设计,应该让代码中尽量少出现null关键字,而java8所提供的Optional类则在减少NullPointException的同时,也提升了代码的美观度。但首先我们需要明确的是,它并 不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现,从而避免NullPointException

Optional的引入是为了解决null的问题,那么到底是解决null的什么问题呢?

  • Java8新特性(一) –
    lambda表达式
  • Java8新特性(二) – Optional类
  • Java8新特性(三) –
    流式数据处理
  • Java8新特性(四) –
    默认接口方法
  • 待定

一. 直观感受

假设我们需要返回一个字符串的长度,如果不借助第三方工具类,我们需要调用str.length()方法:

if(null == str) { // 空指针判定
    return 0;
}
return str.length();

如果采用Optional类,实现如下:

return Optional.ofNullable(str).map(String::length).orElse(0);

Optional的代码相对更加简洁,当代码量较大时,我们很容易忘记进行null判定,但是使用Optional类则会避免这类问题。

我们知道当我们针对null调用方法的之后,就会抛出空指针异常,Optional就是为了解决这个问题而来的。

NullPointException可以说是所有java程序员都遇到过的一个异常,虽然java从设计之初就力图让程序员脱离指针的苦海,但是指针确实是实际存在的,而java设计者也只能是让指针在java语言中变得更加简单、易用,而不能完全的将其剔除,所以才有了我们日常所见到的关键字null

二. 基本使用

Optional通过封装目标对象的方式来表示,当我们使用的时候,Optional是必然存在的,因为如果结果为null,会返回一个固定的EMPTY实例,这样就不会存在null引用的问题了。

空指针异常是一个运行时异常,对于这一类异常,如果没有明确的处理策略,那么最佳实践在于让程序早点挂掉,但是很多场景下,不是开发人员没有具体的处理策略,而是根本没有意识到空指针异常的存在。当异常真的发生的时候,处理策略也很简单,在存在异常的地方添加一个if语句判定即可,但是这样的应对策略会让我们的程序出现越来越多的null判定,我们知道一个良好的程序设计,应该让代码中尽量少出现null关键字,而java8所提供的Optional类则在减少NullPointException的同时,也提升了代码的美观度。但首先我们需要明确的是,它并 不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现,从而避免NullPointException

1.对象创建

创建空对象

Optional<String> optStr = Optional.empty();

上面的示例代码调用empty()方法创建了一个空的Optional<String>对象型。

创建对象:不允许为空
Optional提供了方法of()用于创建非空对象,该方法要求传入的参数不能为空,否则抛NullPointException,示例如下:

Optional<String> optStr = Optional.of(str);  // 当str为null的时候,将抛出NullPointException

创建对象:允许为空
如果不能确定传入的参数是否存在null值的可能性,则可以用Optional的ofNullable()方法创建对象,如果入参为null,则创建一个空对象。示例如下:

Optional<String> optStr = Optional.ofNullable(str);  // 如果str是null,则创建一个空对象

那该如何来使用Optional呢?

一. 直观感受

假设我们需要返回一个字符串的长度,如果不借助第三方工具类,我们需要调用str.length()方法:

if(null == str) { // 空指针判定
    return 0;
}
return str.length();

如果采用Optional类,实现如下:

return Optional.ofNullable(str).map(String::length).orElse(0);

Optional的代码相对更加简洁,当代码量较大时,我们很容易忘记进行null判定,但是使用Optional类则会避免这类问题。

2.流式处理

流式处理也是java8给我们带来的一个重量级新特性,让我们对集合的操作变得更加简洁和高效,下一篇关于java8新特性的文章,将对流失处理进行全面的讲解。这里Optional也提供了两个基本的流失处理:映射和过滤。

为了演示,我们设计了一个User类,如下:

/**
 * @author: zhenchao.Wang 2016-9-24 15:36:56
 */
public class User {

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;

    private Optional<String> email;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略setter和getter
}

手机和邮箱不是一个人的必须有的,所以我们利用Optional定义。

映射:map与flatMap
映射是将输入转换成另外一种形式的输出的操作,比如前面例子中,我们输入字符串,而输出的是字符串的长度,这就是一种隐射,我们利用方法map()得以实现。假设我们希望获得一个人的姓名,那么我们可以如下实现:

String name = Optional.ofNullable(user).map(User::getName).orElse("no name")

这样当入参user不为空的时候则返回其name,否则返回no name 如我我们希望通过上面方式得到phone或email,利用上面的方式则行不通了,因为map之后返回的是Optional,我们把这种称为Optional嵌套,我们必须在map一次才能拿到我们想要的结果:

long phone = optUser.map(User::getPhone).map(Optional::get).orElse(-1L);

其实这个时候,更好的方式是利用flatMap,一步拿到我们想要的结果:

long phone = optUser.flatMap(User::getPhone).orElse(-1L);

flapMap可以将方法返回的各个流扁平化成为一个流,具体在下一篇专门讲流式处理的文章中细说。

过滤:fliter
filiter,顾名思义是过滤的操作,我们可以将过滤操作做为参数传递给该方法,从而实现过滤目的,加入我们希望筛选18周岁以上的成年人,则可以实现如下:

optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));

我们不能滥用Optional,只有在逻辑上可能为null时才使用Optional来封装目标对象,如果逻辑上肯定不可能为null时,就不要使用Optional封装对象,这样当阅读代码的人看到有Optional则表示目标对象是可以为null的。

二. 基本使用

3.默认行为

默认行为是当Optional为不满足条件时所执行的操作,比如在上面的例子中我们使用的orElse()就是一个默认操作,用于在Optional对象为空时执行特定操作,当然也有一些默认操作是当满足条件的对象存在时执行的操作。

get()
get用于获取变量的值,但是当变量不存在时则会抛出NoSuchElementException,所以如果不确定变量是否存在,则不建议使用

orElse(T other)
当Optional的变量不满足给定条件时,则执行orElse,比如前面当str为null时,返回0。

orElseGet(Supplier<? extends X> expectionSupplier)
如果条件不成立时,需要执行相对复杂的逻辑,而不是简单的返回操作,则可以使用orElseGet实现:

long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> {
    // do something here
    return -1L;
});

orElseThrow(Supplier<? extends X> expectionSupplier)
与get()方法类似,都是在不满足条件时返回异常,不过这里我们可以指定返回的异常类型。

ifPresent(Consumer<? super T>)
当满足条件时执行传入的参数化操作。

这样之后,如果程序中又出现了null指针异常,那么只能是你的代码逻辑有误,而不可能是非逻辑原因了,也就是不该为null的时候出现了null,这肯定是你逻辑搞错了。

1.对象创建

创建空对象

Optional<String> optStr = Optional.empty();

上面的示例代码调用empty()方法创建了一个空的Optional<String>对象型。

创建对象:不允许为空
Optional提供了方法of()用于创建非空对象,该方法要求传入的参数不能为空,否则抛NullPointException,示例如下:

Optional<String> optStr = Optional.of(str);  // 当str为null的时候,将抛出NullPointException

创建对象:允许为空
如果不能确定传入的参数是否存在null值的可能性,则可以用Optional的ofNullable()方法创建对象,如果入参为null,则创建一个空对象。示例如下:

Optional<String> optStr = Optional.ofNullable(str);  // 如果str是null,则创建一个空对象

三. 注意事项

Optional是一个final类,未实现任何接口,所以当我们在利用该类包装定义类的属性的时候,如果我们定义的类有序列化的需求,那么因为Optional没有实现Serializable接口,这个时候执行序列化操作就会有问题:

public class User implements Serializable{

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;  // 不能序列化

    private Optional<String> email;  // 不能序列化

不过我们可以采用如下替换策略:

private long phone;

public Optional<Long> getPhone() {
    return Optional.ofNullable(this.phone);
}

 

2.1 创建Optional实例

public class OptionalTest { public static void main(String[] args) { Optional<String> o = Optional.empty();// 创建一个空Optional }}

public class OptionalTest { public static void main(String[] args) { Optional<String> op = Optional.of;// 创建一个目标对象必须有值的Optional }}

这种创建方式,如果of的参数为null,则直接抛出异常。

public class OptionalTest { public static void main(String[] args) { Optional<String> opt = Optional.ofNullable;// 创建一个目标对象可为null的Optional }}

2.流式处理

流式处理也是java8给我们带来的一个重量级新特性,让我们对集合的操作变得更加简洁和高效,下一篇关于java8新特性的文章,将对流失处理进行全面的讲解。这里Optional也提供了两个基本的流失处理:映射和过滤。

为了演示,我们设计了一个User类,如下:

/**
 * @author: zhenchao.Wang 2016-9-24 15:36:56
 */
public class User {

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;

    private Optional<String> email;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略setter和getter
}

手机和邮箱不是一个人的必须有的,所以我们利用Optional定义。

映射:map与flatMap
映射是将输入转换成另外一种形式的输出的操作,比如前面例子中,我们输入字符串,而输出的是字符串的长度,这就是一种隐射,我们利用方法map()得以实现。假设我们希望获得一个人的姓名,那么我们可以如下实现:

String name = Optional.ofNullable(user).map(User::getName).orElse("no name");

这样当入参user不为空的时候则返回其name,否则返回no name 如我我们希望通过上面方式得到phone或email,利用上面的方式则行不通了,因为map之后返回的是Optional,我们把这种称为Optional嵌套,我们必须在map一次才能拿到我们想要的结果:

long phone = optUser.map(User::getPhone).map(Optional::get).orElse(-1L);

其实这个时候,更好的方式是利用flatMap,一步拿到我们想要的结果:

long phone = optUser.flatMap(User::getPhone).orElse(-1L);

flapMap可以将方法返回的各个流扁平化成为一个流,具体在下一篇专门讲流式处理的文章中细说。

过滤:fliter
filiter,顾名思义是过滤的操作,我们可以将过滤操作做为参数传递给该方法,从而实现过滤目的,加入我们希望筛选18周岁以上的成年人,则可以实现如下:

optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));

2.2 中间操作-处理Optional

这个filter是一个校验器,由于Optional中也只有一个元素,称其为过滤有点太大了,如果Optional中的元素满足给定的校验条件,则将封装元素的Optional返回,否则返回空的Optional。

public class OptionalTest { public static void main(String[] args) { filterTest(); } public static void filterTest(){ Optional<String> os = Optional.of.filter(e -> e.length; System.out.println(os.isPresent; }}

执行结果:

false

因为字符串“123456”长度小于7,所以返回一个空的Optional。

map是映射之意,就是针对Optional封装的元素进行操作,然后返回一个封装着操作结果的新的Optional。

public class OptionalTest { public static void main(String[] args) { mapTest(); } public static void mapTest(){ Optional<Integer> oi = Optional.of("abcdefg").map(e -> e.length; System.out.println; }}

执行结果:

7

这个方法是扁平化映射,所谓扁平就是处理嵌套的Optional。

比如有一个Person类,其中有一个Optional<String>类型的字段name,我们在别处获取到了一个Optional<Person>类型的实例,现在想要获取到这个Person的的姓名,这就是嵌套的Optional。

class Person{ Optional<String> name; public Person(String name){ this.name = Optional.of; } public Optional<String> getName(){ return name; }}

如果我们使用map获取到的是如下的:

public class OptionalTest { public static void main(String[] args) { flatMapTest(); } public static void flatMapTest(){ Optional<Person> op = Optional.of(new Person); Optional<Optional<String>> oos = op.map(Person::getName); String name = oos.get().orElseGet->"noName"); System.out.println; }}

而我们使用flatMap的话就可以是这样的:

public class OptionalTest { public static void main(String[] args) { flatMapTest(); } public static void flatMapTest(){ Optional<Person> op = Optional.of(new Person); Optional<String> os = op.flatMap(Person::getName); String name = os.orElseGet->"noName"); System.out.println; }}

嵌套的Optional要获取到目标对象,必须通过多次去壳才行,这也是使用map映射的原理,但是使用flatMap,扁平化功能,一次去壳操作就完成了。

3.默认行为

默认行为是当Optional为不满足条件时所执行的操作,比如在上面的例子中我们使用的orElse()就是一个默认操作,用于在Optional对象为空时执行特定操作,当然也有一些默认操作是当满足条件的对象存在时执行的操作。

get()
get用于获取变量的值,但是当变量不存在时则会抛出NoSuchElementException,所以如果不确定变量是否存在,则不建议使用

orElse(T other)
当Optional的变量不满足给定条件时,则执行orElse,比如前面当str为null时,返回0。

orElseGet(Supplier<? extends X> expectionSupplier)
如果条件不成立时,需要执行相对复杂的逻辑,而不是简单的返回操作,则可以使用orElseGet实现:

long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> {
    // do something here
    return -1L;
});

orElseThrow(Supplier<? extends X> expectionSupplier)
与get()方法类似,都是在不满足条件时返回异常,不过这里我们可以指定返回的异常类型。

ifPresent(Consumer<? super T>)
当满足条件时执行传入的参数化操作。

2.3 从Optional中获取目标值

有四种方式:

  • public T
    get():直接获取Optional中的值,如果是空的要抛出NoSuchElementException异常
  • public T orElse:如果Optional不为空,则将值返回,否则返回指定的other
  • public T orElseGet(Supplier<? extends T>
    other):如果Optional不为空,则返回值,否则将通过指定方式other生成的值返回
  • public <X extends Throwable> T orElseThrow(Supplier<?
    extends X> exceptionSupplier) throws
    X:如果Optional不为空,返回值,否则抛出由指定的exceptionSupplier生成的异常

public class OptionalTest { public static void main(String[] args) { getTest(); orElseTest(); } public static void getTest(){ Optional<String> os = Optional.of; System.out.println; } public static void orElseTest(){ Optional<String> os = Optional.empty(); System.out.println(os.orElse("default")); System.out.println(os.orElseGet->"default")); System.out.println(os.orElseThrow(RuntimeException::new)); }}

执行结果:

123456defaultdefaultException in thread "main" java.lang.RuntimeException at java.util.Optional.orElseThrow(Optional.java:290) at com.dh.stream.OptionalTest.orElseTest(OptionalTest.java:29) at com.dh.stream.OptionalTest.main(OptionalTest.java:17)

Optional使用时不能直接get,这时会走进老路,get的时候可能为null,就会抛出异常,这时候你就会想要在get之前进行isPresent判断,这个不使用Optional又有什么区别的。

我们使用Optional就是为了简化null判断,所以拒绝使用get方法,Optional提供了正确使用的方法是:orElse、orElseGet、orElseThrow三个方法。

使用orElse方法,我们可以从非空的Optional中获取到值,如果是空的Optional,可以返回orElse方法参数指定的默认值。

使用orElseGet方法,我们可以在空的Optional的情况下主动构建一个默认返回结果。

orElseThrow方法,在空Optional的情况下会抛出一个指定的异常。

说了这么多,最终是为了在开发中使用Optional,正如开始时说的,我们要明确Optional规避的是那种null,不能在所有的地方都使用它。

当项目的业务规则下某个对象可能为null(就是业务允许的null),这种情况下,出现null是正常现象,这种情况需要规避,我们使用Optional封装目标对象,保证不会存在null调用抛出空指针。

但是如果在业务规则下某个对象不可能为null(就是业务不允许为null),这种情况下,出现null就是程序出错了,并不是正常现象,这种时候我们不能用Optional去封装目标对象来规避问题,而是要直接使用,一旦出错就可以及时的排查问题,不至于被Optional将问题给隐藏掉。

三. 注意事项

Optional是一个final类,未实现任何接口,所以当我们在利用该类包装定义类的属性的时候,如果我们定义的类有序列化的需求,那么因为Optional没有实现Serializable接口,这个时候执行序列化操作就会有问题:

public class User implements Serializable{

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;  // 不能序列化

    private Optional<String> email;  // 不能序列化

不过我们可以采用如下替换策略:

private long phone;

public Optional<Long> getPhone() {
    return Optional.ofNullable(this.phone);
}

看来Optional在设计的时候就没有考虑将它作为类的字段使用~

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图