9

从技术上讲,我知道为什么类需要实现可序列化。原因是 ObjectOutputStream 的 writeObject 方法在写入对象状态之前在内部检查“可序列化的实例”。

但我的问题是,这有什么需要?writeObject 方法可以简单地写入对象状态,无论对象(需要写入其状态)是否实现可序列化?

根据wiki,一个类实现此接口以指示其非瞬态数据成员可以写入 ObjectOutputStream。但另一个问题是为什么类需要实现可序列化只是为了确定字段是否是瞬态的。即使是没有实现可序列化的类也应该被序列化(标记为瞬态的字段除外)。

将对象标记为可序列化(带有接口)并不会神奇地使该对象可序列化,它一直是可序列化的,只是现在您表达了系统可以自己找到的东西,所以我认为没有真正好的理由序列化就是现在的样子。

为什么类需要实现可序列化标记接口来序列化类?

4

3 回答 3

5

通过提供marker接口,人们可以选择是否制作class Serializable。有时,您可能不想要这个!(当然,将其设为默认值并不好:例如,请参阅此问题的答案:Why Java needs Serializable interface?

于 2013-11-16T12:27:36.073 回答
4

通过封装,我们假装没有透露对象的内部表示,我们只通过它们的公共接口与我们的组件交互;当我们想要更改组件中数据的内部表示而不破坏其用户的任何代码时,我们通常会利用该属性。

相反,序列化意味着通过将对象的状态转换为可以在以后存储和恢复的其他格式来暴露对象的内部状态。这意味着,一旦被序列化,一个对象的内部结构就不能在不冒这个复活过程成功的风险的情况下被改变。

序列化的问题不仅可能出现在开放系统的情况下,也可能出现在以某种方式依赖它的分布式系统中。例如,如果我们停止我们的应用程序服务器,它可能会选择序列化当前会话中的对象以便稍后在服务器重新启动时复活它们,但是如果我们使用新版本的可序列化对象重新部署我们的应用程序,它们是否仍然是当服务器尝试复活它们时兼容吗?在分布式系统中常见使用代码移动性,即,类集位于一个中央存储库中,可供客户端和服务器共享公共代码。在这种方法中,由于对象被序列化以在客户端和服务器之间共享,如果我们更新这个公共存储库中的可序列化类,我们是否冒着破坏任何东西的风险?

例如,假设我们有一个 Person 类,如下所示:

public class Person {
   private String firstName;
   private String lastName;
   private boolean isMale;
   private int age;

  public boolean isMale() {
     return this.isMale;
  }
  public int getAge() {
    return this.age;
  }
  //more getters and setters
}

假设我们发布了我们的 API 的第一个版本,其中包含一个 Person 的抽象。但是,对于第二个版本,我们想引入两个更改:首先,我们发现如果我们可以存储一个人的出生日期而不是整数形式的年龄会更好,其次我们对当 Java 没有枚举时可能会出现类 Person,但现在我们想用它们来表示一个人的性别。

显然,由于字段被正确封装,我们可以在不影响公共接口的情况下更改类的内部工作。有点像这样:

public class Person {
   private String firstName;
   private String lastName;
   private Gender gender;
   private Date dateOfBirth;

  public boolean isMale() {
     return this.gender == Gender.MALE;
  }
  public int getAge() {
    Calendar today = Calendar.getInstance();
    Calendar birth = Calendar.getInstance();
    birth.setTime(this.dateOfBirth);
    return today.get(Calendar.YEAR) - birth.get(Calendar.YEAR);
  }
  //the rest of getters and setters
}

通过如上所示进行这些更改,我们可以确保预先存在的客户端不会中断,因为即使我们更改了对象状态的内部表示,我们也保持公共接口不变。

然而,考虑到 Person 类默认是可序列化的,如果我们的系统是一个开放系统,可能会有数千行代码依赖于它们能够基于原始类复活序列化对象这一事实,甚至可能是基于类的原始版本作为父类序列化扩展类的客户。这些对象中的一些可能已经被我们 API 的用户序列化为二进制形式或其他格式,他们现在希望进化到我们的第二版代码。

然后,如果我们想像在第二个示例中那样做一些更改,我们会立即破坏其中的一些;所有具有基于原始版本的类的序列化对象的对象,这些对象存储的对象包含一个名为 age 的 int 类型的字段,其中包含一个人的年龄,而名为 isMale 的 boolean 类型的字段包含有关性别的信息,很可能在这些对象的反序列化,因为新的类定义使用了新的字段和新的数据类型。

很明显,这里的问题是序列化暴露了我们对象的敏感信息,现在我们不能简单地改变任何东西,甚至不能改变我们认为封装的东西,因为通过序列化,所有东西都被公开了。

现在,考虑一个场景,其中 JDK API 中的每个类默认都是可序列化的。Java 的设计者根本无法在不冒险破坏许多应用程序的情况下发展 Java 的 API。他们将被迫假设有人可能拥有 JDK 中任何类的序列化版本。

有一些方法可以处理可序列化类的演变,但这里重要的一点是,当涉及到封装时,我们希望尽可能地包含可序列化类,对于那些我们确实需要序列化的类,那么我们可能需要考虑任何可能的场景的含义,在这些场景中,我们可能会尝试使用其类的进化版本来复活一个对象。

尽管如此,序列化也具有安全隐患,因为有关我们对象的重要敏感信息很容易暴露。

因此,标记可序列化的类可以让 API 的设计者更容易处理它们。

于 2013-11-16T12:28:26.093 回答
0

Serializable 和 Cloneable 一样被称为标记接口。当您使用一些标记接口(无方法实现)时,您想告诉 jvm 或编译器在运行时或编译时添加或检查某些内容。

标记接口定义:

“一个接口被称为标记接口,当它被java解释器提供为句柄来标记一个类时,它可以在运行时向它提供特殊行为并且它们没有任何方法声明”。

标记界面也是对代码进行分类的好方法。您可以创建标记接口以逻辑划分代码,如果您有自己的工具,则可以对这些类执行一些预处理操作。对于开发 API 和框架(如 Spring 或 Struts)特别有用。

标记接口还可以帮助代码覆盖率或代码审查工具根据标记接口的指定行为来查找错误。

请参阅 wiki http://en.wikipedia.org/wiki/Marker_Interface_pattern中的标记界面

于 2013-11-16T12:33:54.103 回答