class CSer {
private String name;
private int age; public CSer() {
} public CSer(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "CSer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
} public class Main {
public static void main(String[] args) {
// 初始化
CSer player = new CSer();
player.setName("niko");
player.setAge(18);
System.out.println(player); // 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("major")));) {
oos.writeObject(player);
} catch (IOException e) {
e.printStackTrace();
} // 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(new File("major").toPath()));) {
CSer player1 = (CSer) ois.readObject();
System.out.println(player1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

由于 CSer 没有实现 Serializbale 接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:

java.io.NotSerializableException: org.example.CSer
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.example.Main.main(Main.java:46)

ObjectOutputStreamwriteObject0() 方法。其部分源码如下:

// remaining cases
// 判断对象类型,调用对应方法进行序列化
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
// 如果对象不能被序列化,则抛出 NotSerializableException 异常
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}

ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

Seralizable

CSer 实现了 Serializable 接口,就可以序列化和反序列化了

class CSer implements Serializable {

ObjectOutputStream 为例,它在序列化的时候会依次调用 writeObject()writeObject0()writeOrdinaryObject()writeSerialData()invokeWriteObject()defaultWriteFields()

private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
// 获取对象的类,并检查是否可以进行默认的序列化
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize(); // 获取对象的基本类型字段的数量,以及这些字段的值
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
// 将基本类型字段的值写入输出流
bout.write(primVals, 0, primDataSize, false); // 获取对象的非基本类型字段的值
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
// 循环写入对象的非基本类型字段的值
for (int i = 0; i < objVals.length; i++) {
// 调用 writeObject0 方法将对象的非基本类型字段序列化写入输出流
try {
writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
}
// 如果在写入过程中出现异常,则将异常包装成 IOException 抛出
catch (IOException ex) {
if (abortIOException == null) {
abortIOException = ex;
}
}
}
}

ObjectInputStream 为例,它在反序列化的时候会依次调用 readObject()readObject0()readOrdinaryObject()readSerialData()defaultReadFields()

private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOException {
// 获取对象的类,并检查对象是否属于该类
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
} // 获取对象的基本类型字段的数量和值
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 从输入流中读取基本类型字段的值,并存储在 primVals 数组中
bin.readFully(primVals, 0, primDataSize, false);
if (obj != null) {
// 将 primVals 数组中的基本类型字段的值设置到对象的相应字段中
desc.setPrimFieldValues(obj, primVals);
} // 获取对象的非基本类型字段的数量和值
int objHandle = passHandle;
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
// 循环读取对象的非基本类型字段的值
for (int i = 0; i < objVals.length; i++) {
// 调用 readObject0 方法读取对象的非基本类型字段的值
ObjectStreamField f = fields[numPrimFields + i];
objVals[i] = readObject0(Object.class, f.isUnshared());
// 如果该字段是一个引用字段,则将其标记为依赖该对象
if (f.getField() != null) {
handles.markDependency(objHandle, passHandle);
}
}
if (obj != null) {
// 将 objVals 数组中的非基本类型字段的值设置到对象的相应字段中
desc.setObjFieldValues(obj, objVals);
}
passHandle = objHandle;
}

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

static transient 修饰的字段是不会被序列化。

ObjectStreamClass 部分源码:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
// 获取该类中声明的所有字段
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT; // 遍历所有字段,将非 static 和 transient 的字段添加到 list 中
for (int i = 0; i < clFields.length; i++) {
Field field = clFields[i];
int mods = field.getModifiers();
if ((mods & mask) == 0) {
// 根据字段名、字段类型和字段是否可序列化创建一个 ObjectStreamField 对象
ObjectStreamField osf = new ObjectStreamField(field.getName(), field.getType(), !Serializable.class.isAssignableFrom(cl));
list.add(osf);
}
} int size = list.size();
// 如果 list 为空,则返回一个空的 ObjectStreamField 数组,否则将 list 转换为 ObjectStreamField 数组并返回
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}

