transient的误用
错误的代码:
public class A implements Serializable { private String someState; private transient Log log = LogFactory.getLog(getClass()); public void f() { log.debug("enter f"); ... } }
这里的本意是不希望Log对象被序列化. 不过这里在反序列化时, 会因为log未初始化, 导致f()方法抛空指针, 正确的做法是将log定义为静态变量或者定位为具备变量.
正确的写法:
public class A implements Serializable { private String someState; private static final Log log = LogFactory.getLog(A.class); public void f() { log.debug("enter f"); ... } } public class A implements Serializable { private String someState; public void f() { Log log = LogFactory.getLog(getClass()); log.debug("enter f"); ... } }
不必要的初始化
错误的代码:
public class B { private int count = 0; private String name = null; private boolean important = false; }
这里的变量会在初始化时使用默认值:0, null, false, 因此上面的写法有些多此一举.
正确的写法:
public class B { private int count; private String name; private boolean important; }
最好用静态final定义Log变量
private static final Log log = LogFactory.getLog(MyClass.class);
这样做的好处有三:
可以保证线程安全
静态或非静态代码都可用
不会影响对象序列化
选择错误的类加载器
错误的代码:
Class clazz = Class.forName(name); Class clazz = getClass().getClassLoader().loadClass(name);
这里本意是希望用当前类来加载希望的对象, 但是这里的getClass()可能抛出异常, 特别在一些受管理的环境中, 比如应用服务器, web容器, Java WebStart环境中, 最好的做法是使用当前应用上下文的类加载器来加载.
正确的写法:
ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) cl = MyClass.class.getClassLoader(); // fallback Class clazz = cl.loadClass(name);
反射使用不当
错误的代码:
Class beanClass = ... if (beanClass.newInstance() instanceof TestBean) ...
这里的本意是检查beanClass是否是TestBean或是其子类, 但是创建一个类实例可能没那么简单, 首先实例化一个对象会带来一定的消耗, 另外有可能类没有定义默认构造函数. 正确的做法是用Class.isAssignableFrom(Class) 方法.
正确的写法:
Class beanClass = ... if (TestBean.class.isAssignableFrom(beanClass)) ...
不必要的同步
错误的代码:
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());