使用“空”对象替代引用是否为空判断
使用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判断才有实际的意义。
- 使用gcc编译gdb调试
- 剑指OFFER之第一个只出现一次的字符(九度OJ1283)
- c++中类长度解析
- 剑指OFFER之丑数(九度OJ1214)
- 剑指OFFER之把数组排成最小的数(九度OJ1504)
- 剑指OFFER之从1到n中出现1的次数(九度OJ1373)
- 剑指OFFER之最大子向量和(连续子数组的最大和)(九度OJ1372)
- 剑指OFFER之最小的K个数(九度OJ1371)
- 剑指OFFER之数组中出现次数超过一半的数字(九度OJ1370)
- 如何成为一名10x的数据分析师?
- 肥料生产商全套三拼在手:拟融资1.47亿元
- 《外媒Cointelegraph专访Qtum量子链Jordan Earls》—浅谈目前Dapps的主要问题
- ASP .Net Core 2.0 修改默认端口
- 不满一岁的潜力股:2017年3D打印机器人盘点
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python第四十四课——继承性之单继承
- python第四十五课——继承性之多重继承
- Linux系统——keepalived高可用集群服务
- python第四十五课——继承性之多继承
- HTTP服务器Nginx服务介绍续
- python第四十六课——函数重写
- Linux系统Memcached服务介绍
- python第四十七课——类属性和函数属性
- python第四十八课——类函数和对象函数
- python第四十九课——对象序列化与反序列化
- python第五十课——多态性
- python第五十一课——__slots
- Linux系统安全配置iptables服务介绍
- ThreadLocal企业中真实应用
- python第五十二课--自定义异常类