Externalizable

除了 Serializable 之外,Java 还提供了一个序列化接口 Externalizable

实现 Externalizable 接口的 CSer 类和实现 Serializable 接口的 CSer 类有一些不同:

1)新增了一个无参的构造方法。

使用 Externalizable 进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。否则的话,会抛出异常 InvalidClassException

2)新增了两个方法 writeExternal()readExternal(),实现 Externalizable 接口所必须的。如果不重写具体的 writeExternal()readExternal() 方法,反序列化后得到的对象字段都变成了默认值。

重写两个方法:

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
} @Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}

Externalizable 和 Serializable 都是用于实现 Java 对象的序列化和反序列化的接口,但是它们有以下区别:

①、Serializable 是 Java 标准库提供的接口,而 Externalizable 是 Serializable 的子接口;

②、Serializable 接口不需要实现任何方法,只需要将需要序列化的类标记为 Serializable 即可,而 Externalizable 接口需要实现 writeExternal 和 readExternal 两个方法;

③、Externalizable 接口提供了更高的序列化控制能力,可以在序列化和反序列化过程中对对象进行自定义的处理,如对一些敏感信息进行加密和解密。

transient 关键字用于修饰类的成员变量,在序列化对象时,被修饰的成员变量不会被序列化和保存到文件中。其作用是告诉 JVM 在序列化对象时不需要将该变量的值持久化,这样可以避免一些安全或者性能问题。但是,transient 修饰的成员变量在反序列化时会被初始化为其默认值(如 int 类型会被初始化为 0,引用类型会被初始化为 null),因此需要在程序中进行适当的处理。

transient 关键字和 static 关键字都可以用来修饰类的成员变量。其中,transient 关键字表示该成员变量不参与序列化和反序列化,而 static 关键字表示该成员变量是属于类的,不属于对象的,因此不需要序列化和反序列化。

在 Serializable 和 Externalizable 接口中,transient 关键字的表现也不同,在 Serializable 中表示该成员变量不参与序列化和反序列化,在 Externalizable 中不起作用,因为 Externalizable 接口需要实现 readExternal 和 writeExternal 方法,需要手动完成序列化和反序列化的过程。

serialVersionUID

serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。

1)添加一个默认版本的序列化 ID:

private static final long serialVersionUID = 1L。

2)添加一个随机生成的不重复的序列化 ID。

private static final long serialVersionUID = -2095916884810199532L;

3)添加 @SuppressWarnings 注解。该注解会为被序列化类自动生成一个随机的序列化 ID。

@SuppressWarnings("serial")

