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