带你撸出一手好代码
使用Null对象替代引用是否为空判断

编程语言中最常见运行时异常非NullPointerException莫属,只要程序依赖于外部的输入数据,比如说http请求传递的查询字符串参数、关系数据库连接、磁盘文件读取,空引用异常就无法避免。通常,程序需要满足某些条件才能正常的往下执行,假如这些条件依赖外部输入数据,而这些外部输入的数据肯定无法保证百分百不出错,比如说网络连接失败、数据库用户名密码错误等,当程序被这些节外生枝的障碍打断时,空引用异常就极有可能被引发。

 

比如说,原本我们调用一个方法,这个方法会执行连接数据库操作并返回一个数据库连接对象。然而,由于某种原因导致连接失败,这个方法并没有照常返回数据库连接对象而是返回一个null值,当我们使用对象时假如不进行是否为空检测,程序就会抛出NullPointerException,但是假如进行检测的话代码又会变得极其丑陋,没有程序员喜欢自己的代码中到处都是if(connection == null)这样的条件判断。

 

而且这种对象是否为空的判断还会传播,在一系列函数调用的过程中,其中某一个调用返回一个null值, 这个函数调用栈中所有的调用都有可能受到波及,直到最外层的调用。这些函数中会出现很多是否为空的判断,严重影响代码的美观程度、可读性,甚至还增加了出BUG的几率。

 

空引用问题是永远无法避免的, 除非从语言层面进行解决, 现在一些现代的新语言的设计已经引入避免此问题的机制。但是一些年纪较大的语言, 比如说Java,只能通过一些代码编写技巧来尽量弱化空引用带来的问题。「使用Null对象代替是否为空判断」是一种流行的解决此问题的技巧。

 

public class Main {
    public static void main(String[] args) {
        getWorstLanguage(false).toString();
        getBestLanguage(false).toString();
    }
    
    public  static String getWorstLanguage( boolean exists) {
        if(exists ) {
            return "Java";
        } else {
            return "";
        }
    }
    
    public static String getBestLanguage( boolean exists ) {
        if( exists ) {
            return "PHP";
        } else {
            return null;
        }
    }
}


main方法中第二行代码执行会抛出空引用异常。 其实两个方法的if条件都没有被满足,然而它们一个返回长度为0的空字符串,一个返回null, 空字符串虽然没有实际意义,但却并非是空引用, 因此在其上执行操作不会抛出空引用异常,代码也是相对安全的。

 

想要避免getBestLanguage方法使用时有可能会引发的异常,一般都会这么做

 

        String  result = getBestLanguage(false);
        if (result != null) {
            result.toString();
        }


显然这并不如getWorstLanguage方法返回空字符串的方式来的优雅。如果你对字符串的长度有依赖,可以以

 

if (result.length() == 0 ) {}


这种方式进行判断,至少它没有出现异常的危险。

 

同样,在方法返回值为其它对象类型的时候也可以借鉴并扩展这种思路。

 

public class Customer {
        String _name;
 
        public Customer() { }
 
        public Customer(String name) {
            _name = name;
        }
 
        public String GetName() {
            return _name;
        }
    }
 
    public class Site {
        Customer _customer;
 
        public Site() {
        }
 
        public Site(Customer customer) {
            _customer = customer;
        }
 
        public Customer GetCustomer() {
            return _customer;
        }
    }


有Customer 和 Site  两个类,Site类的GetCustomer会返回一个Customer对象,但假如实例化Site对象时使用无参数构造函数,GetCustomer将返回一个空引用。

 

        Site site = new Site();
        Customer customer = site.GetCustomer();
        String name = customer == null ? "guest" : customer.GetName();
        System.out.println(name);


像这种方式使用那两个类,在调用GetName方法时, 除非进行是否为空校验,否则程序会抛出空引用异常。 假如site对象和它的GetCustomer会被频繁的调用,将是难以忍受的,因为customer == null 这样的条件判断会充斥在项目代码的各处。

 

我们可以引入一个“空”对象来改善这个问题

   

 public class Customer {
        String _name;
 
        public Customer() { }
 
        public Customer(String name) {
            _name = name;
        }
 
        public String GetName() {
            return _name;
        }
 
 
        public boolean IsNull()
        {
            return false;
        }
    }
 
    public class NullCustomer extends Customer {
        public NullCustomer() {}
 
        @Override
        public boolean IsNull() {
            return true;
        }
 
        @Override
        public String GetName() {
            return "guest";
        }
    }
 
    public static class Site {
        Customer _customer;
 
        public Site() {
        }
 
        public Site(Customer customer) {
            _customer = customer;
        }
 
        public Customer GetCustomer() {
            return _customer == null ? new NullCustomer(): _customer;
        }
    }


Customer类添加了一个IsNull的实例方法。

 

Site 类的GetCustomer方法内部进行了_customer 成员是否为空的判断,这其实就是把原来在外面的空引用判断提取到了类的内部,把逻辑给封装了起来。

 

与此同时, 我们引入了NullCustomer类型,它继承至Customer,是一个Customer的特例,表示Site对象中_customer成员为空的情况,替代它非空时的行为, 这正如他的命名NullCustomer。

 

原本调用Site对象GetCustomer有可能返回的null值被NullCustomer类的实例所代替, 这样代码的外部可以放心的使用GetCustomer的返回值,不用再提心吊胆的生怕返回空值,也不用做是否为空的判断。

   

public static void main(String[] args) {
        Site site = new Site();
        Customer customer = site.GetCustomer();
        String name = customer.GetName();
        System.out.println(name);
    }



如果要确定GetCustomer的返回值是否为空的情况,可用调用customer对象的IsNull方法


        Customer customer = site.GetCustomer();
        if(customer.IsNull()) {
            
        }



因为不管是Customer 类还是NullCustomer都实现了这个方法, 并且Customer的IsNull方法返回的false, NullCustomer类重写了重Customer继承来的IsNull方法,并返回为true,这种利用多态来面向接口编程的方式,正好满足了我们的需求。

 

总而言之,引用“空”对象可以很好的解决空引用这个牛皮癣似的问题。然而,引入这个机制还需要跟代码的实际情况结合,假如某个对象为空的情况只出现有限的几次,那引入这种机制显得有些杀鸡用牛刀的味道了,使用是否为空判断反而更加轻松;当某个对象是否为空的判断频繁的出现在代码之中, 那么使用“空”对象来代替if判断才有实际的意义。


作者:陈大侠
日期:2018-02-02

留言(0条)

我要发表留言

您的大名 选填
电子邮箱 选填

欢迎关注微信公众号 「带你撸出一手好代码」

首页    GitHub 知乎 豆瓣 博客园