茌平做创建网站公司,网站备案 每年,彬县网招聘,剑灵代做装备网站java对象的序列化和反序列化是什么意思
1、序列化是干啥用的#xff1f; 序列化的原本意图是希望对一个Java对象作一下“变换”#xff0c;变成字节序列#xff0c;这样一来方便持久化存储到磁盘#xff0c;避免程序运行结束后对象就从内存里消失#xff0c;另外变换成字…java对象的序列化和反序列化是什么意思
1、序列化是干啥用的 序列化的原本意图是希望对一个Java对象作一下“变换”变成字节序列这样一来方便持久化存储到磁盘避免程序运行结束后对象就从内存里消失另外变换成字节序列也更便于网络运输和传播所以概念上很好理解
序列化把Java对象转换为字节序列。 反序列化把字节序列恢复为原先的Java对象。 而且序列化机制从某种意义上来说也弥补了平台化的一些差异毕竟转换后的字节流可以在其他平台上进行反序列化来恢复对象。
2、对象序列化的方式 在Java中如果一个对象要想实现序列化必须要实现下面两个接口之一
Serializable 接口 Externalizable 接口
那这两个接口是如何工作的呢两者又有什么关系呢我们分别进行介绍。
2.1 Serializable 接口 一个对象想要被序列化那么它的类就要实现此接口或者它的子接口。
这个对象的所有属性包括private属性、包括其引用的对象都可以被序列化和反序列化来保存、传递。不想序列化的字段可以使用transient修饰。
由于Serializable对象完全以它存储的二进制位为基础来构造因此并不会调用任何构造函数因此Serializable类无需默认构造函数但是当Serializable类的父类没有实现Serializable接口时反序列化过程会调用父类的默认构造函数因此该父类必需有默认构造函数否则会抛异常。
使用transient关键字阻止序列化虽然简单方便但被它修饰的属性被完全隔离在序列化机制之外导致了在反序列化时无法获取该属性的值而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性甚至完全不序列化某些属性或者加密序列化某些属性。
2.2 Externalizable 接口 它是Serializable接口的子类用户要实现的writeExternal()和readExternal() 方法用来决定如何序列化和反序列化。
因为序列化和反序列化方法需要自己实现因此可以指定序列化哪些属性而transient在这里无效。
对Externalizable对象反序列化时会先调用类的无参构造方法这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除或者把该构造方法的访问权限设置为private、默认或protected级别会抛出java.io.InvalidException: no valid constructor异常因此Externalizable对象必须有默认构造函数而且必需是public的。
2.3 对比 使用时你只想隐藏一个属性比如用户对象user的密码pwd如果使用Externalizable并除了pwd之外的每个属性都写在writeExternal()方法里这样显得麻烦可以使用Serializable接口并在要隐藏的属性pwd前面加上transient就可以实现了。如果要定义很多的特殊处理就可以使用Externalizable。
当然这里我们有一些疑惑Serializable 中的writeObject()方法与readObject()方法可以实现自定义序列化而Externalizable 中的writeExternal()和readExternal() 方法也可以他们有什么异同呢
readExternal(),writeExternal()两个方法这两个方法除了方法签名和readObject(),writeObject()两个方法的方法签名不同之外其方法体完全一样。 需要指出的是当使用Externalizable机制反序列化该对象时程序会使用public的无参构造器创建实例然后才执行readExternal()方法进行反序列化因此实现Externalizable的序列化类必须提供public的无参构造。 虽然实现Externalizable接口能带来一定的性能提升但由于实现ExternaLizable接口导致了编程复杂度的增加所以大部分时候都是采用实现Serializable接口方式来实现序列化。
3、Serializable 如何序列化对象
3.1 Serializable演示 然而Java目前并没有一个关键字可以直接去定义一个所谓的“可持久化”对象。
对象的持久化和反持久化需要靠程序员在代码里手动显式地进行序列化和反序列化还原的动作。
举个例子假如我们要对Student类对象序列化到一个名为student.txt的文本文件中然后再通过文本文件反序列化成Student类对象
1、Student类定义
public class Student implements Serializable {private String name;private Integer age;private Integer score;Overridepublic String toString() {return Student: \n name this.name \n age this.age \n score this.score \n;}// ... 其他省略 ...
}2、序列化
public static void serialize( ) throws IOException {Student student new Student();student.setName(CodeSheep);student.setAge( 18 );student.setScore( 1000 );ObjectOutputStream objectOutputStream new ObjectOutputStream( new FileOutputStream( new File(student.txt) ) );objectOutputStream.writeObject( student );objectOutputStream.close();System.out.println(序列化成功已经生成student.txt文件);System.out.println();
}3、反序列化
public static void deserialize( ) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream new ObjectInputStream( new FileInputStream( new File(student.txt) ) );Student student (Student) objectInputStream.readObject();objectInputStream.close();System.out.println(反序列化结果为);System.out.println( student );
}4、运行结果
控制台打印
序列化成功已经生成student.txt文件
反序列化结果为 Student: name CodeSheep age 18 score 1000
3.2 Serializable接口有何用
上面在定义Student类时实现了一个Serializable接口然而当我们点进Serializable接口内部查看发现它竟然是一个空接口并没有包含任何方法
试想如果上面在定义Student类时忘了加implements Serializable时会发生什么呢
实验结果是此时的程序运行会报错并抛出NotSerializableException异常
java对象的序列化和反序列化是什么意思
我们按照错误提示由源码一直跟到ObjectOutputStream的writeObject0()方法底层一看才恍然大悟
java对象的序列化和反序列化是什么意思
如果一个对象既不是字符串、数组、枚举而且也没有实现Serializable接口的话在序列化时就会抛出NotSerializableException异常
**原来Serializable接口也仅仅只是做一个标记用**它告诉代码只要是实现了Serializable接口的类都是可以被序列化的然而真正的序列化动作不需要靠它完成。
3.3 serialVersionUID号有何用
相信你一定经常看到有些类中定义了如下代码行即定义了一个名为serialVersionUID的字段
private static final long serialVersionUID -4392658638228508589L;
你知道这句声明的含义吗为什么要搞一个名为serialVersionUID的序列号
继续来做一个简单实验还拿上面的Student类为例我们并没有人为在里面显式地声明一个serialVersionUID字段。
我们首先还是调用上面的serialize()方法将一个Student对象序列化到本地磁盘上的student.txt文件
接下来我们在Student类里面动点手脚比如在里面再增加一个名为id的字段表示学生学号
public class Student implements Serializable {private String name;private Integer age;private Integer score;private Integer id;这时候我们拿刚才已经序列化到本地的student.txt文件还用如下代码进行反序列化试图还原出刚才那个Student对象
运行发现报错了并且抛出了InvalidClassException异常
java对象的序列化和反序列化是什么意思
这地方提示的信息非常明确了序列化前后的serialVersionUID号码不兼容
从这地方最起码可以得出两个重要信息
1、serialVersionUID是序列化前后的唯一标识符
2、默认如果没有人为显式定义过serialVersionUID那编译器会为它自动声明一个
第1个问题 serialVersionUID序列化ID可以看成是序列化和反序列化过程中的“暗号”在反序列化时JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对只有两者一致才能重新反序列化否则就会报异常来终止反序列化的过程。
第2个问题 如果在定义一个可序列化的类时没有人为显式地给它定义一个serialVersionUID的话则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID一旦像上面一样更改了类的结构或者信息则类的serialVersionUID也会跟着变化
所以为了serialVersionUID的确定性写代码时还是建议凡是implements Serializable的类都最好人为显式地为它声明一个serialVersionUID明确值
当然如果不想手动赋值你也可以借助IDE的自动添加功能比如我使用的IntelliJ IDEA按alt enter就可以为类自动生成和添加serialVersionUID字段十分方便
两种特殊情况 1、凡是被static修饰的字段是不会被序列化的
2、凡是被transient修饰符修饰的字段也是不会被序列化的
对于第一点因为序列化保存的是对象的状态而非类的状态所以会忽略static静态域也是理所应当的。
对于第二点就需要了解一下transient修饰符的作用了。
如果在序列化某个类的对象时就是不希望某个字段被序列化比如这个字段存放的是隐私值如密码等那这时就可以用transient修饰符来修饰该字段。
比如在之前定义的Student类中加入一个密码字段但是不希望序列化到txt文本则可以
public class Student implements Serializable {private static final long serialVersionUID -4392658638228508589L;private transient String name;private Integer age;private Integer score;private transient String passwd;这样在序列化Student类对象时password字段会设置为默认值null这一点可以从反序列化所得到的结果来看出
public static void serialize() throws IOException {Student student new Student();student.setName(CodeSheep);student.setAge(18);student.setScore(1000);student.setPasswd(123);4、实现Externalizable
public UserInfo() {userAge20;//这个是在第二次测试使用判断反序列化是否通过构造器
}
public void writeExternal(ObjectOutput out) throws IOException {// 指定序列化时候写入的属性。这里仍然不写入年龄out.writeObject(userName);out.writeObject(usePass);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {// 指定反序列化的时候读取属性的顺序以及读取的属性// 如果你写反了属性读取的顺序你可以发现反序列化的读取的对象的指定的// 属性值也会与你写的读取方式一一对应。因为在文件中装载对象是有序的userName(String) in.readObject();usePass(String) in.readObject();
}我们在序列化对象的时候由于这个类实现了Externalizable 接口在writeExternal()方法里定义了哪些属性可以序列化哪些不可以序列化所以对象在经过这里就把规定能被序列化的序列化保存文件不能序列化的不处理然后在反序列的时候自动调用readExternal()方法根据序列顺序挨个读取进行反序列并自动封装成对象返回然后在测试类接收就完成了反序列。
Externalizable 实例类的唯一特性是可以被写入序列化流中该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现。
writeExternal(ObjectOutput out) 该对象可实现 writeExternal 方法来保存其内容它可以通过调用 DataOutput 的方法来保存其基本值或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。 readExternal(ObjectInput in) 对象实现 readExternal 方法来恢复其内容它通过调用 DataInput 的方法来恢复其基础类型调用 readObject 来恢复对象、字符串和数组。
externalizable和Serializable的区别
1、实现serializable接口是默认序列化所有属性如果有不需要序列化的属性使用transient修饰。externalizable接口是serializable的子类实现这个接口需要重写writeExternal和readExternal方法指定对象序列化的属性和从序列化文件中读取对象属性的行为。
2、实现serializable接口的对象序列化文件进行反序列化不走构造方法载入的是该类对象的一个持久化状态再将这个状态赋值给该类的另一个变量。实现externalizable接口的对象序列化文件进行反序列化先走构造方法得到控对象然后调用readExternal方法读取序列化文件中的内容给对应的属性赋值。
5、序列化的受控和加强 5.1 约束性加持 从上面的过程可以看出序列化和反序列化的过程其实是有漏洞的因为从序列化到反序列化是有中间过程的如果被别人拿到了中间字节流然后加以伪造或者篡改那反序列化出来的对象就会有一定风险了。
毕竟反序列化也相当于一种 “隐式的”对象构造 因此我们希望在反序列化时进行受控的对象反序列化动作。
那怎么个受控法呢
答案就是 自行编写readObject()函数用于对象的反序列化构造从而提供约束性。
既然自行编写readObject()函数那就可以做很多可控的事情比如各种判断工作。
还以上面的Student类为例一般来说学生的成绩应该在0 ~ 100之间我们为了防止学生的考试成绩在反序列化时被别人篡改成一个奇葩值我们可以自行编写readObject()函数用于反序列化的控制
private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {// 调用默认的反序列化函数objectInputStream.defaultReadObject();// 手工检查反序列化后学生成绩的有效性若发现有问题即终止操作if( 0 score || 100 score ) {throw new IllegalArgumentException(学生分数只能在0到100之间);}
}比如我故意将学生的分数改为101此时反序列化立马终止并且报错
对于上面的代码为什么自定义的private的readObject()方法可以被自动调用跟一下底层源码来一探究竟跟到了ObjectStreamClass类的最底层是反射机制在起作用是的在Java里果然万物皆可“反射”滑稽即使是类中定义的private私有方法也能被抠出来执行了简直引起舒适了。
5.2 单例模式增强 一个容易被忽略的问题是可序列化的单例类有可能并不单例
举个代码小例子就清楚了。
比如这里我们先用java写一个常见的「静态内部类」方式的单例模式实现
public class Singleton implements Serializable {private static final long serialVersionUID -1576643344804979563L;private Singleton() {}private static class SingletonHolder {private static final Singleton singleton new Singleton();}public static synchronized Singleton getSingleton() {return SingletonHolder.singleton;}
}然后写一个验证主函数
public class Test2 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectOutputStream objectOutputStream new ObjectOutputStream(new FileOutputStream( new File(singleton.txt) ));// 将单例对象先序列化到文本文件singleton.txt中objectOutputStream.writeObject( Singleton.getSingleton() );objectOutputStream.close();ObjectInputStream objectInputStream new ObjectInputStream(new FileInputStream( new File(singleton.txt) ));// 将文本文件singleton.txt中的对象反序列化为singleton1Singleton singleton1 (Singleton) objectInputStream.readObject();objectInputStream.close();Singleton singleton2 Singleton.getSingleton();// 运行结果竟打印 false System.out.println( singleton1 singleton2 );}}
运行后我们发现反序列化后的单例对象和原单例对象并不相等了这无疑没有达到我们的目标。
解决办法是在单例类中手写readResolve()函数直接返回单例对象
private Object readResolve() {return SingletonHolder.singleton;
}
package serialize.test;import java.io.Serializable;public class Singleton implements Serializable {private static final long serialVersionUID -1576643344804979563L;private Singleton() {}private static class SingletonHolder {private static final Singleton singleton new Singleton();}public static synchronized Singleton getSingleton() {return SingletonHolder.singleton;}private Object readResolve() {return SingletonHolder.singleton;}
}这样一来当反序列化从流中读取对象时readResolve()会被调用用其中返回的对象替代反序列化新建的对象。