返回null还是empty

第一个问题,函数是应当返回null还是长度为0的数组(或集合)?
第二个问题,函数输入参数不当时,是异常还是返回null?

先看第一个问题

有两个约定我觉得应当遵守:

1.返回零长度的数组或集合而不是null(详见《Effective Java》)

理由就是,如果返回empty,就可以少了很多not-null判断:

List<Person> list = queryPerson();
if (list != null) {
for (Person p : list) {
//do something
}
}

如果queryPerson永不返回null,那代码可以这样:
	
List<Person> list = queryPerson();
for (Person p : list) {
//do something
}


遵守这个规则的一个例子就是Spring JdbcTemplate的query方法,在查询不到结果时,返回的是empty List:
RowMapperResultSetExtractor:

public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());
int rowNum = 0;

while (rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}


2. 不要区分null引用和empty值

引用:

7. 降低修改时的误解性,不埋雷
……
一个原则就是永远不要区分null引用和empty值。

详见[url]http://javatar.iteye.com/blog/1056664[/url]

但现实世界没那么理想

比如Spring,在这两个规则上,都没有统一的处理方式

org.springframework.util.StringUtils:

public static String[] tokenizeToStringArray(
String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

if (str == null) {
return null;
}
}

public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {

if (str == null) {
return new String[0];
}
}

两个功能相似的函数,在str==null时,一个返回null,一个返回empty
同一个class中都不统一,很奇怪

顺便说一下这两个函数的最大不同:

String str = "a,b;c;,d";
String delimiter = ";,";

//同时把";"和","当作分隔符
String[] sa = StringUtils.tokenizeToStringArray(str, delimiter);
assertEquals(4, sa.length);
assertEquals("a", sa[0]);
assertEquals("b", sa[1]);
assertEquals("c", sa[2]);
assertEquals("d", sa[3]);

//认为";,"是一个分隔符
sa = StringUtils.delimitedListToStringArray(str, delimiter);
assertEquals(2, sa.length);
assertEquals("a,b;c", sa[0]);
assertEquals("d", sa[1]);


Apache StringUtils也有类似的方法,对应关系如下:

Apache Spring
---------------------------------------------------------
split tokenizeToStringArray
splitByWholeSeparator delimitedListToStringArray

两个函数对str==null的处理是统一了,但它认为null和""是不同的情况:

public static String[] split(String str, String separatorChars) {
return splitWorker(str, separatorChars, -1, false);
}
private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) {

if (str == null) {
return null;
}
int len = str.length();
if (len == 0) {
return ArrayUtils.EMPTY_STRING_ARRAY; //that is, new String[0]
}
}
//splitByWholeSeparatorWorker在这方面跟split一样


再看看Apache ArrayUtils:

public static int[] toPrimitive(Integer[] array) {
if (array == null) {
return null;
} else if (array.length == 0) {
return EMPTY_INT_ARRAY;
}
}

同样是区分了null和empty

所以,保险起见,对数组和集合还是要加上not-null判断,虽然代码丑了一点;除非代码都是你自己写的或者你看过源码了确保永不返回null

第二个问题

Apache FileUtils和IOUtils,对输入参数为null的处理,很多时候是抛出异常,但有时也会“静静的失败”

Apache FileUtils:

//抛出异常
public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
if (srcDir == null) {
throw new NullPointerException("Source must not be null");
}
if (srcDir.exists() && srcDir.isDirectory() == false) {
throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
}
//...
}

//注意到这个方法没有考虑file==null的情况
public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append)
throws IOException {
FileOutputStream out = null;
try {
out = openOutputStream(file, append);
final BufferedOutputStream buffer = new BufferedOutputStream(out);
IOUtils.writeLines(lines, lineEnding, buffer, encoding);
buffer.flush();
out.close(); // don't swallow close Exception if copy completes normally
} finally {
IOUtils.closeQuietly(out);
}
}

//what if "file==null" ?
public static FileOutputStream openOutputStream(File file, boolean append) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
//...
}
return new FileOutputStream(file, append);
}


Apache IOUtils:

//不抛异常
public static void writeLines(Collection<?> lines, String lineEnding, OutputStream output, Charset encoding)
throws IOException {

if (lines == null) {
return;
}
//...
}


Apache BeanUtils:

//不抛异常
public static void populate(Object bean, Map properties)
throws IllegalAccessException, InvocationTargetException
{
BeanUtilsBean.getInstance().populate(bean, properties);
}

public void populate(Object bean, Map properties)
throws IllegalAccessException, InvocationTargetException {

// Do nothing unless both arguments have been specified
if ((bean == null) || (properties == null)) {
return;
}
//...
}


看来这个问题也是见仁见智
如果你认为NullPointerException或者IllegalArgumentException有必要通知调用方,那就抛异常,特别是调用方想知道为什么调用失败:
e.g. File存在但没有写权限
e.g. File本应该是文件但传入的是目录

个人总结:
应该采取“防御式编程”,怀疑一切
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值