0%

【Java小记】Java中带默认值的属性初始化写法的对比

这里笔者想讨论一个有关编程风格的问题。它针对一个很常见的编程场景:初始化属性值,如果已配置该属性,初始化为配置的值,否则使用默认值初始化。

来看一个具体的场景:程序带有截图功能,这需要初始化一个截图目录以存储图片。如果配置指定了截图目录路径,则按配置初始化截图目录,否则初始化为默认目录。

这里假设有两个属性,一个是 captureDir 代表截图目录路径的字符串表示(来源于配置文件),另一个是 captureDirectory 代表截图目录对应的 File 对象。

典型的获取截图目录 File 对象的方法实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public File getCaptureDirectory() {
if (captureDirectory != null) {
return captureDirectory;
}

if (captureDir == DEFAULT_SCREENSHOT_DIR) {
captureDirectory = new File(workDirectory, captureDir);
} else {
captureDirectory = new File(captureDir);
}

if (!captureDirectory.exists()) {
boolean made = captureDirectory.mkdirs();
if (!made) {
String errMsg = "截图文件夹创建失败:" + captureDirectory;
captureDirectory = null;
throw new ConfigException(errMsg);
}
}

return captureDirectory;
}

代码逻辑很简单:判断 captureDirectory 是否已初始化,如是直接返回,否则执行初始化逻辑。由于进行了“非空”判断,姑且叫它“非空拦截法”吧。

反过来,我们也可以“判空”,再执行初始化,叫“判空初始化法”好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public File getCaptureDirectory() {
if (captureDirectory == null) {
if (captureDir == DEFAULT_SCREENSHOT_DIR) {
captureDirectory = new File(workDirectory, captureDir);
} else {
captureDirectory = new File(captureDir);
}

if (!captureDirectory.exists()) {
boolean made = captureDirectory.mkdirs();
if (!made) {
String errMsg = "截图文件夹创建失败:" + captureDirectory;
captureDirectory = null;
throw new ConfigException(errMsg);
}
}
}

return captureDirectory;
}

除此之外,Java8 引入的 Optional 类也很适合处理这种场景——叫“Optional初始化法”吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public File getCaptureDirectory() {
return Optional.ofNullable(captureDirectory)
.orElseGet(
() -> {
if (captureDir == DEFAULT_SCREENSHOT_DIR) {
captureDirectory = new File(workDirectory, captureDir);
} else {
captureDirectory = new File(captureDir);
}

if (!captureDirectory.exists()) {
boolean made = captureDirectory.mkdirs();
if (!made) {
String errMsg = "截图文件夹创建失败:" + captureDirectory;
captureDirectory = null;
throw new ConfigException(errMsg);
}
}

return captureDirectory;
});
}

这样,针对带默认值的属性初始化场景,就有三种编写方法了:

  • 非空拦截法
  • 判空初始化法
  • Optional 初始化法

那用哪个呢?

我们从代码量和缩进风格两个维度来比较一下三个方法。

从代码量上说,三个方法差异不大,从示例看只有两行的差异。

从缩进风格看,非空拦截法完全没有额外缩进,是三者中最“平”的;判空初始化法包围了一层 if 语句,多了一级缩进;Optional 初始化法由于链式调用以及 lambda 表达式又多了一级缩进。

小结

由于代码量差异较小,所以这里单论缩进效果来选择使用哪种方法。

首选当然是“非空拦截法”,它将代码量最小的逻辑分支前置处理,逻辑上更为清晰。

其次“判空初始化法”,它将逻辑分支不同部分“隔离”处理,“共享”相同部分,减少了代码量。

最后“Optional 初始化法”,它的缩进较多,如果主体逻辑代码也有较多的缩进,那就不太美观了。另外,如果 lambda 表达式代码太多,就会显得很混乱。但是,Optional 的链式代码有一个优点,即逻辑语义更为清晰。代码量较少的情况下也是一个不错的选择。