Skip to main content

🍵Java对象的序列化和反序列化

·475 words·3 mins
Yalois
Author
Yalois

知道了PHP的序列化,我竟然不会Java序列化,学!

先学个单词

Serialize v.序列化
英 [ˈsɪəriəlaɪz] 美 [ˈsɪriəlaɪz]

什么是对象的序列化
#

在Java中,序列化是指将对象转换为字节流的过程。序列化可以实现对象的持久化,转为字节流之后可以将其保存到文件、数据库或者在网络中传输。

**那什么是持久化呢?**持久化就是将内存中的数据保存起来使它能够长期存在。比如我把某个Java对象保存到本地的一个文件,下次使用的时候再反序列化出来用。

反序列就是逆过程,把字节流转化为对象。使字节流在内存中重新创建一个实际的Java对象。

下面是一个例子,不能说看完例子就学会实例化了,写代码的时候往往有很多情况和细节,这时候就需要多写一写代码,多思考了。(这是我对自己说的)不要只看文章前面

Java代码实现(例子)
#

//代码由GPT提供

import java.io.*;

// 定义一个可序列化的类
class Person implements Serializable {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class SerializationExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        // 序列化对象
        try {
            FileOutputStream fileOut = new FileOutputStream("person.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(person);
            out.close();
            fileOut.close();
            System.out.println("对象已序列化并保存到person.ser文件中");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化对象
        try {
            FileInputStream fileIn = new FileInputStream("person.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            Person deserializedPerson = (Person) in.readObject();
            in.close();
            fileIn.close();
            System.out.println("从person.ser文件中反序列化对象:" + deserializedPerson.name + " " + deserializedPerson.age);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

对象流
#

ObjectInputStream类和ObjectOutputStream类创建的对象称为对象输入流和对象输出流。

对象输出流使用writeObject(Object)方法将一个对象obj写入到文件。

对象输入流使用readObject()读取一个对象到程序中。

ObjectInputStream和ObjectOutputStream的构造方法如下:

ObjectInputStream(InputStream in);
ObjectOutputStream(OutputStream out);

需要和InputStream和OutputStream配合使用。

在使用对象流写入或者读入对象时,要保证对象是序列化的。

实现了Serializable接口的对象就是序列化对象。

Serializable是一个标记型接口,是不包含任何方法的接口。在class后面只需要implements一下就行。

序列化实现对象的克隆
#

克隆就是拷贝,拷贝又分为深拷贝和浅拷贝,之前看js的时候遇到过这个问题。序列化可以实现的是对象的深拷贝。

在Java中有好几种克隆对象的方式,序列化只是其中一种。

先看下面的代码

public class eq{
	public static void main(String[] args){
		Person a=new Person("小李","20");
		Person b=a;
		System.out.println(a);
		System.out.println(b);
	}
}
class Person{
	String name;
	String age;
	Person(String name,String age){
		this.name=name;
		this.age=age;
	}
}

image-20240612093931507

Person b=a;

这里把a赋值给b实际上是把a的引用赋值给了b。a和b指向同一块地址,依旧还是一个对象,对b的内容进行修改,是修改引用对象的数据,a的值也会改变。

有时候需要一个复制品,复制品的内容修改不会影响原来的实体。就可以用到克隆。

有人可能会想,我直接把原来对象的每一个属性的值都给新对象的属性赋值一遍就可以了,为什么还要克隆呢?

对象属性少可以这么操作,如果对象属性多并且对象里面还有对象,那就得深克隆了。

如果被序列化的对象中有的成员变量是一个对象,那这个对象也得也得实现Serializable接口。

import java.io.*;

class Person implements Serializable {
    String name;
    int age;
    Hat hat; //对象中的对象
    public Person(String name,int age,Hat hat)
    {
        this.name=name;
        this.age=age;
        this.hat=hat;
    }
}
class Hat{// implements Serializable{
    String color;
    public Hat(String color)
    {
        this.color=color;
    }
}
public class test {
    public static void main(String[] args) {
        Hat hat=new Hat("红色");
        Person a=new Person("Yalois",20,hat);
        try {
            FileOutputStream out=new FileOutputStream("Person.ser");
            ObjectOutputStream outObj=new ObjectOutputStream(out);
            outObj.writeObject(a);
            outObj.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

//这个代码会报错
java.io.NotSerializableException: Hat
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at test.main(test.java:31)
    
//String也是对象为什么不报错?
看看String的代码就知道了
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {...

这个时候把Hat改一下就好了。

class Hat implements Serializable{
    String color;
    public Hat(String color)
    {
        this.color=color;
    }
}

也就说序列化的时候要保证这个对象里的对象也实现了Serializable接口。才能序列化下来。

然后来看看克隆对象后新对象的区别

import java.io.*;

class Person implements Serializable {
    String name;
    int age;
    Hat hat;
    public Person(String name,int age,Hat hat)
    {
        this.name=name;
        this.age=age;
        this.hat=hat;
    }
}
class Hat implements Serializable{
    String color;
    public Hat(String color)
    {
        this.color=color;
    }
}
public class test {
    public static void main(String[] args) {
        Hat hat=new Hat("红色");
        Person a=new Person("Yalois",20,hat);
        System.out.println("克隆前");
        System.out.println(hat);
        System.out.println(a);
        try {
            FileOutputStream out=new FileOutputStream("Person.ser");
            ObjectOutputStream outObj=new ObjectOutputStream(out);
            outObj.writeObject(a);
            outObj.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream in=new FileInputStream("Person.ser");
            ObjectInputStream inObj=new ObjectInputStream(in);
            Person b=(Person) inObj.readObject();
            System.out.println("克隆新对象:");
            System.out.println(b.hat);
            System.out.println(b);
            inObj.close();
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}
克隆前
Hat@1b6d3586
Person@4554617c
克隆新对象:
Hat@6acbcfc0
Person@5f184fc6

可以发现,克隆后的对象的引用和原先的引用不一样,新对象是完全独立的存在。修改新对象对原来的对象没有影响。

细节
#

使某些属性不被序列化
#

可以使用 transient 关键字修饰

// 该属性不再被序列化和反序列化
private transient String name;

有关serialVersionUID
#

【Java 基础篇】serialVersionUID 详解-CSDN博客

参考文章
#

Java基础——对象的序列化(通俗易懂,排版优美)_通过序列化机制来创建对象什么意思是-CSDN博客

【WEB】Java JDBC反序列化 | 狼组安全团队公开知识库 (wgpsec.org)

Java对象持久化-CSDN博客

教材:Java2实用教程

【Java】对象的序列化和克隆详解_java 自带对象克隆-CSDN博客

[java中的标记接口(标签接口) - yanggb - 博客园 (cnblogs.com)](https://www.cnblogs.com/yanggb/p/10664155.html#:~:text=具体说的就是,标记接口是计算机科学中的一种设计思路,用于给那些面向对象的编程语言描述对象。,因为编程语言本身并不支持为类维护元数据,而标记接口可以用作描述类的元数据,弥补了这个功能上的缺失。 对于实现了标记接口的类,我们就可以在运行时通过反射机制去获取元数据。)

https://www.cnblogs.com/9dragon/p/10901448.html

Java的RMI介绍及使用方法详解 | w3cschool笔记

【Java 基础篇】serialVersionUID 详解-CSDN博客

编辑日期: 2024.06.14