Seralizable的更多相关文章

  1. java笔记--关于克隆技术

    关于克隆 --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3884817.html"谢谢-- 1.假克隆 如: ObjectA ...

  2. .Net的基础概念

    1,参数传递. 默认都是按值传递(无论引用还是值类型),也就意味着传递参数的一个副本给方法.之后在方法体内对参数的更改,对原始参数没有影响. 使用ref/out可以按引用传递,直接影响原始参数变量.两 ...

  3. JAVA序列化与反序列化三种格式存取(默认格式、XML格式、JSON格式)

    什么是序列化 java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输.或者持久化存储到数据库或文件系统中:然后在需要的时候 ...

  4. Android开发艺术探索——第二章:IPC机制(上)

    Android开发艺术探索--第二章:IPC机制(上) 本章主要讲解Android的IPC机制,首先介绍Android中的多进程概念以及多进程开发模式中常见的注意事项,接着介绍Android中的序列化 ...

  5. spark之JDBC开发(实战)

    一.概述 Spark Core.Spark-SQL与Spark-Streaming都是相同的,编写好之后打成jar包使用spark-submit命令提交到集群运行应用$SPARK_HOME/bin#. ...

  6. Hibernate 再接触 悲观锁和乐观锁

    为什么取1248 二进制 CRUD 移位效率高 在并发和效率选择一个平衡点 一般不会考虑幻读 因为我们不会再一个事务里查询两次,(只能设置为seralizable) 悲观锁和乐观锁的前提是read-u ...

  7. 两种序列化的方法 Serializable Parcelable

    原文:https://blog.csdn.net/hacker_crazy/article/details/80840868 一.Serializable 1.Serializable 是java的序 ...

  8. mysql事务隔离级别测试

    隔离性mysql提供了4种不同的隔离级别以支持多版本并发控制(MVCC)较低级别的隔离通常可以执行更高的并发,系统的开销也更低read uncommited(未提交读)read commited(提交 ...

  9. Android中传递对象的三种方法

    Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! Android中,Activity和Fragment之间传递对象,可以通过将对象序列化并存入Bundle或者I ...

  10. SSH框架之Hibernate第一篇

    1.2Hibernate的概述: 1.2.1 什么Hibernate? Hibernate(开发源代码的对象关系映射框架)是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它 ...

随机推荐

  1. 代码随想录Day10

    232.用栈实现队列 请你仅使用两个栈实现先入先出队列.队列应当支持一般队列支持的所有操作(push.pop.peek.empty): 实现 MyQueue 类: void push(int x) 将 ...

  2. 5. 从0开始学ARM-MRS、MSR、寻址操作、原子操作原理

    一.程序状态寄存器访问指令 ARM微处理器支持程序状态寄存器访问指令,用于在程序状态寄存器和通用寄存器之间传送数据. MRS MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR) MRS指 ...

  3. RISC-V全志D1多媒体套件文章汇总

    提示 此开发板的任何问题都可以在我们的论坛交流讨论 https://forums.100ask.net/c/aw/d1/57 文章目录汇总 教程共计14章,下面是章节汇总: 第0章_RISC-V全志D ...

  4. Win32 插入符光标跟随的打字小程序

    1.先创建插入符光标 在WM_CREATE消息中 LRESULT OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam) { HDC hdc = GetDC ...

  5. 【YashanDB知识库】ODBC驱动类问题定位方法

    [标题]ODBC驱动类问题定位方法 [需求分类]故障分析 [关键字]ODBC [需求描述]由于我们的ODBC接口目前尚不完善,经常会遇见ODBC接口能力不足导致应用功能无法运行的问题,需要定位手段确定 ...

  6. 2023/11/15 NOIP 模拟赛

    T1 游戏 标签 尺取 线段树 单调队列 线段树进阶 思路 抽象题意,相当于有 \(t\) 个点,有 \(n\) 个下接 \(x\) 轴的矩形. 首先明显可以按照 \(c\) 排序,然后尺取. 写法 ...

  7. Mmdetection dataset pipline

    数据的加载顺序是上图(来自mmdetection官网)中的顺序进行,上图中只有一次padding,但是其实dataloader一共有两次padding,一次是pad,另外一次就是collect后,给模 ...

  8. AI工具合集

    以下工具来源于互联网,可能会失效,请参考使用 网红工具 名称 链接 说明   GPT-4 https://chat.openai.com/ 需要梯子,需要付费.功能最强大的聊天机 器人. 文心一言 h ...

  9. 个头小却很能“打”!合合信息扫描全能王推出A4便携式打印机

    个头小却很能"打"!合合信息扫描全能王推出A4便携式打印机   过去,为了打印一份清晰工整的材料,人们往往需要到专门的打印店或办公室.处理文件.对于销售.物流人员.工程师.医生.媒 ...

  10. 支付宝携手HarmonyOS SDK打造高效便捷的扫码支付体验

    背景 在日常的购物转账.生活缴费等在线支付中,用户在正式拉起支付界面前,均需要至少经历一次"识别"+两次"寻找",即识别归属应用.寻找应用.寻找扫码入口,才能完 ...