Introduction to Java Serialization – Java序列化简介

最后修改: 2017年 5月 15日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Serialization is the conversion of the state of an object into a byte stream; deserialization does the opposite. Stated differently, serialization is the conversion of a Java object into a static stream (sequence) of bytes, which we can then save to a database or transfer over a network.

序列化是将对象的状态转换为字节流;反序列化的作用则相反。换句话说,序列化是将一个Java对象转换为静态的字节流(序列),然后我们可以将其保存到数据库或通过网络传输。

2. Serialization and Deserialization

2.序列化和反序列化

The serialization process is instance-independent; for example, we can serialize objects on one platform and deserialize them on another. Classes that are eligible for serialization need to implement a special marker interface, Serializable. 

序列化过程是与实例无关的;例如,我们可以在一个平台上序列化对象,而在另一个平台上反序列化。符合序列化条件的类需要实现一个特殊的标记接口, Serializable。

Both ObjectInputStream and ObjectOutputStream are high level classes that extend java.io.InputStream and java.io.OutputStream, respectively. ObjectOutputStream can write primitive types and graphs of objects to an OutputStream as a stream of bytes. We can then read these streams using ObjectInputStream.

ObjectInputStreamObjectOutputStream都是高水平的类,分别扩展了java.io.InputStreamjava.io.OutputStreamObjectOutputStream可以将原始类型和对象的图形作为字节流写入OutputStream。然后我们可以使用ObjectInputStream读取这些流。

The most important method in ObjectOutputStream is:

ObjectOutputStream中最重要的方法是。

public final void writeObject(Object o) throws IOException;

This method takes a serializable object and converts it into a sequence (stream) of bytes. Similarly, the most important method in ObjectInputStream is:

这个方法接收一个可序列化的对象,并将其转换为一个字节的序列(流)。同样地,ObjectInputStream中最重要的方法是。

public final Object readObject() 
  throws IOException, ClassNotFoundException;

This method can read a stream of bytes and convert it back into a Java object. It can then be cast back to the original object.

这个方法可以读取一个字节流并将其转换为一个Java对象。然后,它可以被投回给原始对象。

Let’s illustrate serialization with a Person class. Note that static fields belong to a class (as opposed to an object) and are not serialized. Also, note that we can use the keyword transient to ignore class fields during serialization:

让我们用一个Person类来说明序列化。请注意,静态字段属于一个类(而不是一个对象),不被序列化。另外,注意我们可以使用关键字transient来在序列化过程中忽略类的字段。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;

    // getters and setters
}

The test below shows an example of saving an object of type Person to a local file, and then reading the value back in:

下面的测试显示了一个将Person类型的对象保存到本地文件,然后再读回值的例子。

@Test 
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () 
  throws IOException, ClassNotFoundException { 
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
    
    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    objectOutputStream.close();
    
    FileInputStream fileInputStream
      = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close(); 
 
    assertTrue(p2.getAge() == person.getAge());
    assertTrue(p2.getName().equals(person.getName()));
}

We used ObjectOutputStream for saving the state of this object to a file using FileOutputStream. The file “yourfile.txt” is created in the project directory. This file is then loaded using FileInputStream. ObjectInputStream picks this stream up and converts it into a new object called p2.

我们使用ObjectOutputStream将这个对象的状态保存到文件中,使用FileOutputStream。文件“yourfile.txt”在项目目录中被创建。然后使用FileInputStream加载这个文件。 ObjectInputStream拾取这个流并将其转换为一个名为p2的新对象。

Finally, we’ll test the state of the loaded object, and ensure it matches the state of the original object.

最后,我们将测试加载对象的状态,并确保它与原始对象的状态一致。

Note that we have to explicitly cast the loaded object to a Person type.

请注意,我们必须明确地将加载的对象转换为Person类型。

3. Java Serialization Caveats

3.Java序列化的注意事项

There are some caveats which concern serialization in Java.

在Java中,有一些关于序列化的注意事项。

3.1. Inheritance and Composition

3.1.继承和组成

When a class implements the java.io.Serializable interface, all its sub-classes are serializable as well. Conversely, when an object has a reference to another object, these objects must implement the Serializable interface separately, or else a NotSerializableException will be thrown:

