什么是可序列化?

168

在Java中,一个类被标记为Serializable是什么意思?或者说,在一般情况下呢...


12
以下是 Serializable 类的说明:通过实现 java.io.Serializable 接口,可以启用类的可序列化。未实现此接口的类将无法将其状态序列化或反序列化。所有可序列化类的子类型都是可序列化的。序列化接口没有任何方法或字段,只用于标识可序列化的语义。 - Ritwik Bose
41
如果你已经知道什么是序列化和反序列化,这是一段非常好的解释。(不是赞美)这样的定义只有在你对它有一定了解的情况下才能帮助你更好地理解技术问题。 - Xonatron
@RitwikBose,Java.io.Serializable是一个标签接口。 - linjiejun
如果你是Java新手,这是多态性的一个实际应用。如果一个类实现了一个接口,那么这个类可以被传递到任何其父类可以被接受为参数的地方。在这种情况下,子类可以被传递到像Results.writeOutput(Serializable s)这样的函数中。 - Nathan majicvr.com
13个回答

158

序列化是将一个对象从内存中持久化为一系列比特位,例如用于保存到磁盘上。反序列化则是相反的过程 - 从磁盘中读取数据以恢复/创建一个对象。

在您的问题背景下,这是一个接口,如果一个类实现了它,那么这个类可以被不同的序列化程序自动地进行序列化和反序列化。


2
请注意,除非明确标记为不需要序列化,否则所有未显式标记的字段也将被序列化。这意味着您可以通过序列化根对象轻松保存复杂的数据结构。 - Thorbjørn Ravn Andersen
1
那么,当我们谈论“对象”时,是指类实例化的对象,还是像程序集、文件等任何“软件对象”?如果是后者,那么它只是在程序和环境之间发送数据的标准化方式吗? - Vandrey
1
@Sunburst275 - 在这种情况下,这是类在内存中的内存表示 - 即类的实例(没有必要谈论程序集的序列化,因为它们通常作为文件存储在磁盘上,可以直接发送)。 - Oded

53
尽管大多数用户已经给出了答案,但我想为那些需要举例来解释这个想法的人添加一个示例:
假设你有一个类似下面这样的人员类:
public class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    public String firstName;
    public String lastName;
    public int age;
    public String address;

    public void play() {
        System.out.println(String.format(
                "If I win, send me the trophy to this address: %s", address));
    }
    @Override
    public String toString() {
        return String.format(".....Person......\nFirst Name = %s\nLast Name = %s", firstName, lastName);
    }
}

然后你可以创建一个如下所示的对象:

Person william = new Person();
        william.firstName = "William";
        william.lastName = "Kinaan";
        william.age = 26;
        william.address = "Lisbon, Portugal";

你可以将该对象序列化到多个流中。我会将其序列化到两个流中:

序列化到标准输出:

public static void serializeToStandardOutput(Person person)
            throws IOException {
        OutputStream outStream = System.out;
        ObjectOutputStream stdObjectOut = new ObjectOutputStream(outStream);
        stdObjectOut.writeObject(person);
        stdObjectOut.close();
        outStream.close();
    }

将对象序列化到文件中:

public static void serializeToFile(Person person) throws IOException {
        OutputStream outStream = new FileOutputStream("person.ser");
        ObjectOutputStream fileObjectOut = new ObjectOutputStream(outStream);
        fileObjectOut.writeObject(person);
        fileObjectOut.close();
        outStream.close();
    }

然后:

从文件反序列化:

public static void deserializeFromFile() throws IOException,
            ClassNotFoundException {
        InputStream inStream = new FileInputStream("person.ser");
        ObjectInputStream fileObjectIn = new ObjectInputStream(inStream);
        Person person = (Person) fileObjectIn.readObject();
        System.out.println(person);
        fileObjectIn.close();
        inStream.close();
    }

谢谢。我现在明白了。 - Vandrey
所以基本上,如果您在Java类的开头添加“implements Serializable”,则只能以其对象属性的形式将对象打印到文件中?好的... - Collin

43

