null值判断以及空指针异常应该是我们在代码中经常遇到的。针对null值的处理有两种:

(1)将null值替换为null对象(本质上,是利用多态)

(2)利用Java 8 的Optional对象

首先,看下方法将null值替换为null对象如何实现?

举个栗子:

一家公用公司的系统以Site表示地点(场所),顾客的信息以Customer表示,PaymentHistory表示顾客的付款记录,BillingPlan表示账单。

重构前的代码如下:


package com.tim;

/**
 * 通常我们代码是这么写的
 * @date   2017年7月13日
 */
public class Site {
    
    /**
     * 顾客类
     * @date   2017年7月13日
     */
    private static class Customer{
        private String _name;
        private BillingPlan _plan;
        private PaymentHistory _history;
        
        public BillingPlan getPlan(){
            return _plan;
        }
        public String getName(){
            return _name;
        }
        public PaymentHistory getHistory(){
            return _history;
        }
    }
    
    /**
     * 账单类 
     * @date   2017年7月13日
     */
    private static class BillingPlan{
        public static BillingPlan basic(){
            return new BillingPlan();
        }
    }
    
    /**
     * 付款计划类
     * @date   2017年7月13日
     */
    private static class PaymentHistory{
        private int _weeksDelinquentInLastYear;
        public int getWeeksDelinquentInLastYear(){
            return _weeksDelinquentInLastYear;
        }
    }

    private Customer _customer;
    
    Customer getCustomer(){
        return _customer;
    }
    
   /**
    * 业务代码
    * @date   2017年7月13日
    */
    public static void doSomething(){
        Site site = new Site();
        Customer customer = site.getCustomer();
        BillingPlan plan;
        //代码某处用到
        if(customer == null ){
            plan = BillingPlan.basic();
        }else{
            plan = customer.getPlan();
        }
        //代码某处用到
        String customerName;
        if(customer == null){
            customerName = "occupant";
        }else{
            customerName = customer.getName();
        }
        //代码某处用到
        int weeksDelingquent;
        if(customer == null || customer.getHistory() == null){
            weeksDelingquent = 0;
        }else{
            weeksDelingquent = customer.getHistory().getWeeksDelinquentInLastYear();
        }
        System.err.println(customerName);
        System.err.println(weeksDelingquent);
    }
    
    public static void main(String[] args) {
        doSomething();
    }
}

可以看到,在业务方法代码doSomething()中存在多处判断对象是否为null,如:customer == null 或 customer.getHistory(),造成代码很冗余。

重构方法一:利用null对象替换null值,重构之后的代码如下:

package com.tim;
/**重构方法一
 * 利用:null对象替换null值(本质上是多态)
 * @date   2017年7月13日
 */
public class SiteV2 {

    /**
     * 顾客类空对象
     * @date   2017年7月13日
     */
    private static class NullCustomer extends Customer{
        public boolean isNull(){
            return true;
        }
        public BillingPlan getPlan(){
            return BillingPlan.basic(); //针对null的处理
        }
        public String getName(){
            return "occupant";
        }
        public PaymentHistory getHistory(){
            return PaymentHistory.newNull();
        }
    }
    
    /**
     * 顾客类 
     * @date   2017年7月13日
     */
    private static class Customer{
        private String _name;
        private BillingPlan _plan;
        private PaymentHistory _history;
        
        public BillingPlan getPlan(){
            return _plan;
        }
        public String getName(){
            return _name;
        }
        public PaymentHistory getHistory(){
            return _history;
        }
        public boolean isNull(){
            return false;
        }
        static Customer newNull(){
            return new NullCustomer();
        }
    }
    
    /**
     * 账单类 
     * @date   2017年7月13日
     */
    private static class BillingPlan{
        public static BillingPlan basic(){
            return new BillingPlan();
        }
    }
    
