【Java8新特性】05 使用Optional取代null

简介: 【Java8新特性】05 使用Optional取代null

Java8 由Oracle在2014年发布,是继Java5之后最具革命性的版本。

Java8吸收其他语言的精髓带来了函数式编程,lambda表达式,Stream流等一系列新特性,学会了这些新特性,可以让你实现高效编码优雅编码。


1. 不受待见的空指针异常


有个小故事:null引用最早是由英国科学家Tony Hoare提出的,多年后Hoare为自己的这个想法感到后悔莫及,并认为这是"价值百万的重大失误"。可见空指针是多么不受待见。


NullPointerException是Java开发中最常遇见的异常,遇到这种异常我们通常的解决方法是在调用的地方加一个if判空。


if判空越多会造成过多的代码分支,后续代码维护也就越来越复杂。


2. 糟糕的代码


比如看下面这个例子,使用过多的if判空。


Person对象里定义了House对象,House对象里定义了Address对象:

public class Person {
    private String name;
    private int age;
    private House house;
    public House getHouse() {
        return house;
    }
}
class House {
    private long price;
    private Address address;
    public Address getAddress() {
        return address;
    }
}
class Address {
    private String country;
    private String city;
    public String getCity() {
        return city;
    }
}


现在获取这个人买房的城市,那么通常会这样写:

public String getCity() {
    String city = new Person().getHouse().getAddress().getCity();
    return city;
}


但是这样写容易出现空指针的问题,比如这个人没有房,House对象为null。接着你会改造这段代码,加上很多判断条件:

public String getCity2(Person person) {
    if (person != null) {
        House house = person.getHouse();
        if (house != null) {
            Address address = house.getAddress();
            if (address != null) {
                String city = address.getCity();
                return city;
            }
        }
    }
    return "unknown";
}


为了避免空指针异常,每一层都加上判断,但是这样会造成代码嵌套太深,不易维护。


你可能想到如何改造上面的代码,比如加上提前判空退出:

public String getCity3(Person person) {
    String city = "unknown";
    if (person == null) {
      return city; 
    }
    House house = person.getHouse();
    if (house == null) {
        return city;
    }
    Address address = house.getAddress();
    if (address == null) {
        return city;
    }
    return address.getCity();
}


但是这样简单的代码已经加入了三个退出条件,非常不利于后面代码维护。那怎样才能将代码写的优雅一点呢,下面引入今天的主角"Optional"。


3. 解决空指针的"银弹"


从Java8开始引入了一个新类 java.util.Optional,这是一个对象的容器,意味着可能包含或者没有包含一个非空的值。下面重点看一下Optional的常用方法:

public final class Optional<T> {
    // 通过指定非空值创建Optional对象
    // 如果指定的值为null,会抛空指针异常
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
    // 通过指定可能为空的值创建Optional对象
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    // 返回值,不存在抛异常
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    // 如果值存在,根据consumer实现类消费该值
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
    // 如果值存在则返回,如果值为空则返回指定的默认值
    public T orElse(T other) {
        return value != null ? value : other;
    }
    // map flatmap等方法与Stream使用方法类似,这里不再赘述,读者可以参考之前的Stream系列。
}


以上就是Optional类常用的方法,使用起来非常简单。


4. Optional使用入门


(1)创建Optional实例

  • 创建空的Optional对象。可以通过静态工厂方法Optional.Empty() 创建一个空的对象,例如:
Optional<Person> optionalPerson = Optional.Empty();
  • 指定非空值创建Optional对象。
Person person = new Person();
Optional<Person> optionalPerson = Optional.of(person);
  • 指定可能为空的值创建Optional对象。
Person person = null; // 可能为空
Optional<Person> optionalPerson = Optional.of(person);


(2)常用方法

ifPresent

如果值存在,则调用consumer实例消费该值,否则什么都不执行。举个栗子:

String str = "hello java8";
// output: hello java8
Optional.ofNullable(str).ifPresent(System.out::println);
String str2 = null;
// output: nothing
Optional.ofNullable(str2).ifPresent(System.out::println);


filter, map, flatMap

在三个方法在前面讲Stream的时候已经详细讲解过,读者可以翻看之前写的文章,这里不再赘述。


orElse

如果value为空,则返回默认值,举个栗子:

public void test(String city) {
    String defaultCity = Optional.ofNullable(city).orElse("unknown");
}


orElseGet

如果value为空,则调用Supplier实例返回一个默认值。举个例子:

public void test2(String city) {
    // 如果city为空,则调用generateDefaultCity方法
    String defaultCity = Optional.of(city).orElseGet(this::generateDefaultCity);
}
private String generateDefaultCity() {
    return "beijing";
}


orElseThrow

如果value为空,则抛出自定义异常。举个栗子:

public void test3(String city) {
    // 如果city为空,则抛出空指针异常。
    String defaultCity = Optional.of(city).orElseThrow(NullPointerException::new);
}


5. 使用Optional重构代码


再看一遍重构之前的代码,使用了三个if使代码嵌套层次变得很深。

// before refactor
public String getCity2(Person person) {
    if (person != null) {
        House house = person.getHouse();
        if (house != null) {
            Address address = house.getAddress();
            if (address != null) {
                String city = address.getCity();
                return city;
            }
        }
    }
    return "unknown";
}


使用Optional重构

public String getCityUsingOptional(Person person) {
    String city = Optional.ofNullable(person)
            .map(Person::getHouse)
            .map(House::getAddress)
            .map(Address::getCity).orElse("Unknown city");
    return city;
}


只使用了一行代码就获取到city值,不用再去不断的判断是否为空,这样写代码是不是很优雅呀。赶紧用Optional重构你的项目吧~


相关文章
|
2月前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
66 2
|
2月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
41 3
|
2月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
33 2
|
2月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
34 3
|
21天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
30 4
|
2月前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
69 3
|
2月前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
26 2
|
2月前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
27 2
|
2月前
|
Java
Java Set以其“不重复”的特性,为我们提供了一个高效、简洁的处理唯一性约束数据的方式。
【10月更文挑战第16天】在Java编程中,Set接口确保集合中没有重复元素,每个元素都是独一无二的。HashSet基于哈希表实现,提供高效的添加、删除和查找操作;TreeSet则基于红黑树实现,不仅去重还能自动排序。通过这两个实现类,我们可以轻松处理需要唯一性约束的数据,提升代码质量和效率。
39 2
|
2月前
|
存储 Java 数据处理
在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出
【10月更文挑战第14天】在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出。本文通过两个案例展示了Set的实用性和高效性:快速去重和高效查找。通过将列表转换为HashSet,可以轻松实现去重;而Set的contains方法则提供了快速的元素查找功能。这些特性使Set成为处理大量数据时的利器。
19 4