Java中如何避免麻烦的null值判断

来源:互联网 发布:debian ubuntu 性能 编辑:程序博客网 时间:2024/05/17 07:31

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判断。

补充:关于Java 8中的Optional对null的处理,参考书籍:《Java 8 实战》P203-P219