Java 核心技术之序列化 Serializable

简介: 一、序列化概念将对象在内存中的状态保存下来,在需要的时候获取。序列化:将对象转换为字节序列,以便在网络传输或存储。反序列化:将字节序列转换为对象。

一、序列化概念

将对象在内存中的状态保存下来,在需要的时候获取。

序列化:将对象转换为字节序列,以便在网络传输或存储。

反序列化:将字节序列转换为对象。

二、序列化特点

类必须实现Serializable接口,父类未实现Serializable接口则父类不参与序列化,父类实现Serializable接口后子类不需要显式实现Serializable接口;

类的静态变量不参与序列化;

transient关键字修饰的变量不参与序列化;

序列化对象的成员变量serialVersionUID和反序列化的对象的成员变量serialVersionUID值必须一致;

对象的实例变量引用其他对象,引用的对象也将参与序列化

三、序列化步骤

创建OutputStream;

OutputStream outputStream = new FileOutputStream("object.txt");

创建ObjectOutputStream;

ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

调用ObjectOutputStream的writeObject方法进行序列化;

objectOutputStream.writeObject(obj);

关闭输出流;

objectOutputStream.close();



四、反序列化步骤

创建InputStream;

InputStream inputStream = new FileInputStream("object.txt");

创建ObjectInputStream;

ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

调用ObjectInputStream的readObject方法进行反序列化;

Object obj = objectInputStream.readObject();

关闭输出流;

objectInputStream.close();



五、序列化实例

5.1 实例一:类必须实现Serializable接口

Serializable接口是一个标记接口,本身并没有定义方法,但是类必须实现Serializable接口才能进行序列化,否则进行序列化时将抛出NotSerializableException异常。

public interface Serializable {
}

类User包含username和password两个成员变量,并未实现Serializable接口

package com.zzuhkp.javanote.serializable;
public class User{
    private String username;
    private String password;
    public User() {
        System.out.println("User no arg constructor");
    }
    public User(String username, String password) {
        System.out.println("User with arg constructor");
        this.username = username;
        this.password = password;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  • 测试类Main对User类中的成员变量赋值,进行序列化后然后反序列化打印User信息;
package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        writeObj(user);
        System.out.println(readObj());
    }
    public static void writeObj(Object obj) throws IOException {
        OutputStream outputStream = new FileOutputStream("object.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }
    public static Object readObj() throws IOException, ClassNotFoundException {
        InputStream inputStream = new FileInputStream("object.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
        return obj;
    }
}
  • 程序运行后抛出java.io.NotSerializableException异常,如下所示;
User with arg constructor
Exception in thread "main" java.io.NotSerializableException: com.zzuhkp.javanote.serializable.User
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
  at com.zzuhkp.javanote.serializable.Main.writeObj(Main.java:16)
  at com.zzuhkp.javanote.serializable.Main.main(Main.java:9)

5.2 实例二 未实现Serializable的父类不参与序列化

  • 创建类CommonVO,其只有一个成员变量id,该类未实现Serializable接口;
package com.zzuhkp.javanote.serializable;
public class CommonVO {
    private String id;
    public CommonVO(){
        System.out.println("CommonVO no arg constructor");
    }
    public CommonVO(String id) {
        System.out.println("CommonVO with arg constructor");
        this.id = id;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return "CommonVO{" +
                "id='" + id + '\'' +
                '}';
    }
}
  • 使User类继承CommonVO类;
package com.zzuhkp.javanote.serializable;
public class User extends CommonVO implements Serializable {
    //省略代码和实例一相同,未做改变
}
  • 修改测试类如下所示:
package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        user.setId("123");
        writeObj(user);
        User user1= (User) readObj();
        System.out.println(user1.toString());
        System.out.println("id:"+user1.getId());
    }
    //省略代码和实例一相同,未做改变
}
  • 程序运行结果如下所示,可以看到反序列化并没有把前面设置的父类成员变量id的值打印出来,并且可以看到反序列化时程序调用了User的无参构造方法创建了User的实例;
CommonVO no arg constructor
User with arg constructor
CommonVO no arg constructor
User{username='zzuhkp', password='123456'}
id:null

5.3 实例三 类的静态变量和transient修饰的变量不参与序列化;

