返回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值。

详见 http://javatar.iteye.com/blog/1056664

但现实世界没那么理想

比如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本应该是文件但传入的是目录

个人总结:
应该采取“防御式编程”,怀疑一切

你可能感兴趣的:(java,apache,spring,编程)