我已经读过,通常抽象类不应该在Java中使用Serializable。子类应该是可序列化的(如果需要,可以使用自定义读取,写入方法,例如,当抽象类具有字段时)。
这背后的原因是什么?为什么它被认为是糟糕的设计?
Update1:我有一个抽象类,包含一些字段和三个子类。截至目前,我正在使用以下方法。
我已经使用自定义读取,写入方法使所有子类可序列化。在抽象类中,我有以下方法。
void writeFields(ObjectOutputStream out)throws IOException { .... }
void readFields(ObjectInputStream in) throws IOException, ClassNotFoundException{ ... }
在自定义读取,子类中的write方法中,我调用这些方法来(de)序列化抽象类中的字段。这种方法是否正确?或者有更好的方法吗?
更新2:我接受了Tom的建议并制作了我的抽象类Serializable。 (我希望所有子类都是Serializable,我在抽象类中有数据)这是一个旁边,但只是为了完成我正在使用的故事reflection to change final fields as advised by Jeremy Manson.
答案 0 :(得分:7)
我认为原因是如果Abstract类实现Serializable,则无法说子类型不应该是Serializable。最好让每个具体类型为自己声明......
答案 1 :(得分:3)
让我们采取相反的立场。如果您要对对象进行反序列化,那么它的类型是什么?
根据定义,无法实例化抽象类。如果你可以序列化它,这意味着它也可以被反序列化,并且会得到一个抽象类的实例。这与抽象类的定义相矛盾,因此无法完成。
答案 2 :(得分:3)
我不知道这一定是糟糕的设计。序列化实际上是一个实现问题(注意,Josh Bloch不同意我的观点),因此对接口没有意义。如果抽象类具有状态,那么您可能希望将其设置为可序列化。如果它没有状态,那么实际上没有任何理由这样做。
我们举一个例子。 java.security.cert.Certificate
是一个抽象的可序列化类,具有"type"
可序列化字段。如果它不可序列化,则子类不可能是可序列化的并且设置该字段。你会被迫进入黑客行列。
请注意java.io.Serializable
是一个黑客攻击。它不应该是一个接口。注释(或类似transient
的语言演变)会更合适。
与往常一样,最好选择合成继承而不是随机类可序列化。
答案 3 :(得分:0)
它只是糟糕的设计,因为它是一个强制决定,如果你想要一个具有非可序列化成员的子类,那该怎么办呢。
这就是原因。
E.g。 List不是Serializable,但每个主要列表实现都是。 (我知道list是一个接口,但是没有成员的抽象类= / =接口)
答案 4 :(得分:0)
如果 abstract
类包含重要的序列化字段,并且您不打算实现自定义对象读取器/写入器,则必须将该基类声明为 Serializable
。< /p>
将基类(或接口)声明为 Serializable
会使所有子类 Serializable
包括任何传递路径。
如果你实现了一些框架,后者是有意义的:你可以让你的用户自动传播接口,否则你应该记录你只使用 Serializable
或为用户提供的类实现运行时检查。
我为实体添加了基类(为了统一命名标准,对于 id
、createdDate
、updatedDate
等字段),但忘记将其标记为 Serializable
。因此,任何保存在会话存储 (Redis) 中的实体在反序列化时都具有 null
id
值 )) 您不希望这样。
UPDATE Serializable
需要公共无参数构造函数进行反序列化。继承声明不会为子类或超类创建无参数构造函数,这是用户的苦差事。没有默认构造函数的超类会破坏所有子类的反序列化 ))
UPDATE 2 有一种方法可以声明子类不是为序列化而设计的,即使它是从 Serialazable
基类继承的,尽管它是运行时特性:
private void writeObject(ObjectOutputStream out) throws IOException
{ throw new NotSerializableException(); }
private void readObject(ObjectInputStream in) throws IOException
{ throw new NotSerializableException(); }