那些丑陋的Java API

Java语言自从1995年正式推出以来,已经成为被广泛采用的编程语言之一。在 TIOBE编程语言排行榜榜单上也一直保持着前几名的位置。

《Java: The Good Parts / Java语言精粹》一书中,SUN的工程师更是大加赞扬了Java在各个方面的优点,其实Java存在很多Bad Parts需要改善。有时候开发者只是需要一个很简单的支持,但是SUN(如今是Oracle)的大师们却需要我们死很多脑细胞才能实现我们想要的。

Tiago Fernandez曾经做过一个很有意思的投票,统计对Java API的不满意程度,EJB2.x、Date/Time/Calendar、XML/DOM、AWT/Swing、EJB3.x、Mail、Servlet/JSP、I/O等均上榜。让我们来看看这些“丑陋的Java API”吧!

(1)臭名昭著的EJB
这个就不多说了。虽然EJB3.x有所改善,但是开发者更能接受Spring了。

(2)日期处理
Java中的日期处理API是被所有的开发者深恶痛绝的!最初使用java.util.Date实现,包含了日期与时间,但是无法实现国际化。更奇怪的是属性的偏移量,月份与小时都是基于0,月份中的天数则是基于1,而年则是从1900开始的。而随着JDBC的发布,又诞生了一个java.sql.Date,它继承自java.util.Date,但却有不同的含义。IBM在1998年贡献了一个java.util.Calendar,实现了国际化,但却更复杂。

// Return Last Day of a Month "2013/4/8"
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2013);
calendar.set(Calendar.MONTH, 4 - 1);
calendar.set(Calendar.DATE, 8);
calendar.set(Calendar.DATE, calendar.getActualMaximum(Calendar.DATE));
// calendar.add(Calendar.DATE, -1);

Date dd = cal.getTime();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
String lastDay = sdf.format(dd);


2005年,Stephen Colebourne发布Joda-Time库,这个库非常流行并且得到了广泛的使用。Stephen Colebourne领导的JSR 310: Date and Time API已经被添加到了Java 8的特性列表中,期待有所改善吧!

http://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result
http://stackoverflow.com/questions/15629222/java-sql-timestamp-comparison-bug
http://stackoverflow.com/questions/13141123/java-sql-timestamp-created-from-java-util-date-why-always-before-it

(3)异常捕获
比如以下一个简单的反射获取字段值,需要捕获4个不同的异常:
Field field;
try {
	
	field = getClass().getField("testField");
	Object value = field.get(this);
	
} catch (SecurityException e) {
	e.printStackTrace();
} catch (NoSuchFieldException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
}

类似的API包括:反射API、RMI API、JDBC API、 JAXP等Xml相关API、J2EE相关API(JNDI/EJB2/JMS/JavaMail等)等等。
很多开发者不得不捕获所有的Exception和Throwable。

XStream Thoughtworks的开源产品,采用的是xpp来进行xml和Java对象之间的转换。它不需要schema或其他的mapping文件就可以进行java对象和xml文件之间的转换,API调用起来非常方便,并且扩展功能强大。

Java 7 支持多异常捕获:
Field field;
try {
	
	field = getClass().getField("testField");
	Object value = field.get(this);
	
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
	e.printStackTrace();
}


(4)资源释放
在发生异常时,如果想释放资源,我们不得不先做空判断,然后再关闭,而关闭的时候还有可能继续发生异常:
Reader in = null;