  • 修改实例一中的User如下所示;
package com.zzuhkp.javanote.serializable;
import java.io.Serializable;
public class User  implements Serializable {
    public static String USER_TYPE="1";
    private transient String password;
    //省略代码和实例一中的User一致
}
  • 修改测试类如下所示;
package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        writeObj(user);
        User.USER_TYPE="2";
        User user1= (User) readObj();
        System.out.println(user1.toString());
        System.out.println(User.USER_TYPE);
    }
    //省略代码和实例一中的Main类保持一致
}

序列化后修改User类的静态变量USER_TYPE,程序运行,打印结果如下。反序列化后获取的静态变量的值和序列化之前并不保持一致,说明类的静态变量不参数序列化;transient修饰的password变量在反序列化后并未打印出序列化之前设置的值,说明transient修饰的成员变量也不参与序列化。

User with arg constructor
User{username='zzuhkp', password='null'}
2

5.4 实例四 序列化ID serialVersionUID必须一致

  • 修改User类如下所示,注意serialVersionUID的值为1L。
package com.zzuhkp.javanote.serializable;
import java.io.Serializable;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String password;
    public User() {
        System.out.println("User no arg constructor");
    }
    public User(String username, String password) {
        System.out.println("User with arg constructor");
        this.username = username;
        this.password = password;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  • 新建SocketClient类作为Socket连接的客户端,代码如下:
package com.zzuhkp.javanote.serializable;
import java.io.*;
import java.net.Socket;
public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket("127.0.0.1",8080);
        User user = new User("zzuhkp", "123456");
        OutputStream outputStream = socket.getOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(user);
        objectOutputStream.close();
    }
}
  • 新建项目,并在新项目中新建User类,User类的serialVersionUID为2L,其他内容和原项目User保值一致,代码如下;
package com.zzuhkp.javanote.serializable;
import java.io.Serializable;
public class User  implements Serializable {
    private static final long serialVersionUID = 2L;
    //省略代码和实例四上面User内容一致
}
  • 在新项目中新建SocketServer类作为Socket的服务端,代码如下:
package com.zzuhkp.javanote.serializable;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket serverSocket=new ServerSocket(8080);
        Socket socket=serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(obj.toString());
    }
}

分别运行SocketServer和SocketClient,SocketServer程序运行报InvalidClassException异常,并提示 stream classdesc serialVersionUID = 1, local class serialVersionUID = 2,说明serialVersionUID必须保持一致才能序列化成功。

Exception in thread "main" java.io.InvalidClassException: com.zzuhkp.javanote.serializable.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
  at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
  at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
  at com.zzuhkp.javanote.serializable.SocketServer.main(SocketServer.java:15)

六、序列化对象的writeObject方法和readObject方法


序列化时ObjectOutputStream将通过反射获取并尝试调用序列化对象的

private void writeObject(ObjectOutputStream objectOutputStream)

方法进行序列化,如果不存在该方法则

ObjectOutputStream

使用默认的序列化方式进行序列化。在writeObject方法中可以先调用

objectOutputStream.defaultWriteObject()

方法进行默认的序列化,然后调用ObjectOutputStream类的其他方法序列化;

反序列化时ObjectInputStream将通过反射获取并尝试调用序列化对象的


         

方法进行反序列化,如果不存在该方法则ObjectInputStream使用默认的反序列化方式进行反序列化。在readObject中可以先调用

objectInputStream.defaultReadObject()

方法进行默认的反序列化,然后调用ObjectInputStream类的其他方法反序列化;

修改User类如下所示:

package com.zzuhkp.javanote.serializable;
import java.io.*;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password;
    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException, ClassNotFoundException {
        objectOutputStream.defaultWriteObject();
        objectOutputStream.writeObject(this.password);
    }
    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        objectInputStream.defaultReadObject();
        this.password = (String) objectInputStream.readObject();
    }
    //省略代码和实例一中的User类代码相同
}
  • 修改实例一中的Main类如下所示:
package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        writeObj(user);
        User user1= (User) readObj();
        System.out.println(user1.toString());
    }
    //省略代码和实例一中的Main类代码一致
}
  • 运行结果如下,说明使用自定义的序列化方式对transient修饰的成员变量序列化已经生效。
User with arg constructor
User{username='zzuhkp', password='123456'}

七、序列化接口Externalizable


Externalizable接口继承Serializable接口,并声明了两个方法用于分别对对象进行序列化和反序列化,查看Externalizable的主要源码如下:

package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

