NullObject模式

对于项目中无数次的判空,对代码质量整洁度产生了十分之恶劣的影响,对于这种现象,我们称之为“判空灾难”。

那么,这种现象如何治理呢,你可能听说过NullObject模式,不过这不是我们今天的武器,但是还是需要介绍一下NullObject模式。

什么是NullObject模式呢?

以上解析来自Wikipedia。

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

示例代码如下(命名来自网络,哈哈到底是有多懒):

Nullable是空对象的相关操作接口,用于确定对象是否为空,因为在空对象模式中,对象为空会被包装成一个Object,成为Null Object,该对象会对原有对象的所有方法进行空实现…

public interface Nullable {      boolean isNull(); }
登录后复制

这个接口定义了业务对象的行为。

 
登录后复制
public interface DependencyBase extends Nullable {      void Operation();  }
登录后复制

这是该对象的真实类,实现了业务行为接口DependencyBase与空对象操作接口Nullable。

public class Dependency implements DependencyBase, Nullable {      @Override     public void Operation() {         System.out.print("Test!");     }      @Override     public boolean isNull() {         return false;     }  }
登录后复制

这是空对象,对原有对象的行为进行了空实现。

public class NullObject implements DependencyBase{      @Override     public void Operation() {         // do nothing     }      @Override     public boolean isNull() {         return true;     }  }
登录后复制

在使用时,可以通过工厂调用方式来进行空对象的调用,也可以通过其他如反射的方式对对象进行调用(一般多耗时几毫秒)在此不进行详细叙述。

public class Factory {      public static DependencyBase get(Nullable dependencyBase){         if (dependencyBase == null){             return new NullObject();         }         return new Dependency();     }  }
登录后复制

这是一个使用范例,通过这种模式,我们不再需要进行对象的判空操作,而是可以直接使用对象,也不必担心NPE(NullPointerException)的问题。

public class Client {      public void test(DependencyBase dependencyBase){         Factory.get(dependencyBase).Operation();     }  }
登录后复制
.NR Null Object

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

  1. 分析所选类可声明为接口的方法;

  2. 抽象出公有接口;

  3. 创建空对象,自动实现公有接口;

  4. 对部分函数进行可为空声明;

  5. 可追加函数进行再次生成;

  6. 自动的函数命名规范

让我们来看一个使用范例:

Java判空如何实现-LMLPHP

怎么样,看起来是不是非常快速便捷,只需要在原有需要进行多次判空的对象中,邮件弹出菜单,选择Generate,并选择NR Null Object即可自动生成相应的空对象组件。

那么如何来获得这款插件呢?
安装方式

可以直接通过IDEA的Preferences中的Plugins仓库进行安装。

Java判空如何实现-LMLPHP

搜索“NR Null Oject”或者“Null Oject”进行模糊查询,点击右侧的Install,restart IDEA即可。

Java判空如何实现-LMLPHP

Optional

还有一种方式是使用Java8特性中的Optional来进行优雅地判空,Optional来自官方的介绍如下:

一个可能包含也可能不包含非null值的容器对象。如果存在值,isPresent()将返回true,get()将返回该值。

话不多说,举个例子。

有如下代码,需要获得Test2中的Info信息,但是参数为Test4,我们要一层层的申请,每一层都获得的对象都可能是空,最后的代码看起来就像这样。

public String testSimple(Test4 test) {        if (test == null) {            return "";        }        if (test.getTest3() == null) {            return "";        }        if (test.getTest3().getTest2() == null) {            return "";        }        if (test.getTest3().getTest2().getInfo() == null) {            return "";        }        return test.getTest3().getTest2().getInfo();    }
登录后复制

但是使用Optional后,整个就都不一样了。

public String testOptional(Test test) {         return Optional.ofNullable(test).flatMap(Test::getTest3)                 .flatMap(Test3::getTest2)                 .map(Test2::getInfo)                 .orElse("");     }
登录后复制

1、Optional.ofNullable(test),如果test为空,则返回一个单例空Optional对象,如果非空则返回一个Optional包装对象,Optional将test包装;

public static <T> Optional<T> ofNullable(T value) {         return value == null ? empty() : of(value);     }
登录后复制

2、flatMap(Test::getTest3)判断test是否为空,如果为空,继续返回第一步中的单例Optional对象,否则调用Test的getTest3方法;

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {         Objects.requireNonNull(mapper);         if (!isPresent())             return empty();         else {             return Objects.requireNonNull(mapper.apply(value));         }     }
登录后复制

3、flatMap(Test3::getTest2)同上调用Test3的getTest2方法;

4、map(Test2::getInfo)同flatMap类似,但是flatMap要求Test3::getTest2返回值为Optional类型,而map不需要,flatMap不会多层包装,map返回会再次包装Optional;

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {        Objects.requireNonNull(mapper);        if (!isPresent())            return empty();        else {            return Optional.ofNullable(mapper.apply(value));        }    }
登录后复制

5、orElse("");获得map中的value,不为空则直接返回value,为空则返回传入的参数作为默认值。

public T orElse(T other) {     return value != null ? value : other; }
登录后复制

怎么样,使用Optional后我们的代码是不是瞬间变得非常整洁,或许看到这段代码你会有很多疑问,针对复杂的一长串判空,Optional有它的优势,但是对于简单的判空使用Optional也会增加代码的阅读成本、编码量以及团队新成员的学习成本。毕竟Optional在现在还并没有像RxJava那样流行,它还拥有一定的局限性。

如果直接使用Java8中的Optional,需要保证安卓API级别在24及以上。

Java判空如何实现-LMLPHP

你也可以直接引入Google的Guava。(啥是Guava?来自官方的提示)

引用方式,就像这样:

dependencies {       compile 'com.google.guava:guava:27.0-jre'       // or, for Android:       api 'com.google.guava:guava:27.0-android'     }
登录后复制

不过IDEA默认会显示黄色,提示让你将Guava表达式迁移到Java Api上。

Java判空如何实现-LMLPHP

当然,你也可以通过在Preferences搜索"Guava"来Kill掉这个Yellow的提示。

Java判空如何实现-LMLPHP

使用Optional具有如下优点:
  1. 将防御式编程代码完美包装

  2. 链式调用

  3. 有效避免程序代码中的空指针

但是也同样具有一些缺点:
  1. 流行性不是非常理想,团队新成员需要学习成本

  2. 安卓中需要引入Guava,需要团队每个人处理IDEA默认提示,或者忍受黄色提示

当然,Kotlin以具有优秀的空安全性为一大特色,并可以与Java很好的混合使用,like this:

test1?.test2?.test3?.test4
登录后复制

以上就是Java判空如何实现的详细内容,更多请关注Work网其它相关文章!

08-28 06:56