JDK1.1源码学习之Serializable源码解析
- 1. Serializable接口结构和统计
- 2. Serializable接口源码翻译
- 3. Serializable接口说明
- 3.1 序列化
- 4. Serializable接口最佳实践
- 4.1 User类
- 4.2 测试类
- 5. Serializable接口总结
1. Serializable接口结构和统计
还记得之前我们说math包下面的BigDecimal类好欺负吧,
于是我们看来一下源码:
可以看到他继承自Number类(这里省略里面的内容,只看继承)。
}
而Number类又实现Serializable接口,于是乎我们就先来分析一下这个接口吧。
public abstract class Number implements java.io.Serializable {}
Serializable接口统计结果(IDEA Statistic插件统计)如下:
文件名
大小
总行数
源代码行数
注释行数
空行数
Serializable.java
3.58 KB
79
3
72
4
2. Serializable接口源码翻译
/** @(#)Serializable.java 1.8 2001/12/12
*
* 版权所有 2002年 Sun Microsystems,Inc.保留所有权利。
* SUN 所有者/机密。使用受许可条款约束。
*/
package java.io;
/**
* 类的序列化由 java.io.Serializable 接口来标识。
* 没有实现此接口的类是无法序列化或反序列化的。
* 可序列化的子类也是可以序列化的。
* 序列化接口没有方法或字段,只是用于标识可序列化。
*
* 为了使不可序列化类的子类允许序列化并且负责保存和恢复超类的公共、受保护和(若可访问)包字段的状态。
* 只有当它扩展的类具有可访问的无参构造函数来初始化类的状态时,子类才可以实现上面的功能。
* 在这种情况下,声明一个类 Serializable 是错误的。这会在运行时检测到错误。
*
* 在反序列化过程中,不可序列化类的字段将使用类的公共或受保护的无参数构造函数进行初始化。
* 可序列化的子类必须可以访问无参数构造函数。可序列化子类的字段将从流中恢复。
*
* 在遍历时,可能会遇到不支持 Serializable 接口的对象。此时,会抛出 NotSerializableException 异常,并识别不可序列化对象的类。
*
* 在序列化和反序列化过程中需要特殊处理的类必须实现具有这些签名的特殊方法:
*
* private void writeObject(java.io.ObjectOutputStream out)
* throws IOException
* private void readObject(java.io.ObjectInputStream in)
* throws IOException, ClassNotFoundException;
* writeObject 方法负责为其特定类写入对象的状态,以便相应的 readObject 方法可以恢复它。
* 可以通过调用 out.defaultWriteObject 来保存 Object 字段的默认机制。该方法不需要关注属于其超类或子类的状态。
* 可通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存对象的状态。
* readObject 方法负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来恢复对象的非静态和非瞬态字段的默认机制。
* defaultReadObject 方法使用流中的信息将保存在流中的对象的字段分配给当前对象中相应命名的字段。
* 这可以处理类已经演变为添加新字段的情况。该方法不需要关注属于其超类或子类的状态。
* 通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存对象的状态。
*
* @author unascribed(名可名,非常名)
* @version 1.8, 2001/12/12
* @see ObjectOutputStream
* @see ObjectInputStream
* @see ObjectOutput
* @see ObjectInput
* @see Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
3. Serializable接口说明
可以看到有效代码也就3行,却写了72行注释,对这个类描述的比较详细了。这里就不赘述上面翻译的内容了,大体就是说实现了这个类就可以序列化了,包括其子类。
在翻译的时候遇到了一个比较费解的地方,就是作者那一栏:unascribed
网上查了一下翻译是:未经描述、未归因。
于是又上网百度了一下
作者:unascribed
Java源码中的 Author: unascribed
表面上意思是这个类不知道作者是谁。但笔者认为不是不知道归属于谁,而是sun公司把它当做是免费的贡献于大众的一个东西,不属于任何人,也属于任何人。这就是开源的意义。
开源软件的出现为个人或小团队开启了大门,他们将自己赤身裸体的暴露出来,整个开源的社会体系中没有门槛,拥有的只有展现自我的激情,充斥着春秋战国时代的理论纷争,新的战场人人可以参与,胜败由自己决定 。
作者:刀客迪克 https://www.bilibili.com/read/cv12550424 出处:bilibili
很中意最后一个作者做出的解释,又加以自己的理解就为其做释:
名可名,非常名:可以言表的都是最肤浅的,所以一切尽在不言中。
其他名人对此的理解:
(林语堂) 可以说出来的道,便不是经常不变的道;可以叫的出来的名,便不是经常不变的名。
(陈鼓应) (同上) 可以用言辞表达的道,就不是常道;可以说的出来的名,就不是常名。
(释德清) 真常之道,本无相无名,不可言说。凡可言者,则非真常之道矣,故非常道。
试想一下,宇宙混沌之初万物皆为无名,人类为了便于记忆事物于是就开始起名,一旦事物有了名字就是容易改变了,比如古代对时间称为时辰、白昼、黑夜,现在虽然在用也不是一成不变。
3.1 序列化
好了我们回到主题,那么这个序列化又是什么意思呢?
我们知道所有的类实例化之后都是存在本地内存里面的,
但是处于网络的时代我们大部分的需求是将类的状态进行传递共享的(就是你本地new出来一个类对象,想将里面的属性值一起通过网络或其他形式传给另一台服务器进行处理)。
这个时候就需要序列化(将内存里面的对象以流的形式存储到文件或二进制流)了,而另一台服务器的工作就是反序列化(从文件或二进制流中读取数据流实例化到内存中)。
于是为了实现上面的功能,Java就定义了一个Serializable接口来标识哪些类对象可以用来序列化(状态可传输、保留)。
这里传输是用二进制流的形式,所以就要用到ObjectOutputStream和ObjectInputStream,看字面意思就知道是一个写入一个读出。输出到文件可以是任意后缀名,但是官方建议使用.ser后缀。
示意图如下,这里以文件为例,也可以存到数据库等:
比如,我本地有一个User类,new出来的实例对象属性有名字:Jack、年龄:18;一旦电脑关机这个对象就被销毁了,因为new出来的都是在内存中。这时候我希望保留到自己的磁盘或者传输到另一台服务器上,让服务器知道我new出来的属性值(状态),如果是你,你如何解决这个问题呢?
4. Serializable接口最佳实践
下面是最简单例子,如果想查看更多例子请参考文档:第3天(Serializable官方源码解析汇总)
4.1 User类
/** 版权所有(c) Jack魏 2022 - 2022
*/
package jack.io.jack;
import java.io.*;
/**
* @author Jack魏
* @version 1.0 2022-04-20 21:05
*/
public class User implements Serializable {
private String name;
private int 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;
}
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.2 测试类
package jack.io;import jack.io.jack.User;
import java.io.*;
/**
* @author Jack魏
* @version 1.0 2022-04-20 00:01
*/
public class SerializableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User();
user.setName("Jack魏");
user.setAge(18);
// 注意这里的目录,否则系统找不到指定的路径。
String fileName = "E:\\code\\tmp\\user.ser";
// 序列化
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
fos.close();
// 反序列化
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
System.out.println(obj);
}
}
生成的序列化文件如下(其实就是二进制文件):
5. Serializable接口总结