    /**
     * 付款计划类 空对象
     * @date   2017年7月13日
     */
    private static class NullPaymentHistory extends PaymentHistory{
        public int getWeeksDelingquentInLastYear(){
            return 0;
        }
    }
    
    /**
     * 付款计算类
     * @date   2017年7月13日
     */
    private static class PaymentHistory{
        private int _weeksDelinquentInLastYear;
        public int getWeeksDelinquentInLastYear(){
            return _weeksDelinquentInLastYear;
        }
        static NullPaymentHistory newNull(){
            return new NullPaymentHistory();
        }
    }
    
    private Customer _customer;
    
    Customer getCustomer(){
        return _customer==null?Customer.newNull():_customer;
    }
    
    /**
     * 业务代码
     * @date   2017年7月13日
     */
    public static void doSomething(){
        SiteV2 site = new SiteV2();
        Customer customer = site.getCustomer();
        BillingPlan plan = customer.getPlan();
        String customerName = customer.getName();
        int weeksDelingquent = customer.getHistory().getWeeksDelinquentInLastYear();
        System.err.println(customerName);
        System.err.println(weeksDelingquent);
    }
    
    public static void main(String[] args) {
        doSomething();
    }
    
}

分析:

重构之后,在业务代码中对于null的处理都移到相应类的空对象中处理,因此,业务代码中只需要一行,如:BillingPlanplan = customer.getPlan();(一行代码轻松搞定)此时,无需再对customer进行null判断,因为如果customer是null时,会进行相应的null处理。

本质上,利用多态,你不必再向对象询问“你是什么类型(即使是null,我们也有相应的null对象进行相应的处理)”而后根据得到的答案调用对象的某个行为,你只管调用该行为就是了,其他的一切多态机制会为你安排妥当。

重构方法二:利用Java 8 Optional对象对null值进行重构:

package com.tim;
import java.util.Optional;
/**重构方法二:
 * 利用:Java 8 的Optional进行null值的封装
 * @date   2017年7月13日
 */
public class SiteV3 {

    /**
     * 顾客类
     * @date   2017年7月13日
     */
    private static class Customer{
        private String _name;
        private BillingPlan _plan;
        private PaymentHistory _history;
        
        public Optional<BillingPlan> getPlan(){
            return Optional.ofNullable(_plan);
        }
        public String getName(){
            return _name;
        }
        public Optional<PaymentHistory> getHistory(){
            return Optional.ofNullable(_history);
        }
        
    }
    
    /**
     * 账单类
     * @date   2017年7月13日
     */
    private static class BillingPlan{
        public static BillingPlan basic(){
            return new BillingPlan();
        }
    }
    
    /**
     * 付款计划类
     * @date   2017年7月13日
     */
    private static class PaymentHistory{
        private int _weeksDelingquentInLastYear;
        public int getWeeksDelingquentInLastYear(){
            return _weeksDelingquentInLastYear;
        }
    }
    
    private Customer _customer;
    
    Optional<Customer> getCustomer(){
        return Optional.ofNullable(_customer);
    }
    
    /**
     * 业务代码类
     * @date   2017年7月13日
     */
    public static void doSomething(){
        SiteV3 site = new SiteV3();
        Optional<Customer> customer = site.getCustomer();
        BillingPlan plan = customer.flatMap(c->c.getPlan()).orElse(BillingPlan.basic());
        String customerName = customer.map(c->c.getName()).orElse("occupant");
        int weeksDelingquent = customer.flatMap(c->c.getHistory()).map(h->h.getWeeksDelingquentInLastYear()).orElse(0);
        System.err.println(customerName);
        System.err.println(weeksDelingquent);
    }
    
    public static void main(String[] args) {
        doSomething();
    }
}

分析:

利用Java 8的Optional对象重构相比重构方法一中利用null对象替换null值,代码长度更短,由于对于空情况的判断只通过Optional来实现,无需重构方法一中重新编写null对象,最后在调用值的时候也只是流式处理,也只需要一行代码就可获取所需的值,无需做null判断。