这意味着类的实例可以被转换为字节流(例如保存到文件中),然后再次转换回类。重新加载可能发生在程序的不同实例甚至不同机器上。但是,序列化(无论哪种语言)涉及各种问题,特别是当您在可序列化对象内部具有对其他对象的引用时。


22

这里有一个关于Serialization的详细解释:(我的博客)

什么是Serialization?

Serialization是将对象状态序列化并以一系列字节的形式表示和存储的过程。可以将其存储在文件中。从文件中读取对象状态并恢复它的过程称为反序列化。

为什么需要Serialization?

在现代架构中,总是需要存储对象状态然后检索它。例如,在Hibernate中,要存储对象,我们应该使类可序列化。它的作用是一旦对象状态以字节形式保存,就可以将其传输到另一个系统,该系统可以从状态中读取并检索类。对象状态可以来自数据库、不同的JVM或单独的组件。借助Serialization,我们可以检索对象状态。

代码示例和说明:

首先让我们看一下Item类:

public class Item implements Serializable{

    /**
    *  This is the Serializable class
    */
    private static final long serialVersionUID = 475918891428093041L;
    private Long itemId;
    private String itemName;
    private transient Double itemCostPrice;
    public Item(Long itemId, String itemName, Double itemCostPrice) {
        super();
        this.itemId = itemId;
        this.itemName = itemName;
        this.itemCostPrice = itemCostPrice;
      }

      public Long getItemId() {
          return itemId;
      }

     @Override
      public String toString() {
          return "Item [itemId=" + itemId + ", itemName=" + itemName + ", itemCostPrice=" + itemCostPrice + "]";
       }


       public void setItemId(Long itemId) {
           this.itemId = itemId;
       }

       public String getItemName() {
           return itemName;
       }
       public void setItemName(String itemName) {
            this.itemName = itemName;
        }

       public Double getItemCostPrice() {
            return itemCostPrice;
        }

        public void setItemCostPrice(Double itemCostPrice) {
             this.itemCostPrice = itemCostPrice;
        }
}
在上面的代码中,可以看到Item类实现了Serializable接口。
这是一个使类可序列化的接口。
现在我们可以看到一个名为serialVersionUID的变量被初始化为Long类型。这个数字由编译器根据类和类属性的状态计算得出。这个数字将帮助jvm在从文件读取对象状态时识别对象状态。
因此,我们可以查看官方的Oracle文档:
“序列化运行时会为每个可序列化的类关联一个版本号,称为serialVersionUID。在反序列化过程中,该号码用于验证已序列化对象的发送者和接收者是否加载了与序列化兼容的对象类。如果接收者已经为该对象加载了不同serialVersionUID的类,则反序列化将导致InvalidClassException。可序列化的类可以通过声明一个名为“serialVersionUID”的字段(该字段必须是静态、最终的、long类型)来显式地声明其自己的serialVersionUID:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 如果可序列化的类没有显式声明serialVersionUID,则序列化运行时将为该类计算一个默认的serialVersionUID值,该值基于类的各个方面,如Java(TM)对象序列化规范所述。然而,强烈建议所有可序列化的类明确声明serialVersionUID值,因为默认的serialVersionUID计算对编译器实现可能因类细节而异,因此在反序列化期间可能导致意外的InvalidClassExceptions。因此,为了确保在不同的Java编译器实现之间具有一致的serialVersionUID值,可序列化的类必须声明一个显式的serialVersionUID值。此外,在可能的情况下,强烈建议显式声明serialVersionUID声明使用private修饰符,因为这样的声明仅适用于直接声明的类——serialVersionUID字段作为继承成员是没有用处的。”
如果您已经注意到,我们还使用了另一个关键字transient
如果一个字段不可序列化,它必须标记为transient。在这里,我们将itemCostPrice标记为transient,并不希望它被写入文件中。
现在让我们来看一下如何将对象的状态写入文件,然后从文件中读取它。
public class SerializationExample {

    public static void main(String[] args){
        serialize();
       deserialize();
    } 

