Java常见重构技巧,去除!=null判空的5种思路,第4和第5种知道的开发者不多

Java常见重构技巧 - 去除不必要的!=

项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?@pdai

场景一:null无意义之常规判断空

  • 通常是这样的
private void xxxMethod(String key){
    if(key!=null&&!"".equals(key)){
        // do something
    }
}
  • 初步的,使用Apache Commons,Guvava, Hutool等StringUtils
private void xxxMethod(String key){
    if(StringUtils.isNotEmpty(key)){
        // do something
    }
}

场景二:null无意义之使用断言Assert

  • 考虑用Assert断言
private void xxxMethod(String key){
    Assert.notNull(key);

    // do something
}

场景三:写util类是否都需要逐级判断空

逐级判断空,还是抛出自定义异常,还是不处理?It Depends…

随手翻了下,hutool IdcardUtil 显然是交给调用者判断的。

/**
    * 是否有效身份证号
    *
    * @param idCard 身份证号,支持18位、15位和港澳台的10位
    * @return 是否有效
    */
public static boolean isValidCard(String idCard) {
    idCard = idCard.trim();// 这里idCard没判断空
    int length = idCard.length();
    switch (length) {
        case 18:// 18位身份证
            return isValidCard18(idCard);
        case 15:// 15位身份证
            return isValidCard15(idCard);
        case 10: {// 10位身份证,港澳台地区
            String[] cardVal = isValidCard10(idCard);
            return null != cardVal && "true".equals(cardVal[2]);
        }
        default:
            return false;
    }
}
  • 再比如 Apache Common IO中, 并没判断空
/**
    * Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
    * @param input the byte array to read from
    * @param output the <code>OutputStream</code> to write to
    * @throws IOException In case of an I/O problem
    */
public static void copy(final byte[] input, final OutputStream output)
        throws IOException {
    output.write(input);
}

场景四:让null变的有意义

返回一个空对象(而非null对象),比如NO_ACTION是特殊的Action,那么我们就定义一个ACTION。下面举个“栗子”,假设有如下代码

public interface Action {
  void doSomething();}

public interface Parser {
  Action findAction(String userInput);
}

其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。

解决这个问题的一个方式,就是使用Null Object pattern(空对象模式)

NullObject模式首次发表在“ 程序设计模式语言 ”系列丛书中。一般的,在面向对象语言中,对对象的调用前需要使用判空检查,来判断这些对象是否为空,因为在空引用上无法调用所需方法。

我们来改造一下

类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象:

public class MyParser implements Parser {
  private static Action NO_ACTION = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return NO_ACTION;
    }
  }
}

对比下面两份调用实例

1.冗余: 每获取一个对象,就判一次空

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing} 
else {
  action.doSomething();
}

2.精简

ParserFactory.getParser().findAction(someInput).doSomething();

因为无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。

顺便再提下一个插件:

.NR Null Object插件
NR Null Object是一款适用于Android Studio、IntelliJ IDEA、PhpStorm、WebStorm、PyCharm、RubyMine、AppCode、CLion、GoLand、DataGrip等IDEA的Intellij插件。其可以根据现有对象,便捷快速生成其空对象模式需要的组成成分,其包含功能如下:

  • 分析所选类可声明为接口的方法;
  • 抽象出公有接口;
  • 创建空对象,自动实现公有接口;
  • 对部分函数进行可为空声明;
  • 可追加函数进行再次生成;
  • 自动的函数命名规范

场景五:Java8中使用Optional

假设我们有一个像这样的类层次结构:

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

解决这种结构的深层嵌套路径是有点麻烦的。我们必须编写一堆 null 检查来确保不会导致一个 NullPointerException:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

我们可以通过利用 Java 8 的 Optional 类型来摆脱所有这些 null 检查。map 方法接收一个 Function 类型的 lambda 表达式,并自动将每个 function 的结果包装成一个 Optional 对象。这使我们能够在一行中进行多个 map 操作。Null 检查是在底层自动处理的。

Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

还有一种实现相同作用的方式就是通过利用一个 supplier 函数来解决嵌套路径的问题:

Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
    .ifPresent(System.out::println);

调用 obj.getNested().getInner().getFoo()) 可能会抛出一个 NullPointerException 异常。在这种情况下,该异常将会被捕获,而该方法会返回 Optional.empty()。

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

