为什么Java需要Serializable接口?

121

我们在IT技术领域中大量使用序列化,需要在每个使用的对象上指定Serializable标签有点繁琐,特别是当它是第三方类,我们无法更改时。

问题是: 既然Serializable是一个空接口,并且Java提供了强大的序列化功能,一旦添加了implements Serializable为什么他们不把所有东西都做成可序列化呢?

我错过了什么吗?


如果您想使自己的对象可序列化,该怎么办?或者我理解错了什么? - Joe Phillips
我仍然会收到NotSerializableException异常,因为我的对象的所有字段都必须是可序列化的。 - Yoni Roit
1
完全同意Pop Catalin和dmitry的观点:“这种可序列化技巧只是几十年前做出的另一个错误决定。” 无论序列化有多危险都不重要。而且声明是显式的并不意味着“你知道你必须特别注意”:每个需要它的人都会先放置“implements”内容,然后再考虑如果出了问题的影响。如果他们给我们一个“Unserializable”接口来应用于特殊情况,一切都可能更清晰明了。 - Jack
13个回答

124

序列化充满了陷阱。这种形式的自动序列化支持使类内部成为公共API的一部分(这就是为什么javadoc会给出类的持久化形式)。

对于长期持久性,类必须能够解码此表单,这限制了您可以对类设计进行的更改。这破坏了封装性。

序列化也可能导致安全问题。通过能够序列化它引用的任何对象,类可以访问它通常无法访问的数据(通过解析生成的字节数据)。

还有其他问题,例如内部类的序列化形式没有明确定义。

使所有类可序列化将加剧这些问题。请查看Effective Java第二版,特别是第74项:审慎实现Serializable


2
这显然是更好的答案,我很失望选择的答案不是这个,似乎发布者选择时心中有一个“我很烦因为我必须声明可序列化”的议程。这是一个想要自由发挥而不遵守过去已经学到的安全和设计教训的人的例子。 - gbtimmon
@McDowell 为什么顺序很重要? - Geek
@Geek - 我说错了话(这教训我在重新检查规范之前评论三年前的答案)。字段顺序无关紧要。FYI:您可以在此处找到有关序列化的详细要求:http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html - McDowell
13
@McDowell 你好。我是四年前发布这个问题的原始作者,我刚刚选定了你的回答作为采纳答案,如果这有任何意义的话。我认为你的回答真的更好一些,而当时的我可能还不够成熟以理解它。现在已经修复 :) - Yoni Roit
为什么一些框架(如kafka)在序列化数据时不强制要求实例实现Serializable接口? - feiyuerenhai
显示剩余3条评论

30

我认为Java和.Net的人这一次都错了,最好将所有内容默认序列化,并且只需要标记那些不能安全序列化的类。

例如,在70年代创建的Smalltalk语言中,每个对象都是默认可序列化的。考虑到绝大多数对象都可以安全序列化,而只有极少数对象不能安全序列化,我不知道为什么在Java中没有这种情况。

将对象标记为可序列化(使用接口)并不能使该对象神奇地变得可序列化,它本来就是可序列化的,只是现在您表达了系统本来就能够找到的东西,因此我认为目前的序列化方式没有真正充分的理由。

我认为这要么是设计师做出的糟糕决定,要么是序列化被忽视了,或者平台从未准备好在所有对象上默认进行安全、一致的序列化。


