Java集合源码分析(二)-标识接口

97 阅读7分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

标识接口简介

在 Java 集合类中有三个常见的标识接口,他们只起到标识性的作用,并没有具体方法

  • RandomAccess
  • Cloneable
  • Serializable

RandomAccess

源码注释翻译 + 个人理解

package java.util;

/**
 * 标记接口使用 List 实现,指明它们支持快速(通常为固定时间)随机访问
 * 这个接口的主要目的是当应用于随机或顺序访问列表时允许通用算法改变他们的行为去提供良好的性能
 *
 * 当操作随机访问列表(例如 ArrayList)的最佳算法应用于顺序访问列表(例如 LinkedList)时会产生 n 平方的时间复杂度
 * 建议通用列表算法在应用算法之前检查所给的列表是否是该接口的实例,如果它应用于顺序访问列表会提供较差的性能,必要时要修改它们的行为去保证可接受的性能
 *
 * 众所周知,随机访问和顺序访问的区别是经常容易被搞混的
 * 例如,如果列表在实践中得到很大但固定的访问时间,就能提供渐近线访问时间
 * 这样的列表实现通常应该实现这个接口
 *
 * 根据经验,列表的实现应该实现这个接口,这个循环是这类的典型实例
 * <pre>
 *     for (int i=0, n=list.size(); i &lt; n; i++)
 *         list.get(i);
 * </pre>
 * 运行速度比这个循环快:
 * <pre>
 *     for (Iterator i=list.iterator(); i.hasNext(); )
 *         i.next();
 * </pre>
 * 这个接口是 Java 集合框架的成员
 *
 * @since 1.4
 */
public interface RandomAccess {
}

总结

  • 实现 RandomAccess 接口代表这个类是一个支持快速访问的类
  • 应用随机访问列表算法于顺序访问列表时会导致平方的时间复杂度
  • 使用 fori 比 iterator 运行速度更快

Cloneable

源码注释翻译 + 个人理解

package java.lang;

/**
 * 一个类实现 Cloneable 接口表明 java.lang.Object#clone() 方法对这个类的实例进行字段对字段的复制是合法
 *
 * 在没有实现 Cloneable 接口的实例上调用对象的克隆方法会抛出 CloneNotSupportedException 异常
 *
 * 按照惯例,实现这个接口的类应该通过一个公共方法重写 Object.clone (受保护)
 * 重写这个方法的明细查看 java.lang.Object#clone() 
 *
 * 注意,这个接口没有包含 clone 方法
 * 因此,不可能仅仅通过实现这个接口去克隆对象
 * 即使以反射方法调用 clone 方法,也不能保证它会成功
 *
 * @since JDK1.0
 */
public interface Cloneable {
}

总结

  • 实现 Cloneable 接口代表这个类可以使用 java.lang.Object#clone() 进行字段对字段的复制
  • 要调用 clone 方法必须要实现 Cloneable 接口,否则会抛出 CloneNotSupportedException 异常
  • 继承 Cloneable 接口应该重写 Object.clone 方法

Serializable

源码注释翻译 + 个人理解

package java.io;

