开发面向接口或者提供接口中如何处理空值原创
金蝶云社区-DC_W
DC_W
2人赞赏了该文章 851次浏览 未经作者许可,禁止转载编辑于2021年12月01日 14:30:51

如果存在一个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();


赞 2