Java:“命名参数”和“默认参数值”
函数可读性
我们关注下一面例子中的函数可读性。这是一个打印任意集合内容的函数。
/*Java的集合都有一个默认的toString实现,但是它格式化输出是固定的,而且往往不是你需要的样子*/
public static <T> String joinToString(Collection<T> collection, String separator, String prefix, String postfix){ ...}
此函数看起来超级简单,这是一个我们常用的工具类中的一个函数。我们来看看joinToString的调用:
/*Java*/
joinToString(collection, ", ", ", ", ".");
你能看出这些String都对应的是什么参数吗?这个集合的元素是用空格还是点号来分割?如果你不去查看函数的声明,我们很难回答这个问题。或许你记住了这些声明,又或许你可以借助你的IDE,但从调用代码来看,这依然很隐晦。
对于Boolean类型的标志,这个问题尤其明显。为解决这个问题,一些Java编程风格推荐创建enum类型而不是采用Boolean;而另外一些风格,会要求你通过添加注释,在注释中指明参数的名称,像这个样子:
/*Java*/
joinToString(collection, /*separator*/", ", /*prefix*/", ", /*postfix*/".");
通过指明参数名称,函数的可读性增强了,但依然不是很理想。
命名参数
Kotlin等一些语言调用函数时可以显示的指明一些参数,看起来非常优雅,而且可读性非常好,调用如下:
joinToString(collection, separator = " ", prefix = " ", postfix = ".")
你是不是看到这样的代码比看到Java代码顺眼很多呢?这行代码比起Java调用可读性确实非常优秀。
我们再讨论一个问题。
默认参数值
Java的另一个普遍存在的问题是,一些类的重载函数实在太多了。只要看一眼java.lang.Thread以及它对应的8个构造方法就让人受够了!这些重载,原本是为了向后兼容,方便这些API的使用者,又或者处于别的原因,但导致的最终结果是一致的:重复。这些参数名和类型被重复了一遍又一遍,如果你是一个良好公民,还必须在每次重载的时候重复大部分的文档。与此同时,当你省略部分参数的重载函数时,你可能搞不清楚他们到底用的是哪个。
在一些语言中,如Kotlin,可以在声明函数的时候指定参数的默认值,避免了创建重载的函数。我们看一下Kotlin中joinToString是如何声明的:
/*separator ,prifix和postfix是有默认值的参数*/
/*常用情况下字符串可以不加前缀或者后缀并用逗号分隔*/
fun <T> joinToString(collection:Collection<T>,
separator:String = ", ",
prefix:String = " ",
postfix:String = " "
):String {
...
}
函数声明看起来没什么特别的,就是多了个默认值。那我们看看joinToString在Kotlin中的调用:
>>> joinToString(list)
1,2,3
>>> joinToString(list,separator = "; ") /*同 joinToString(list,"; ")*/
1; 2; 3
>>> joinToString(list,separator = "; ", prefix = "[", postfix = "]") /*joinToString(list,"; ","[", "]")*/
[1; 2; 3]
...
你是否发现并没有重载joinToString函数,却达到了重载函数的目的?而且在调用时可以指明参数名称,代码看起来非常的清晰。
我们来讨论讨论这些特性在Java中的实现。
Java中的“命名参数”和“默认参数值”
很不幸,Java中不能采用命名参数也不能采用默认参数值。相当多的Java开发人员已经习惯了函数重载,如果是良好开发者也习惯了重复文档。既然Java不支持这些特性,我们只能从编程风格上做文章来达到我们的目的。Builder Pattern 能解决以上我们的问题。
Builder Pattern
我还是以joinToString为例,用这种模式编写,我不在讲理论了,直接上干货吧。
public final class CollectionUtil {
private CollectionUtil() {
}
public static <T> JoinToStringBuilder join(Collection<T> collection) {
return new JoinToStringBuilder<>(collection);
}
public static final class JoinToStringBuilder<T> {
private final Collection<T> collection;
private String separator = ",";
private String prefix = "";
private String postfix = "";
private JoinToStringBuilder(Collection<T> collection) {
this.collection = collection;
}
public JoinToStringBuilder setSeparator(String separator) {
this.separator = separator;
return this;
}
public JoinToStringBuilder setPrefix(String prefix) {
this.prefix = prefix;
return this;
}
public JoinToStringBuilder setPostfix(String postfix) {
this.postfix = postfix;
return this;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
/*...*/
return result.toString();
}
}
}
我去,代码看起来咋这么长呢?没办法,这是为了提高代码可读性付出的相应代价。
我们看看它的调用:
>>> CollectionUtil.join(list).toString();
1, 2, 3
>>> CollectionUtil.join(list).setSeparator("; ").toString();
1; 2; 3;
>>> CollectionUtil.join(list).setPrefix("[").setPostfix("]").toString();
[1, 2, 3]
你有没有发现代码可读性提高了呢?