如果存在一个UserService用来提供用户查询的功能场景:
public interface UserService{ List<User> listUser(); User get(Integer id); }
对于以上描述的接口方法来看,大概可以推断出可能它包含了以下两个含义:
listUser(): 查询用户列表
get(Integer id): 查询单个用户
对于以上的接口,发现了潜在的问题:
listUser() 如果没有数据,那它是返回空集合还是null呢?
get(Integer id) 如果没有这个对象,是抛异常还是返回null呢?
我们先来讨论listUser(),通常实现会如下:
public List<User> listUser(){ List<User> userList = selectByExample(new UserExample());//查询用户列表 if(CollectionUtils.isEmpty(userList)){//工具类 return null; } return userList; }
对于集合这样返回值,最好不要返回null,因为如果返回了null,会给调用者带来很多麻烦。将会把这种调用风险交给调用者来控制。
将它进行优化:
对于接口(List listUser()),它一定会返回List,即使没有数据,它仍然会返回List(集合中没有任何元素)
public List<User> listUser(){ List<User> userList = selectByExample(new UserExample());//查询用户列表 if(CollectionUtils.isEmpty(userList)){ return Lists.newArrayList();//guava类库提供的方式 } return userList; }
对于接口User get(Integer id)大部分实现会如下:
public User get(Integer id){ return selectByPrimaryKey(id);//从数据库中通过id直接获取实体对象 }
通过代码的时候得知它的返回值很有可能是null! 但我们通过的接口是分辨不出来的!
好一点的方式是在接口明明时补充文档,比如对于异常的说明,使用注解@exception
我们把接口定义加上了说明之后,调用者会看到,如果调用此接口,很有可能抛出“UserNotFoundException(找不到用户)”这样的异常。
这种方式可以在调用者调用接口的时候看到接口的定义,但是,这种方式是”弱提示”的!
可以引入jdk8的Optional,或者使用guava的Optional.
public interface UserService{ /** * 根据用户id获取用户信息 * @param id 用户id * @return 用户实体,此实体有可能是缺省值 */ Optional<User> getOptional(Integer id); } //它的实现可以写成: public Optional<User> getOptional(Integer id){ return Optional.ofNullable(selectByPrimaryKey(id)); }
接下来,关注入参
通过上述的所有接口的描述,你能确定入参id一定是必传的吗?不能确定。除非接口的文档注释上加以说明。
如何约束入参呢?推荐两种方式:1.强制约束2.文档性约束(弱提示)
为了避免空指针调用,我们经常会看到这样的语句
if (id != null) {
//dosomething();
}
最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?
进行判空前,请区分以下两种情况:
1.null是无效有误的:
null就是一个不合理的参数,就应该明确地中断程序,往外抛错误。这种情况常见于api方法。
2.null 是一个有效有意义的返回值:
这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。
假如方法的返回类型是collections,当返回结果是空时,你可以返回一个空的collections(empty list),而不要返回null.这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接print list.size(),又无需担心空指针问题。
下面举个例子,假设有如下代码
public interface Action { void doSomething(); } public interface Parser { Action findAction(String userInput); }
其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。
传统冗余模式:
Parser parser = getParser(); if (parser == null) { // 获取结果为空的执行逻辑 } Action action = parser.findAction(someInput); if (action == null) { // 找不到action处理逻辑 } else { //找到action的处理逻辑 action.doSomething(); }
来改造一下,类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象:
public class MyParser implements Parser { private static Action DO_NOTHING = new Action() { public void doSomething() {}//什么都不做 }; public Action findAction(String userInput) { // ... // 正常逻辑 if ( /* 找不到userInput逻辑 */ ) { return DO_NOTHING; } } }
无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。
MyParser parser = getParser(); parser.getParser().findAction(someInput).doSomething();
推荐阅读