当一个类实现了java.io.Serializable接口时,它的所有子类也是可序列化的。相反,当一个对象有对另一个对象的引用时,这些对象必须分别实现Serializable接口,否则就会抛出NotSerializableException

public class Person implements Serializable {
    private int age;
    private String name;
    private Address country; // must be serializable too
}

If one of the fields in a serializable object consists of an array of objects, then all of these objects must be serializable as well, or else a NotSerializableException will be thrown.

如果可序列化对象中的一个字段由一个对象数组组成,那么所有这些对象也必须是可序列化的,否则将抛出NotSerializableException

3.2. Serial Version UID

3.2.序列版本UID

The JVM associates a version (long) number with each serializable class. We use it to verify that the saved and loaded objects have the same attributes, and thus are compatible on serialization.

JVM将一个版本(long)号与每个可序列化的类联系起来。我们用它来验证保存和加载的对象是否具有相同的属性,因此在序列化时是兼容的。

Most IDEs can generate this number automatically, and it’s based on the class name, attributes, and associated access modifiers. Any changes result in a different number, and can cause an InvalidClassException.

大多数IDE可以自动生成这个数字,它是基于类的名称、属性和相关的访问修饰符。任何改变都会导致一个不同的数字,并可能引起InvalidClassException

If a serializable class doesn’t declare a serialVersionUID, the JVM will generate one automatically at run-time. However, it’s highly recommended that each class declares its serialVersionUID, as the generated one is compiler dependent and thus may result in unexpected InvalidClassExceptions.

如果一个可序列化的类没有声明一个serialVersionUID,JVM将在运行时自动生成一个。然而,我们强烈建议每个类都声明它的serialVersionUID,因为生成的serialVersionUID与编译器有关,因此可能导致意外的InvalidClassExceptions

3.3. Custom Serialization in Java

3.3.Java中的自定义序列化

Java specifies a default way to serialize objects, but Java classes can override this default behavior. Custom serialization can be particularly useful when trying to serialize an object that has some unserializable attributes. We can do this by providing two methods inside the class that we want to serialize:

Java指定了一种默认的方法来序列化对象,但Java类可以覆盖这种默认行为。当试图序列化一个具有一些不可序列化属性的对象时,自定义序列化可能特别有用。我们可以通过在我们想要序列化的类中提供两个方法来做到这一点。

private void writeObject(ObjectOutputStream out) throws IOException;

and

private void readObject(ObjectInputStream in) 
  throws IOException, ClassNotFoundException;

With these methods, we can serialize the unserializable attributes into other forms that we can serialize:

通过这些方法,我们可以将不可序列化的属性序列化为我们可以序列化的其他形式。

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;

    // setters and getters

    private void writeObject(ObjectOutputStream oos) 
      throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }

    private void readObject(ObjectInputStream ois) 
      throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}
public class Address {
    private int houseNumber;

    // setters and getters
}

We can run the following unit test to test this custom serialization:

我们可以运行以下单元测试来测试这个自定义序列化。

@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() 
  throws IOException, ClassNotFoundException {
    Person p = new Person();
    p.setAge(20);
    p.setName("Joe");

    Address a = new Address();
    a.setHouseNumber(1);

    Employee e = new Employee();
    e.setPerson(p);
    e.setAddress(a);

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile2.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(e);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream 
      = new FileInputStream("yourfile2.txt");
    ObjectInputStream objectInputStream 
      = new ObjectInputStream(fileInputStream);
    Employee e2 = (Employee) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(
      e2.getPerson().getAge() == e.getPerson().getAge());
    assertTrue(
      e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}

In this code, we can see how to save some unserializable attributes by serializing Address with custom serialization. Note that we must mark the unserializable attributes as transient to avoid the NotSerializableException.

在这段代码中,我们可以看到如何通过自定义序列化Address来保存一些不可序列化的属性。注意,我们必须将不可序列化的属性标记为transient以避免NotSerializableException.

4. Conclusion

4.结论

In this brief article, we reviewed Java serialization, discussed caveats, and learned how to do custom serialization.

在这篇简短的文章中,我们回顾了Java序列化,讨论了注意事项,并学习了如何进行自定义序列化。

As always, the source code used in this article is available over on GitHub.

一如既往,本文中使用的源代码可在GitHub上获得。