语言特性
序列化
序列化对象中的HashMap、HashSet或HashTable等集合不能包含对象自身的引用
说明:如果一个被序列化的对象中,包含有HashMap、HashSet或HashTable集合,则这些集合中不允许保存当前被序列化对象的直接或间接引用。
因为,这些集合类型在反序列化的时候,会调用到当前序列化对象的hashCode方法,而此时(序列化对象还未完全加载)计算出的hashCode有可能不正确,从而导致对象放置位置错误,破坏反序列化的实例。
示例:
class Super implements Serializable { final Set<Super> set = new HashSet<Super>(); } final class Sub extends Super { private int id; public Sub(int id) { this.id = id; set.add(this); // 集合中引用了当前对象 } public void checkInvariant() { if (!set.contains(this)) { throw new AssertionError("invariant violated"); } } public int hashCode() { return id; } public boolean equals(Object o) { return (o instanceof Sub) && (id == ((Sub) o).id); } }
这个例子中,将当前对象(Sub对象)放入了对象中的HashSet中,在反序列化set时,因为id属性还未完成初始化,导致hashCode的结果为0,从而导致Sub对象在set中的位置放置错误,对象被破坏。
实现Serializable接口的可序列化类应该显式声明 serialVersionUID
说明: 如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。
不过,强烈建议所有可序列化类都显式声明 serialVersionUID 值,原因计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException
。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修改器显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于立即声明类 –
serialVersionUID 字段作为继承成员没有用处。
示例:
public class BeanType implements Serializable
{
private static final long serialVersionUID = -2589766491699675794L;
…
}
泛型
在集合中使用泛型(v1.5+)
说明:Java 1.5版本中增加了泛型,在没有泛型之前,从集合中读取到的每一个对象都必须进行转换。如果有人不小心插入类型错误的对象,在运行时的转换处理就会出错。
有了泛型之后,可以告诉编译器每个集合中接受哪些对象类型。编译器自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。
示例:
不好:错误的将Coin对象插入到samps集合中,直到从集合中获取coin时才收到错误提示
private final Collection stamps = ...;
stamps.add(new Coin(...));
推荐:使用泛型,在编译时会提示类型错误
private final Collection<Stamp> stamps = ...;
stamps.add(new Coin(...));
还有个好处是,从集合中获取元素时,不再需要进行手工类型转换。如下所示:
for(Stamp s : stamps)
{
...
}
类的设计可优先考虑泛型(v1.5+)
说明:使用泛型类型,比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易。
示例:如下所示,一个设计为泛型的stack类,在使用时,无需对栈中元素进行类型转换。
public static void main(String[] args) { Stack<String> stack = new Stack<String>(); for (String arg : args) { stack.push(arg); } while(!stack.isEmpty()) { System.out.println(stack.pop().toUpperCase()); } }
方法的设计可优先考虑泛型(v1.5+)
说明:就如类可以从泛型中受益一般,方法也一样。静态工具方法尤其适合于泛型化。
示例:如下所示,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易。
public static <E> Set<E> union(Set<E> s1, Set<E> s2) { Set<E> result = new HashSet<E>(s1); result.addAll(s2); return result; } public static void main(String[] args) { Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick", "Harry")); Set<String> stooges = new HashSet<String>(Arrays.asList("Larry", "Moe", "Curly")); Set<String> aflCio = union(guys, stooges); System.out.println(aflCio); }
优先使用泛型集合,而不是数组(v1.5+)
说明:数组与泛型集合相比,有两个重要不同点。首先,数组是协变的(covariant),即Sub是Super的子类型,则Sub[]也是Super[]的子类型。
相反,泛型则是不可变的(invariant),对于任意两个类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是其超类型。其次,数组是具体化的,因此数组在运行时才知道并检查它们的元素类型约束。
示例:如下代码是合法的,但执行时会报错
Object[] objectArray = new
Long[1];
objectArray[0] =
"I don't fit in"; //Throws ArrayStoreException
而如下代码则会在编译时报错
List<Object> objectList = new ArrayList<Long>(); //Incompatible types
objectList.add("I don't fit
in");