不必要的同步
错误的写法:
- Collection l = new Vector();
- for (...) {
- l.add(object);
- }
Vector是ArrayList同步版本。
正确的写法:
- Collection l = new ArrayList();
- for (...) {
- l.add(object);
- }
错误的选择List类型
根据下面的表格数据来进行选择
ArrayList LinkedList add (append) O(1) or ~O(log(n)) if growing O(1) insert (middle) O(n) or ~O(n*log(n)) if growing O(n) remove (middle) O(n) (always performs complete copy) O(n) iterate O(n) O(n) get by index O(1) O(n)
HashMap size陷阱
错误的写法:
- Map map = new HashMap(collection.size());
- for (Object o : collection) {
- map.put(o.key, o.value);
- }
这里可以参考guava的Maps.newHashMapWithExpectedSize的实现. 用户的本意是希望给HashMap设置初始值, 避免扩容(resize)的开销. 但是没有考虑当添加的元素数量达到HashMap容量的75%时将出现resize。
正确的写法:
- Map map = new HashMap(1 + (int) (collection.size() / 0.75));
对Hashtable, HashMap 和 HashSet了解不够
这里主要需要了解HashMap和Hashtable的内部实现上, 它们都使用Entry包装来封装key/value, Entry内部除了要保存Key/Value的引用, 还需要保存hash桶中next Entry的应用, 因此对内存会有不小的开销, 而HashSet内部实现其实就是一个HashMap. 有时候IdentityHashMap可以作为一个不错的替代方案. 它在内存使用上更有效(没有用Entry封装, 内部采用Object[]). 不过需要小心使用. 它的实现违背了Map接口的定义. 有时候也可以用ArrayList来替换HashSet.
这一切的根源都是由于JDK内部没有提供一套高效的Map和Set实现。
对List的误用
建议下列场景用Array来替代List:
- list长度固定,比如一周中的每一天
- 对list频繁的遍历,比如超过1w次
- 需要对数字进行包装(主要JDK没有提供基本类型的List)
比如下面的代码。
错误的写法:
- List<Integer> codes = new ArrayList<Integer>();
- codes.add(Integer.valueOf(10));
- codes.add(Integer.valueOf(20));
- codes.add(Integer.valueOf(30));
- codes.add(Integer.valueOf(40));
正确的写法:
- int[] codes = { 10, 20, 30, 40 };
错误的写法:
- // horribly slow and a memory waster if l has a few thousand elements (try it yourself!)
- List<Mergeable> l = ...;
- for (int i=0; i < l.size()-1; i++) {
- Mergeable one = l.get(i);
- Iterator<Mergeable> j = l.iterator(i+1); // memory allocation!
- while (j.hasNext()) {
- Mergeable other = l.next();
- if (one.canMergeWith(other)) {
- one.merge(other);
- other.remove();
- }
- }
- }
正确的写法:
- // quite fast and no memory allocation
- Mergeable[] l = ...;
- for (int i=0; i < l.length-1; i++) {
- Mergeable one = l[i];
- for (int j=i+1; j < l.length; j++) {
- Mergeable other = l[j];
- if (one.canMergeWith(other)) {
- one.merge(other);
- l[j] = null;
- }
- }
- }
实际上Sun也意识到这一点, 因此在JDK中, Collections.sort()就是将一个List拷贝到一个数组中然后调用Arrays.sort方法来执行排序。
用数组来描述一个结构
错误用法:
- /**
- * @returns [1]: Location, [2]: Customer, [3]: Incident
- */
- Object[] getDetails(int id) {...
这里用数组+文档的方式来描述一个方法的返回值. 虽然很简单, 但是很容易误用, 正确的做法应该是定义个类。
正确的写法:
- Details getDetails(int id) {...}
- private class Details {
- public Location location;
- public Customer customer;
- public Incident incident;
- }
对方法过度限制
错误用法:
- public void notify(Person p) {
- ...
- sendMail(p.getName(), p.getFirstName(), p.getEmail());
- ...
- }
- class PhoneBook {
- String lookup(String employeeId) {
- Employee emp = ...
- return emp.getPhone();
- }
- }
第一个例子是对方法参数做了过多的限制, 第二个例子对方法的返回值做了太多的限制。
正确的写法:
- public void notify(Person p) {
- ...
- sendMail(p);
- ...
- }
- class EmployeeDirectory {
- Employee lookup(String employeeId) {
- Employee emp = ...
- return emp;
- }
- }
对POJO的setter方法画蛇添足
错误的写法:
- private String name;
- public void setName(String name) {
- this.name = name.trim();
- }
- public void String getName() {
- return this.name;
- }
有时候我们很讨厌字符串首尾出现空格, 所以在setter方法中进行了trim处理, 但是这样做的结果带来的副作用会使getter方法的返回值和setter方法不一致, 如果只是将JavaBean当做一个数据容器, 那么最好不要包含任何业务逻辑. 而将业务逻辑放到专门的业务层或者控制层中处理。
正确的做法:
- person.setName(textInput.getText().trim());
日历对象(Calendar)误用
错误的写法:
- Calendar cal = new GregorianCalender(TimeZone.getTimeZone("Europe/Zurich"));
- cal.setTime(date);
- cal.add(Calendar.HOUR_OF_DAY, 8);
- date = cal.getTime();
这里主要是对date, time, calendar和time zone不了解导致. 而在一个时间上增加8小时, 跟time zone没有任何关系, 所以没有必要使用Calendar, 直接用Date对象即可, 而如果是增加天数的话, 则需要使用Calendar, 因为采用不同的时令制可能一天的小时数是不同的(比如有些DST是23或者25个小时)
正确的写法:
- date = new Date(date.getTime() + 8L * 3600L * 1000L); // add 8 hrs