/**
 *
 * 类的可序列化性是由实现 java.io.Serializable interface 实现的
 * 没有实现这个接口的类任何状态都不能序列化或反序列化
 * 可序列化类的所有子类都是可序列化的
 * 序列化接口没有方法或字段,服务只有证明可序列化的含义
 *
 * 为了允许不可序列化类的子类可以被序列化,子类应该负责保存和恢复父类的公共、受保护和(如果可访问)包字段的状态
 * 只有当子类继承可访问的无参构造器去初始化类的状态时,子类才能承担它们的责任
 * 如果不是这样,则声明类可序列化时错误的
 * 在运行时会检测到这个错误
 *
 * 在反序列化时,不可序列化类的字段将使用公共或受保护的无参构造器初始化
 * 一个无参构造器必须可以被可序列化子类访问
 * 可序列化子类的字段将从流中被恢复
 *
 * 当遍历图形时,可能会遇到不支持序列化的接口对象
 * 在这种情况下,抛出 NotSerializableException,并且标识这是个不可序列化对象的类
 *
 * 在序列化和反序列化过程要求特别处理的类必须通过实现这些确切签名的特殊方法
 *
 * <PRE>
 * private void writeObject(java.io.ObjectOutputStream out) throws IOException
 * private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
 * private void readObjectNoData() throws ObjectStreamException;
 * </PRE>
 *
 * writeObject 方法负责为其特定类写入对象的状态,以便相应的 readObject 方法可以恢复它
 * 保存对象字段的默认机制可以通过 out.defaultWriteObject 调用
 * 这个方法不需要关注属于它父类或子类的状态
 * 通过使用 writeObject 方法或通过使用支持的原始数据类型的 DataOutput 方法将各个字段写入 ObjectOutputStream 来保存状态
 *
 * readObject 方法负责从流读取和恢复类字段
 * 它可能会访问 .defaultReadObject 去调用默认机制恢复对象的非静态和非瞬态字段
 * defaultReadObject 方法使用流中的信息,通过保存在流中对象的字段和当前对象相应的名称字段分配
 * 这将处理类进化为添加新字段的情况
 * 这个方法不需要关心属于其父类或子类的状态
 * 通过使用 writeObject 方法或通过使用支持的原始数据类型的 DataOutput 方法将各个字段写入 ObjectOutputStream 来保存状态
 * 
 * 如果序列化流没有将给定类列为反序列化对象的父类,readObjectNoData 方法负责初始化特定类对象状态
 * 在接收方使用的反序列化实例类的版本和发送方不同,会发生接收方的版本扩展了发送方版本未扩展的类的情况
 * 如果序列化流被篡改也会发生这种情况;因此,即使存在“恶意”或不完整的源,readObjectNoData 对正确地初始化序列化对象非常有用
 *
 * 在将对象写入流时 需要指定要使用的替代对象的可序列化类应使用确切的签名实现此特殊方法:
 * 当向流中写入对象时,需要指定替代对象的可序列化类应使用明确的签名实现此特殊方法:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 * </PRE><p>
 *
 * 如果方法存在并且它可以被可序列化对象的类中声明方法访问,则通过序列化调用 writeReplace 方法
 * 因此,方法可以私有,受保护和包私有访问
 * 访问子类遵循 java 可访问规则
 *
 * 当从流中读取替换实例时,需要指定替换的类应该使用确切的签名实现这个特殊方法
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
 * </PRE><p>
 *
 * readResolve 方法遵循与 writeReplace 相同的调用规则和可访问性规则
 *
 * 序列化运行时,每个可序列化类和一个被称为 serialVersionUID 版本号关联,版本号用于在反序列化期间去验证序列化对象的发送方和接收方是否为该对象加载了与序列化兼容的类
 * 如果接收方为对象加载的类与相应发送方的类有不同的 serialVersionUID,则反序列化会导致 InvalidClassException 结果
 * 可序列化的类可以通过静态的,最终的并且是 long 类型的 serialVersionUID 的属性,明确地声明它自己的 serialVersionUID
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
 * </PRE>
 *
 * 如果一个可序列化的类没有明确地声明 serialVersionUID,则序列化运行时会根据这个类的各个方面计算出一个该类默认的 serialVersionUID 值,如 Java(TM) 对象序列化规范中所描述
 * 然而,强烈建议所有可序列化类明确地声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节非常敏感,这些细节可能因编译器实现而异
 * 这些细节可能因编译器实现而异,因此在反序列化的期间会产生不期望的结果 InvalidClassException
 * 
 * 因此,为了保证不同的 java 编译器实现一致的 serialVersionUID 值,可序列化类必须声明一个明确的 serialVersionUID 值
 * 同样强烈建议明确地 serialVersionUID 声明尽可能使用 private 修饰,因此 serialVersionUID 字段的声明只适用于当前声明的类,作为继承的成员是无用的
 * 数组类不能声明一个明确的 serialVersionUID,因此它们始终具有默认的计算值,但是不要求匹配 serialVersionUID 值
 *
 * @since   JDK1.1
 */
public interface Serializable {
}

总结

  • 没有继承 Serializable 接口的类都不能进行序列化或反序列化
  • 可序列化类的子类也可以进行序列化
  • 明确声明 serialVersionUID,否则会由于编译器实现不同而产生不同的计算值
  • serialVersionUID 必须是静态的,最终的并且是 long 类型
  • 在反序列化时,不可序列化类的字段将使用公共或受保护的无参构造器初始化