在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。
英文定义如下:
Provide an object as a surrogate for the lack of an object of a given type. The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators.
意思是:为缺少的对象提供一个默认的无意义对象,用来避免 Null 对象的产生。
简单来说,就是用一个空对象,来取代程序中的 Null 值判断,从而让调用者可以直接使用对象,而无需关心对象是否为 Null。
例如,在没用空对象模式之前,要正确的获取以下值:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
它的实现代码如下:
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
非空判断已经多到令我们崩溃了,如果属性中还有更多的对象,那 Null 值判断就更多了,为了解决这个问题,就要使用本文将要介绍的空对象模式了。
空对象模式包含如下角色:
角色之间的 UML 关系图如下:
/**
* 抽象对象
*/
abstract class AbstractObject {
String name;
abstract String getName();
abstract boolean isNull();
}
/**
* 具体对象
*/
class ConcreteObject extends AbstractObject {
public ConcreteObject(final String name) {
this.name = name;
}
@Override
String getName() {
return this.name;
}
@Override
boolean isNull() {
return false;
}
}
/**
* 空对象
*/
class NullObject extends AbstractObject {
@Override
String getName() {
return "Not Available in Customer Database";
}
@Override
boolean isNull() {
return true;
}
}
/**
* 对象生成工厂
*/
class ObjectFactory {
public static AbstractObject creator(final String name) {
AbstractObject result = null;
switch (name) {
case "Java":
result = new ConcreteObject("Java");
break;
case "SQL":
result = new ConcreteObject("SQL");
break;
default:
result = new NullObject();
break;
}
return result;
}
}
程序执行结果如下:
Java
Not Available in Customer
Database SQL
从以上的代码可以看出,其中 getName () 为所有对象需要执行的公共方法,如果没使用空对象模式的情况下,每次在调用 getName () 之前,我们需要先判空再使用,而如果使用的是空对象模式的话,则可以直接使用(该方法)。
空对象模式的优点:
空对象模式的缺点:
JDK 8 中的 Optional 对象使用的就是空对象模式,避免空指针的异常,同时又能写出优雅而简洁的 Java 代码。
Optional 类中有以下几个重要的方法:
小贴士:很多人可能对 “对该值执行提供的 Function 函数调用” 这句话不太理解,它的意思是说,例如下面代码:
Optional.ofNullable(concreteUser).flatMap(u -> u.getAddress())
//其中 “(u -> u.getAddress ())” 这部分代码就是 “该值执行提供的 Function 函数”。
接下来我们就是用 Optional 对象,优雅的实现判空操作,优雅的实现文章开头 4 层令人崩溃的 Null 值判断,实现代码如下。
/**
* 用户类
**/
class User {
public User(Address address) {
this.address = address;
}
private Address address;
public Optional getAddress() {
return Optional.ofNullable(address);
}
public void setAddress(Address address) {
this.address = address;
}
}
/**
* 地址类
**/
class Address {
public Address(Country country) {
this.country = country;
}
private Country country;
public Optional getCountry() {
return Optional.ofNullable(country);
}
public void setCountry(Country country) {
this.country = country;
}
}
/**
* 国际编码类
**/
class Country {
public Country(String isoCode) {
this.isoCode = isoCode;
}
private String isoCode;
public String getIsocode() {
return isoCode;
}
public void setIsocode(String isoCode) {
this.isoCode = isoCode;
}
}
public class Client {
public static void main(final String[] args) {
// JDK 8 Optional 对象判空示例
// 具体对象
User concreteUser = new User(new Address(new Country("china")));
// 空对象
User nullUser = new User(null);
// 具体对象编码获取
String concreteIsocode = Optional.ofNullable(concreteUser)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("暂无").toUpperCase();
// 空对象编码获取
String nullIsocode = Optional.ofNullable(nullUser)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("暂无").toUpperCase();
System.out.println("Concrete User:" + concreteIsocode);
System.out.println("Null User:" + nullIsocode);
}
}
程序直接结果如下:
Concrete User:CHINA Null
User:暂无
在 Java 语言中,解决 NullPointerException 异常的常见方法是使用空对象模式,空对象模式可以省去代码中对 Null 值的判断,从而使代码更加的简洁和优雅。在 JDK 8 之后,Java API 给我们提供了 Optional 类,使用它可以优雅且有效的,规避空对象产生 NullPointerException 的问题。
传统的空对象模式通过服务端避免null值异常,Optional 从客户端使用避免null值异常。尤其是在需要嵌套获取值的情况下考虑使用Optional(文章开头的示例)。