try {
    in = new BufferedReader(new FileReader("文件名"));

    
} catch (IOException e) {

} finally {
    if (in != null) {
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


Java 7支持 try-with-resources
private static void customBufferStreamCopy(File source, File target) {
    try (InputStream fis = new FileInputStream(source);
        OutputStream fos = new FileOutputStream(target)){
       
        byte[] buf = new byte[8192];

        int i;
        while ((i = fis.read(buf)) != -1) {
            fos.write(buf, 0, i);
        }
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}


(5)精度计算
在财务计算的系统中经常会使用到精度计算。Java做大数的精度操作需要用到java.math.BigDecimal和java.text.DecimalFormat。在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。

由于Java没有运算符重载的支持,简单的乘法也不得不写成以下这个样子:
BigDecimal num1 = new BigDecimal("123456.789");
BigDecimal num2 = new BigDecimal("111.111");

BigDecimal result = num1.multiply(num2);


Java中long的最大值是2的63次方减1,如果你想定义一个超过int上限的long
long a = 9223372036854775807; // NG [The literal 9223372036854775807 of type int is out of range]


可以加个“L”:
long a = 9223372036854775807L; // OK


同样的有long-L、double-d、float-f。不知道为什么就不支持大数的精度计算呢。

(6)字符串处理
字符串处理应该是程序中使用最频繁的了,但Java提供的String方法太过于局限,虽然有StringTokenizer但是使用太复杂,所以往往我们不得不使用Commons Lang的StringUtils或者去写自己的StringUtils。
java.lang.String / java.util.StringTokenizer
java.lang.StringBuffer / java.lang.StringBuilder

Apache Commons补充了很多Java的不足,很多开源软件都默认依赖它。 http://commons.apache.org/

(7)基本类型和包装类
int/Integer、double/Double等共存,代码中不得不充斥着各种各样的Integer.parseInt()、Integer.valueOf()。而Java5.0开始提供基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing) ,但对于我们经常处理的比如NOT NULL限制的DB字段,很容易发生NullPointerException。

而Map、List、Set集合类中又无法使用基本型,不得不再导入比如:Trove、Google Guava等lib。

(8)数组与集合
比如取个长度,数组用的属性array.length,而集合用的方法list.size()。

泛型:
public class Test<T> {
	public Test() {
		List<T> list = new ArrayList<T>(); // OK
		T[] array = new T[3]; // NG [Cannot create a generic array of T]
	}
}

Scala创始人Martin Odersky说当年正是因为Java泛型的丑陋才创建了Scala。

赋值转换:
String[] strArray = null;
Object[] objArray = null;
List<String> strList = null;
List<Object> objList = null;
	
objArray = strArray; // OK
objList = strList; // NG [Type mismatch: cannot convert from List<String> to List<Object>]


集合转数组:
比如:ArrayList.toArray();
ArrayList<String> stock_list = new ArrayList<String>();
stock_list.add("stock1");
stock_list.add("stock2");

String[] stockArr1 = stock_list.toArray(); // NG [Type mismatch: cannot convert from Object[] to String[]]
String[] stockArr2 = stock_list.toArray(new String[stock_list.size()]); // OK
String[] stockArr3 = stock_list.toArray(new String[0]); // OK
String[] stockArr4 = stock_list.toArray(new String[stock_list.size() + 10]); // OK

需要注意的是stockArr2和stockArr3(重新创建了一个数组返回)能正常返回我们想要的数据,但是stockArr4返回的数组长度为12,除过前两位以外都是null。

集合类不支持基本类型:
还是需要借助Trove、Google Guava等开源代码

(9)日志
Java诞生后不久log4j就发布了,并且得到了广大程序员的支持,但SUN死活不肯接受log4j 非要自己搞个log框架,自JDK 1.4发布java.util.logging日志包一来直到现在Java 1.7,许多开发者依然使用log4j或者通用的日志工具,而不是使用JDK logger。
JSR-047(Logging API Specification)

还有一些正在起草当中的JSR:
JSR 317: Java Persistence 2.0(替代Hibernate)
JSR 107: JCACHE - Java Temporary Caching API(替代EhCache)
JSR 353: JavaTM API for JSON Processing (阿里的温绍锦是该JSR的专家组成员)
......
Java的规范是有名的多而且复杂,有兴趣大家可以看看 http://jcp.org/en/jsr/all,目前都排到JSR 362了。

(10)一些易错的API
比如:
  File.mkdir()方法
  String.equals()方法
  以对象为Key的Map
  ......

当然,任何语言都不会是完美的,编程语言也都在不断的进化当中。Java中也还有很多相当优秀的API,比如,Doug Lea大师的java.util.concurrent包(JSR 166: Concurrency Utilities),就是功能齐全,性能卓越,优秀完美的并发库。互联网进入大数据处理时代,更加突显这个并发库的重要性。

补充:
1:文中不涉及到类似“泛型”等这种语法糖的问题。只探讨那些常用但是又易错,难用的API。
2:文中不涉及类似Java不支持多继承,没有const关键字等语法上的问题。
3:追加一下Joshua Bloch 的《Java Puzzlers: Traps, Pitfalls, and Corner Cases/Java解惑》希望对大家有所帮助!


参考资料:
http://stackoverflow.com/questions/238173/worst-java-practice-found-in-your-experience
http://www.youtube.com/watch?v=hcY8cYfAEwU
http://d.hatena.ne.jp/ryoasai/20110226/1298703499
http://dev.sei.pku.edu.cn/trac/pkuas/blog/2011/04/06/zhanglei09/my_topic
http://www.cnblogs.com/dushu/archive/2013/01/18/2866379.html
http://www.raychase.net/1098

你可能感兴趣的:(java)