What is the difference between Serializable
and Externalizable
in Java?
11 回答
要添加到其他答案,通过实现java.io.Serializable
,您可以获得类对象的“自动”序列化功能。无需实现任何其他逻辑,它会正常工作。Java 运行时将使用反射来确定如何编组和解组您的对象。
在 Java 的早期版本中,反射非常慢,因此序列化大型对象图(例如在客户端-服务器 RMI 应用程序中)有点性能问题。为了处理这种情况,java.io.Externalizable
提供了接口,它类似于java.io.Serializable
但具有自定义编写的机制来执行编组和解组功能(您需要在类上实现readExternal
和writeExternal
方法)。这为您提供了绕过反射性能瓶颈的方法。
在 Java 的最新版本(当然是 1.3 以后的版本)中,反射的性能比以前要好得多,因此这不是什么问题。我怀疑您很难从Externalizable
现代 JVM 中获得有意义的好处。
此外,内置的 Java 序列化机制不是唯一的,您可以获得第三方替代品,例如 JBoss Serialization,它要快得多,并且是默认的替代品。
一个很大的缺点Externalizable
是你必须自己维护这个逻辑——如果你在你的类中添加、删除或更改一个字段,你必须改变你的writeExternal
/readExternal
方法来解释它。
综上所述,Externalizable
是Java 1.1时代的遗物。真的已经没有必要了。
序列化提供了存储和稍后重新创建对象的默认功能。它使用详细格式来定义要存储的对象的整个图,例如假设您有一个linkedList,并且您的代码如下所示,那么默认序列化将发现所有链接的对象并将进行序列化。在默认序列化中,对象完全由其存储的位构造,没有构造函数调用。
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("/Users/Desktop/files/temp.txt"));
oos.writeObject(linkedListHead); //writing head of linked list
oos.close();
但是,如果您想要限制序列化或不希望您的对象的某些部分被序列化,那么请使用 Externalizable。Externalizable 接口扩展了 Serializable 接口,增加了两个方法,writeExternal() 和 readExternal()。这些在序列化或反序列化时自动调用。在使用 Externalizable 时,我们应该记住默认构造函数应该是公共的,否则代码将抛出异常。请遵循以下代码:
public class MyExternalizable implements Externalizable
{
private String userName;
private String passWord;
private Integer roll;
public MyExternalizable()
{
}
public MyExternalizable(String userName, String passWord, Integer roll)
{
this.userName = userName;
this.passWord = passWord;
this.roll = roll;
}
@Override
public void writeExternal(ObjectOutput oo) throws IOException
{
oo.writeObject(userName);
oo.writeObject(roll);
}
@Override
public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException
{
userName = (String)oi.readObject();
roll = (Integer)oi.readObject();
}
public String toString()
{
StringBuilder b = new StringBuilder();
b.append("userName: ");
b.append(userName);
b.append(" passWord: ");
b.append(passWord);
b.append(" roll: ");
b.append(roll);
return b.toString();
}
public static void main(String[] args)
{
try
{
MyExternalizable m = new MyExternalizable("nikki", "student001", 20);
System.out.println(m.toString());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/Desktop/files/temp1.txt"));
oos.writeObject(m);
oos.close();
System.out.println("***********************************************************************");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/Desktop/files/temp1.txt"));
MyExternalizable mm = (MyExternalizable)ois.readObject();
mm.toString();
System.out.println(mm.toString());
}
catch (ClassNotFoundException ex)
{
Logger.getLogger(MyExternalizable.class.getName()).log(Level.SEVERE, null, ex);
}
catch(IOException ex)
{
Logger.getLogger(MyExternalizable.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
在这里,如果您注释默认构造函数,则代码将引发以下异常:
java.io.InvalidClassException: javaserialization.MyExternalizable;
javaserialization.MyExternalizable; no valid constructor.
我们可以观察到,由于密码是敏感信息,所以我没有在 writeExternal(ObjectOutput oo) 方法中对其进行序列化,也没有在 readExternal(ObjectInput oi) 中设置相同的值。这就是 Externalizable 提供的灵活性。
上述代码的输出如下:
userName: nikki passWord: student001 roll: 20
***********************************************************************
userName: nikki passWord: null roll: 20
我们可以观察到,因为我们没有设置 passWord 的值,所以它为空。
也可以通过将密码字段声明为瞬态来实现。
private transient String passWord;
希望能帮助到你。如果我犯了任何错误,我深表歉意。谢谢。
Serializable
和之间的主要区别Externalizable
- 标记接口:
Serializable
是没有任何方法的标记接口。Externalizable
接口包含两个方法:writeExternal()
和readExternal()
。 - 序列化过程:默认序列化过程将被用于实现
Serializable
接口的类。程序员定义的序列化过程将被用于实现Externalizable
接口的类。 - 维护:不兼容的更改可能会破坏序列化。
- 向后兼容性和控制:如果您必须支持多个版本,您可以完全控制
Externalizable
界面。您可以支持不同版本的对象。如果您实现,则序列化类Externalizable
是您的责任super
- 公共无参数构造函数:
Serializable
使用反射构造对象,不需要无参数构造函数。但Externalizable
需要公共无参数构造函数。
有关详细信息,请参阅博客Hitesh Garg
。
Serialization uses certain default behaviors to store and later recreate the object. You may specify in what order or how to handle references and complex data structures, but eventually it comes down to using the default behavior for each primitive data field.
Externalization is used in the rare cases that you really want to store and rebuild your object in a completely different way and without using the default serialization mechanisms for data fields. For example, imagine that you had your own unique encoding and compression scheme.
对象序列化使用 Serializable 和 Externalizable 接口。 Java 对象只能序列化。如果一个类或其任何超类实现了 java.io.Serializable 接口或其子接口 java.io.Externalizable。大多数 java 类都是可序列化的。
NotSerializableException
:packageName.ClassName
« 要参与序列化过程中的类对象,该类必须实现 Serializable 或 Externalizable 接口。
对象序列化生成一个流,其中包含有关正在保存的对象的 Java 类的信息。对于可序列化的对象,即使存在不同(但兼容)的类实现版本,也会保留足够的信息来恢复这些对象。Serializable 接口被定义为识别实现可序列化协议的类:
package java.io;
public interface Serializable {};
- 序列化接口没有方法或字段,仅用于识别可序列化的语义。对于类的序列化/反序列化,我们可以使用默认的 writeObject 和 readObject 方法(或者)我们可以覆盖类中的 writeObject 和 readObject 方法。
- JVM 将完全控制对象的序列化。使用瞬态关键字来防止数据成员被序列化。
- 这里可序列化的对象是直接从流中重构而不执行
InvalidClassException
« 在反序列化过程中,如果本地类serialVersionUID值与对应的sender类不同。然后导致冲突java.io.InvalidClassException: com.github.objects.User; local class incompatible: stream classdesc serialVersionUID = 5081877, local class serialVersionUID = 50818771
- 类的非瞬态和非静态字段的值被序列化。
对于 Externalizable 对象,容器只保存对象的类的标识;该类必须保存和恢复内容。Externalizable 接口定义如下:
package java.io;
public interface Externalizable extends Serializable
{
public void writeExternal(ObjectOutput out)
throws IOException;
public void readExternal(ObjectInput in)
throws IOException, java.lang.ClassNotFoundException;
}
- Externalizable 接口有两个方法,一个可外部化的对象必须实现 writeExternal 和 readExternal 方法来保存/恢复对象的状态。
- 程序员必须处理要序列化的对象。作为程序员照顾序列化所以,这里的瞬态关键字不会限制序列化过程中的任何对象。
- 重构 Externalizable 对象时,会使用公共无参数构造函数创建实例,然后调用 readExternal 方法。可序列化对象是通过从 ObjectInputStream 中读取来恢复的。
OptionalDataException
« 字段的顺序和类型必须与我们写出的相同。如果流中的任何类型不匹配,则会抛出 OptionalDataException。@Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt( id ); out.writeUTF( role ); out.writeObject(address); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.id = in.readInt(); this.address = (Address) in.readObject(); this.role = in.readUTF(); }
写入(公开)以
ObjectOutput
进行序列化的类的实例字段。
示例«实现可序列化
class Role {
String role;
}
class User extends Role implements Serializable {
private static final long serialVersionUID = 5081877L;
Integer id;
Address address;
public User() {
System.out.println("Default Constructor get executed.");
}
public User( String role ) {
this.role = role;
System.out.println("Parametarised Constructor.");
}
}
class Address implements Serializable {
private static final long serialVersionUID = 5081877L;
String country;
}
示例«实现 Externalizable
class User extends Role implements Externalizable {
Integer id;
Address address;
// mandatory public no-arg constructor
public User() {
System.out.println("Default Constructor get executed.");
}
public User( String role ) {
this.role = role;
System.out.println("Parametarised Constructor.");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt( id );
out.writeUTF( role );
out.writeObject(address);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.address = (Address) in.readObject();
this.role = in.readUTF();
}
}
例子
public class CustomClass_Serialization {
static String serFilename = "D:/serializable_CustomClass.ser";
public static void main(String[] args) throws IOException {
Address add = new Address();
add.country = "IND";
User obj = new User("SE");
obj.id = 7;
obj.address = add;
// Serialization
objects_serialize(obj, serFilename);
objects_deserialize(obj, serFilename);
// Externalization
objects_WriteRead_External(obj, serFilename);
}
public static void objects_serialize( User obj, String serFilename ) throws IOException{
FileOutputStream fos = new FileOutputStream( new File( serFilename ) );
ObjectOutputStream objectOut = new ObjectOutputStream( fos );
// java.io.NotSerializableException: com.github.objects.Address
objectOut.writeObject( obj );
objectOut.flush();
objectOut.close();
fos.close();
System.out.println("Data Stored in to a file");
}
public static void objects_deserialize( User obj, String serFilename ) throws IOException{
try {
FileInputStream fis = new FileInputStream( new File( serFilename ) );
ObjectInputStream ois = new ObjectInputStream( fis );
Object readObject;
readObject = ois.readObject();
String calssName = readObject.getClass().getName();
System.out.println("Restoring Class Name : "+ calssName); // InvalidClassException
User user = (User) readObject;
System.out.format("Obj[Id:%d, Role:%s] \n", user.id, user.role);
Address add = (Address) user.address;
System.out.println("Inner Obj : "+ add.country );
ois.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void objects_WriteRead_External( User obj, String serFilename ) throws IOException {
FileOutputStream fos = new FileOutputStream(new File( serFilename ));
ObjectOutputStream objectOut = new ObjectOutputStream( fos );
obj.writeExternal( objectOut );
objectOut.flush();
fos.close();
System.out.println("Data Stored in to a file");
try {
// create a new instance and read the assign the contents from stream.
User user = new User();
FileInputStream fis = new FileInputStream(new File( serFilename ));
ObjectInputStream ois = new ObjectInputStream( fis );
user.readExternal(ois);
System.out.format("Obj[Id:%d, Role:%s] \n", user.id, user.role);
Address add = (Address) user.address;
System.out.println("Inner Obj : "+ add.country );
ois.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
@看
https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html
默认序列化有点冗长,并假定序列化对象的最广泛使用场景,因此默认格式 (Serializable) 使用有关序列化对象类的信息来注释结果流。
外部化使对象流的生产者能够完全控制精确的类元数据(如果有的话),超出了对类的最低要求标识(例如,它的名称)。这在某些情况下显然是可取的,例如在封闭环境中,对象流的生产者和它的消费者(从流中具体化对象)是匹配的,并且关于类的附加元数据没有任何用途并且会降低性能。
此外(正如 Uri 指出的那样),外部化还提供了对与 Java 类型相对应的流中数据的编码的完全控制。对于(一个人为的)示例,您可能希望将布尔值 true 记录为“Y”,将 false 记录为“N”。外化允许你这样做。
实际上并没有提供 Externalizable 接口来优化序列化过程的性能!但要提供实现您自己的自定义处理的方法,并提供对对象及其超类型流的格式和内容的完全控制!
这方面的示例是实现 AMF(ActionScript 消息格式)远程处理以通过网络传输本机操作脚本对象。
在考虑提高性能的选项时,不要忘记自定义序列化。您可以免费让 Java 做它做得好的事情,或者至少做得足够好,并为它做得不好的事情提供自定义支持。这通常比完整的 Externalizable 支持少很多代码。
Serializable 和 Externalizable 之间存在很多差异,但是当我们比较自定义 Serializable(overrided writeObject() & readObject()) 和 Externalizable 之间的差异时,我们发现自定义实现与 ObjectOutputStream 类紧密绑定,在 Externalizable 案例中,我们自己提供 ObjectOutput 的实现,它可以是 ObjectOutputStream 类,也可以是其他的,例如 org.apache.mina.filter.codec.serialization.ObjectSerializationOutputStream
如果是 Externalizable 接口
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(key);
out.writeUTF(value);
out.writeObject(emp);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.key = in.readUTF();
this.value = in.readUTF();
this.emp = (Employee) in.readObject();
}
**In case of Serializable interface**
/*
We can comment below two method and use default serialization process as well
Sequence of class attributes in read and write methods MUST BE same.
// below will not work it will not work .
// Exception = java.io.StreamCorruptedException: invalid type code: 00\
private void writeObject(java.io.ObjectOutput stream)
*/
private void writeObject(java.io.ObjectOutputStream Outstream)
throws IOException {
System.out.println("from writeObject()");
/* We can define custom validation or business rules inside read/write methods.
This way our validation methods will be automatically
called by JVM, immediately after default serialization
and deserialization process
happens.
checkTestInfo();
*/
stream.writeUTF(name);
stream.writeInt(age);
stream.writeObject(salary);
stream.writeObject(address);
}
private void readObject(java.io.ObjectInputStream Instream)
throws IOException, ClassNotFoundException {
System.out.println("from readObject()");
name = (String) stream.readUTF();
age = stream.readInt();
salary = (BigDecimal) stream.readObject();
address = (Address) stream.readObject();
// validateTestInfo();
}
我添加了示例代码以更好地解释。请签入/签出 Externalizable 的对象案例。这些不直接绑定到任何实现。
Outstream/Instream 与类紧密绑定。我们可以扩展 ObjectOutputStream/ObjectInputStream 但它会有点难用。
基本上,Serializable
是一个标记接口,它意味着一个类对于序列化是安全的,并且 JVM 确定它是如何序列化的。Externalizable
包含 2 个方法,readExternal
和writeExternal
. Externalizable
允许实现者决定如何序列化对象,其中Serializable
序列化对象是默认方式。
一些区别:
对于序列化,不需要该类的默认构造函数,因为 Object 因为 JVM 在反射 API 的帮助下构造相同。在不需要 arg 的外部化构造器的情况下,因为控制权在程序手中,然后通过设置器将反序列化的数据分配给对象。
在序列化中,如果用户想要跳过某些要序列化的属性,则必须将该属性标记为瞬态,反之亦然,外部化不需要。
当任何类都需要向后兼容支持时,建议使用 Externalizable。序列化支持 defaultObject 持久化,如果对象结构被破坏,那么在反序列化时会导致问题。