请记住,这两个解决方案可能没有传统 null 检查那么高的性能。不过在大多数情况下不会有太大问题。

  • 更多Optional,可以看这篇: Java 8 - Optional类
    • Optional类的意义
    • Optional类有哪些常用的方法
    • Optional举例贯穿所有知识点
    • 多重类嵌套Null值判断

更多资料

Java 全栈知识体系 400+文章带你构筑Java开发体系

给这个方法添加单元测试: public String cloneMessage(CisMonitorData data){ log.info("Starting process to clone and publish message to queue for request id {}", data.getTdsStpId()); CisPubTds cisPubTds = monitorDao.getTdsPubRequestById(data.getTdsStpId()); if(cisPubTds == null){ return CisTStpConstants.MSG_1; } if(cisPubTds.getMessage() == null){ return CisTStpConstants.MSG_2; } if(data.getUser() == null){ return CisTStpConstants.MSG_18; } if(data.getComment() != null && data.getComment().length() > 250){ return CisTStpConstants.MSG_19; } String status = null; try { CisPubTds clonePubTds = (CisPubTds)cisPubTds.clone(); clonePubTds.setTdsStpId(null); clonePubTds.setCreatedDate(new Date()); clonePubTds.setCreatedBy(data.getUser()); clonePubTds.setIsStpEd(0); clonePubTds.setStpEdAt(null); clonePubTds.setUserComment(data.getComment()); clonePubTds = monitorDao.saveOrUpdatePubTds(clonePubTds); if(clonePubTds != null && clonePubTds.getTdsStpId() != null && clonePubTds.getTdsStpId() > 0) { log.info("Updating the XML message for cloned request with new TDS STP id : {}", clonePubTds.getTdsStpId()); /Update tracking ID in SCBML message and save it to database/ Document doc = DocumentUtility.StringToDocument(clonePubTds.getMessage()); DocumentUtility.updateNodeValue(doc, clonePubTds.getTdsStpId()); String updatedXml = DocumentUtility.DocumentToString(doc); clonePubTds.setMessage(updatedXml); clonePubTds = monitorDao.saveOrUpdatePubTds(clonePubTds); /End/ log.info("Update of the XML message for cloned request with new TDS STP id : {} is completed", clonePubTds.getTdsStpId()); log.info("Publishing the cloned message"); boolean statusBool = publish(clonePubTds); if(statusBool){ status = CisTStpConstants.MSG_SUCCESS; } log.info("End of publish with statusBool : {}", statusBool); log.info("End of publish with status : {}", status); }else { status = CisTStpConstants.MSG_8; } log.info("End process to clone and publish message to queue for request id {}", data.getTdsStpId()); } catch (CloneNotSupportedException e) { status = CisTStpConstants.MSG_7; log.error("Error while cloning the object {}", e); } catch (Exception e) { log.error("Error while creating object {}", e); } return status; }
06-07
Sure, here's an example of how you can write unit tests for the given method using JUnit: ``` import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.Date; public class ExampleUnitTest { private MonitorDao monitorDao = mock(MonitorDao.class); private ExampleClass exampleClass = new ExampleClass(monitorDao); @Test public void testCloneMessage() throws Exception { // Arrange CisMonitorData data = new CisMonitorData(); data.setTdsStpId(123); data.setUser("Test User"); data.setComment("Test Comment"); CisPubTds cisPubTds = new CisPubTds(); cisPubTds.setTdsStpId(123); cisPubTds.setMessage("Test Message"); when(monitorDao.getTdsPubRequestById(123)).thenReturn(cisPubTds); // Act String result = exampleClass.cloneMessage(data); // Assert assertEquals(CisTStpConstants.MSG_SUCCESS, result); } } ``` In this example, we are using Mockito to mock the `MonitorDao` object. We create a `CisMonitorData` object and set its properties. We also create a `CisPubTds` object and set its properties. We then use the `when` method to mock the `getTdsPubRequestById` method of the `monitorDao` object to return the `cisPubTds` object when called with argument `123`. We then call the `cloneMessage` method of `ExampleClass` with the `data` object as argument and store the result in the `result` variable. Finally, we use the `assertEquals` method to assert that the result is equal to `CisTStpConstants.MSG_SUCCESS`. You can write similar tests for other scenarios, such as when `monitorDao.getTdsPubRequestById` returns null, or when `data.getUser` is null, etc.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值