30
设计和实现一个类时,您需要特别注意,以确保实例可以以合理的方式进行序列化。可序列化接口实际上意味着:"作为程序员,我已经理解了序列化的后果,并允许JVM对其进行序列化"。 - Rolf Rander
它将添加到良好的类设计清单中的另一个问题,例如确保您没有内存泄漏。尽管如此,良好的类设计越来越要求您的类在可以时是可序列化的。 - Pop Catalin
@Rolf,或者它可能意味着“我不在乎X如何序列化它,但是X需要它是可序列化的,所以我会实现Serializable”,其中X可以是一些可互操作的逻辑库(例如HttpSession,没有人关心表单对象是否被序列化)。 - MetroidFan2002
3
@StaxMan,因为后来意识到需要序列化一个类而无法做到,这可能非常代价高昂。这是其中一种情况,稍微多付出一点努力就能得到回报。特别是当你正在编写一个库并且其他人将在没有源代码的情况下使用它时,这一点尤为重要。 - Pop Catalin
2
我完全同意这个答案。在许多编程语言中,一切都是默认可序列化的。实际上,JVM也是如此,因为可以使用反射轻松访问任何类成员,无论它是私有的还是公有的。这种“Serializable”技巧只是几十年前做出的另一个错误决定,以及处理纯Java时的一些缺陷,例如标准库中的一些集合和字符串处理问题,增加了烦恼。幸运的是,有Kryo,但它是依赖项,需要先找到它。这就是内置序列化应该完成的方式。 - dmitry
显示剩余2条评论

23

并非所有的内容都可以被真正地序列化。以网络套接字连接为例,你可以序列化套接字对象的数据/状态,但是活动连接的本质将会丢失。


这将是我的问题,如果我不够聪明去尝试序列化套接字,那么我会在调试期间发现错误。然而,现在我处于这样一种情况,无法使用Java序列化,因为第三方类没有实现可序列化,而且没有什么好的理由。 - Yoni Roit
5
处理这种情况有几种不错的方法,比如编写一个可序列化的包装类,它知道如何读取和写入第三方类的关键数据;将被包装的实例设置为瞬态并覆盖 writeObject 和 readObject 方法。 - Greg Case
你能从 API 中所需的对象继承并序列化这些类吗? - Joel Coehoorn
@Joel:这是个好主意,但仍然是一个hack。我猜整件事情只是另一个失败的权衡。感谢您的评论。 - Yoni Roit
1
这是那些罕见的例子之一,它说明我们真正需要的只是 implements NotSerializable :) - Rob Grant

13
在Java中,Serializable的主要作用是默认使所有其他对象不可序列化。默认情况下,序列化机制是非常危险的,特别是其默认实现方式。因此,就像C ++中的友元一样,默认情况下它是关闭的,即使花费了一些代价来使事物可序列化。

序列化会增加约束和潜在问题,因为结构兼容性不能得到保证。默认情况下关闭它会有好处。

我必须承认,我很少见到标准序列化在非平凡类中做到我想要的事情,特别是在复杂数据结构的情况下。因此,为使类正确序列化所花费的精力远远超过添加接口的成本。


9

对于一些类,特别是那些代表一些更物理的东西,比如文件、套接字、线程或数据库连接,序列化实例是没有任何意义的。对于许多其他类,序列化可能会有问题,因为它破坏了唯一性约束条件,或者强制你处理不同版本的类的实例,这可能不是你想要的。

可以说,最好默认将所有内容设置为可序列化,并通过关键字或标记接口使类不可序列化 - 但是,那些应该使用该选项的人可能不会考虑到这一点。现在,如果你需要实现Serializable,你将会收到一个异常提示。


4

我认为这样做是为了确保你作为程序员知道你的对象可能会被序列化。


3

1

1

必须明确声明某个类的实例是可序列化的,这种语言强制你思考是否应该允许。对于简单的值对象,序列化是微不足道的,但在更复杂的情况下,你需要认真思考。

仅依赖于JVM的标准序列化支持将会导致各种令人讨厌的版本问题。

唯一性、对“真实”资源的引用、计时器和许多其他类型的工件都不是序列化的候选对象。


0

好的,我的答案是这没有什么好的理由。从你的评论中我可以看出你已经了解到了这一点。其他编程语言很高兴地尝试序列化所有在你数到10之后不跳上树的东西。一个对象应该默认为可序列化。

所以,你基本上需要做的就是自己读取第三方类的所有属性。或者,如果这对你来说是个选项:反编译,把该死的关键字放在那里,然后重新编译。


2
序列化会增加约束和潜在问题,因为结构兼容性无法保证。在我看来,默认情况下关闭序列化是有好处的。 - Uri
我不确定你所说的“结构兼容性”是什么意思。 - nes1983

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接