    public static void serialize(){

         Item item = new Item(1L,"Pen", 12.55);
         System.out.println("Before Serialization" + item);

         FileOutputStream fileOut;
         try {
             fileOut = new FileOutputStream("/tmp/item.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut);
             out.writeObject(item);
             out.close();
             fileOut.close();
             System.out.println("Serialized data is saved in /tmp/item.ser");
           } catch (FileNotFoundException e) {

                  e.printStackTrace();
           } catch (IOException e) {

                  e.printStackTrace();
           }
      }

    public static void deserialize(){
        Item item;

        try {
                FileInputStream fileIn = new FileInputStream("/tmp/item.ser");
                ObjectInputStream in = new ObjectInputStream(fileIn);
                item = (Item) in.readObject();
                System.out.println("Serialized data is read from /tmp/item.ser");
                System.out.println("After Deserialization" + item);
        } catch (FileNotFoundException e) {
                e.printStackTrace();
        } catch (IOException e) {
               e.printStackTrace();
        } catch (ClassNotFoundException e) {
               e.printStackTrace();
        }
     }
}

以上是对象序列化和反序列化的示例。

为此,我们使用了两个类。为了序列化对象,我们使用了ObjectOutputStream。我们使用writeObject方法将对象写入文件。

为了反序列化,我们使用了ObjectInputStream,它从文件中读取对象。它使用readObject方法从文件中读取对象数据。

上述代码的输出结果如下:

Before SerializationItem [itemId=1, itemName=Pen, itemCostPrice=12.55]
Serialized data is saved in /tmp/item.ser
After DeserializationItem [itemId=1, itemName=Pen, itemCostPrice=null]

需要注意的是,反序列化对象中的itemCostPricenull,因为它没有被写入。


2
这是一个很好的解释!谢谢你!但说实话,这篇文章看起来比你博客上的那篇更清晰。无论如何,这对我帮助很大! - Vandrey

13

序列化意味着将一个对象的当前状态保存到流中,并从该流中恢复等价的对象。流作为对象的容器。


1
这个定义似乎更加精确。谢谢。 - Promise Preston

6

Serializable 被称为接口,但更像是在运行时对 Serialization 子系统的一个标志。它表示该对象可以被保存。除了不可序列化的对象和标记为 volatile 的对象之外,所有对象实例变量都将被保存。

想象一下,您的应用程序可以作为选项更改颜色,如果不将该设置保留在外部,则每次运行时都需要更改颜色。


6
这不是“编译器标志”。这是运行时序列化子系统的标志。 - user207421
请恕我直言,如果您并不确定这是正确的,为什么要写下来呢?而且您还遗漏了“transient”(瞬时的)这个关键词。总之,这是一个不太好的回答,抱歉。 - user207421
21
如果我没有写那个东西,我就不会被纠正,情况会更糟。所有其他答案都省略了“瞬态”的部分。你甚至没有写答案,只是在恶意挑衅他人。 - AphexMunky

4
从另一个角度来看,序列化是一种称为“标记接口”的接口。 标记接口是一种不包含方法声明的接口,但仅仅标识(或“标记”)实现该接口的类具有某些属性。 如果您了解多态性,这将非常有意义。 在Serializable标记接口的情况下,如果它的参数不实现该接口,则ObjectOutputStream.write(Object)方法将失败。 这是Java中的一个潜在错误,本可以写成ObjectOutputStream.write(Serializable)。强烈推荐阅读Joshua Bloch的《Effective Java》第37条目以了解更多信息。

4

序列化是一种将对象和数据存储或写入文件的技术。使用 ObjectOutputStreamFileOutputStream 类,这些类具有特定的方法来持久化对象,例如 writeObject();

想要更清晰地了解可以点击此处查看图示和更多信息。


3

序列化: 将对象的状态写入文件、网络或任何其他地方。(意味着将 Java 对象支持的形式转换为文件支持的形式或网络支持的形式)

反序列化: 从文件、网络或任何其他地方读取对象的状态。(意味着将文件/网络支持的形式转换为 Java 对象支持的形式)


1

仅补充其他答案并涉及普遍性。序列化有时被称为归档,例如在Objective-C中。


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