一个类实现Externalizable接口,该类即可参与序列化,此时需要我们自己调用writeExternal方法进行序列化,调用readExternal方法进行反序列化,并且实现Serializable接口进行的默认序列化方式不会生效。

修改实例一中的User类如下:

package com.zzuhkp.javanote.serializable;
import java.io.*;
public class User implements Externalizable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String password;
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.username);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.username = (String) in.readObject();
    }
    //省略代码和实例一中User的代码一致
}

修改Main类的代码如下:

package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        writeObj(user);
        User user1= (User) readObj();
        System.out.println(user1.toString());
    }
    //省略代码和实例一中Main方法的代码一致
}

在User类中只对成员变量username进行序列化和反序列化,运行结果如下所示,说明我们自定义的序列化和反序列化已经生效:

User with arg constructor
User no arg constructor
User{username='zzuhkp', password='null'}

八、序列化对象的writeReplace和readResolve方法


如果序列化对象包含Object writeReplace() throws ObjectStreamException方法,在序列化时将使用writeReplace方法的返回值作为序列化的对象替代原序列化对象;

如果序列化对象包含Object readResolve() throws ObjectStreamException方法,在反序列化时将使用readResolve方法的返回值作为反序列化的对象替代原反序列化对象。

修改实例一中的User类如下所示,注意添加了writeReplace()和 readResolve()方法。

package com.zzuhkp.javanote.serializable;
import java.io.*;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String password;
    private Object writeReplace() throws ObjectStreamException{
        System.out.println("writeReplace() is called");
        User user=new User("writeReplace Name","123");
        return user;
    }
    private Object readResolve() throws ObjectStreamException{
        System.out.println("readResolve() is called");
        User user=new User("readResolve Name","456");
        return user;
    }
    public User() {
        System.out.println("User no arg constructor");
    }
    public User(String username, String password) {
        System.out.println("User with arg constructor");
        this.username = username;
        this.password = password;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

测试类Main类代码如下:

package com.zzuhkp.javanote.serializable;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("zzuhkp", "123456");
        writeObj(user);
        User user1= (User) readObj();
        System.out.println(user1.toString());
    }
    public static void writeObj(Object obj) throws IOException {
        OutputStream outputStream = new FileOutputStream("object.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }
    public static Object readObj() throws IOException, ClassNotFoundException {
        InputStream inputStream = new FileInputStream("object.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
        return obj;
    }
}

运行结果如下,说明反序列化的对象已经被readResolve()方法的返回值替代。

User with arg constructor
writeReplace() is called
User with arg constructor
readResolve() is called
User with arg constructor
User{username='readResolve Name', password='456'}
目录
相关文章
|
9月前
|
监控 Cloud Native Java
Quarkus 云原生Java框架技术详解与实践指南
本文档全面介绍 Quarkus 框架的核心概念、架构特性和实践应用。作为新一代的云原生 Java 框架,Quarkus 旨在为 OpenJDK HotSpot 和 GraalVM 量身定制,显著提升 Java 在容器化环境中的运行效率。本文将深入探讨其响应式编程模型、原生编译能力、扩展机制以及与微服务架构的深度集成,帮助开发者构建高效、轻量的云原生应用。
942 44
|
9月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
791 1
|
10月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
319 4
|
10月前
|
Java API Maven
2025 Java 零基础到实战最新技术实操全攻略与学习指南
本教程涵盖Java从零基础到实战的全流程,基于2025年最新技术栈,包括JDK 21、IntelliJ IDEA 2025.1、Spring Boot 3.x、Maven 4及Docker容器化部署,帮助开发者快速掌握现代Java开发技能。
1782 1
|
11月前
|
人工智能 Java
Java多任务编排技术
JDK 5引入Future接口实现异步任务处理,但获取结果不够灵活。Java 8新增CompletableFuture,实现异步任务编排,支持流式处理、多任务组合及异常处理,提升执行效率与代码可读性,简化并发编程复杂度。
255 0
|
8月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
409 1
|
8月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
384 1
|
10月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
1322 5
|
10月前
|
JavaScript 安全 前端开发
Java开发:最新技术驱动的病人挂号系统实操指南与全流程操作技巧汇总
本文介绍基于Spring Boot 3.x、Vue 3等最新技术构建现代化病人挂号系统,涵盖技术选型、核心功能实现与部署方案,助力开发者快速搭建高效、安全的医疗挂号平台。
451 3
|
9月前
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
837 0