6.1java的序列化和反序列化

ゝ一世哀愁。 2024-04-08 10:21 125阅读 0赞

序列化和反序列化

序列化:将对象写入IO流中;
反序列化:从IO流中恢复对象;

序列化机制允许将实现序列化的Java对象转换为字节序列,并将字节序列保存在磁盘中,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使地对象可以脱离程序的运行而独立存在。
序列化的反序列化的目的是为了对象可以脱离程序单独的进行传输,或者持久化,在I/O相关开发中具有重要意义

初步使用Java实现序列化和反序列

  1. class Person1 implements Serializable {
  2. private String name;
  3. private String age;
  4. public Person1() {
  5. System.out.println("调用Person的无参构造函数");
  6. }
  7. public Person1(String name, String age) {
  8. this.name = name;
  9. this.age = age;
  10. System.out.println("调用Person的有参构造函数");
  11. }
  12. @Override
  13. public String toString() {
  14. // TODO 自动生成的方法存根
  15. return "Person{'name' :" + name + ",'age' :" + age + "}";
  16. }
  17. }
  18. // 去掉Person类实现的序列化接口
  19. public class Teacher implements Serializable {
  20. private String name;
  21. private Person1 person;
  22. public Teacher(String name, Person1 person) {
  23. this.name = name;
  24. this.person = person;
  25. }
  26. public static void main(String[] args) throws Exception {
  27. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Teacher.txt"));
  28. Person1 P = new Person1();
  29. Teacher t = new Teacher("mom", P);
  30. oos.writeObject(t);
  31. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Teacher.txt"));
  32. Teacher tc = (Teacher) ois.readObject();
  33. System.out.println(tc.toString());
  34. }
  35. }

a) 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化
b) 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
c) 如果想让某个变量不被序列化,使用transient修饰。
d) 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
e) 反序列化时必须有序列化对象的class文件。
f) 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
g) 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
i) 同一对象序列化多次(被写入同一个文件多次),只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。所以反序列化出来的对象都是同一个 如果被写到不同的文件,反序列化出来的对象将不是同一个
j) 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
k) 数组不能显式地声明serialVersionUID,因为它们始终都有默认的计算值,但是对于数组类,无需匹配serialVersionUID。
l) 可以通过序列化和反序列化的方式实现对象的深复制。

单例模式反序列问题

单例模式下,通过反序列化的方式创建对,每次创建的对象不是同一个对象,需要解决这个问题,需要重写readResolve方法,方法内部返回类中的单例对象,这样每次反序列化之后,都会找到类中的静态变量对象替换反序列的后的对象,注意是把序列化的对象丢弃了统一使用同一个对象

  1. class User implements Serializable {
  2. private static final User instance=new User();
  3. public String name;
  4. public int age;
  5. private User()
  6. {
  7. name="Sean";
  8. age=23;
  9. }
  10. public static User getInstance()
  11. {
  12. return instance;
  13. }
  14. private Object readResolve()
  15. {
  16. return instance;
  17. }
  18. }
  19. public class single {
  20. public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
  21. User a=User.getInstance();
  22. ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("user.txt"));
  23. os.writeObject(a);
  24. os.flush(); //利用管道将实例a序列化输出到文件user.txt中
  25. os.close();
  26. ObjectInputStream is=new ObjectInputStream(new FileInputStream("user.txt"));
  27. User b=(User) is.readObject(); //利用管道将user.txt中的实例反序列化输入进来作为对象b
  28. is.close(); //这实际上就等于重新生成了另一个单例对象
  29. b.name="Moon";
  30. b.age=31;
  31. System.out.println("a's name:"+a.name+" "+"a's age:"+a.age);
  32. System.out.println("b's name:"+b.name+" "+"b's age:"+b.age);
  33. //输出对象a、b的信息,不同则证明反序列化破解了单例模式
  34. }
  35. }

补充:反射模式下的单例问题

单例模式的类下,通过反射创建的对象,两次创建的对象不是同一个。解决办法没有版本解决反射下的单例攻击,只能通过枚举类型来禁用反射
可以通过对类对象加锁的方式观察类对象反射的时候被调用的次数,来发现攻击,立马报错。

  1. class SimpleSingleton {
  2. private static SimpleSingleton singleton;
  3. private static int count;
  4. private SimpleSingleton(){
  5. synchronized (SimpleSingleton.class) {
  6. if (count>0) {
  7. throw new RuntimeException("error:创建了两个实例!");
  8. }
  9. ++count;
  10. }
  11. }
  12. public static SimpleSingleton getInstance() {
  13. if (singleton == null) {
  14. singleton = new SimpleSingleton();
  15. }
  16. return singleton;
  17. }
  18. public void say() {
  19. System.out.println("j");
  20. }
  21. }
  22. public class test{
  23. public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
  24. Constructor<SimpleSingleton> declaredConstructor = SimpleSingleton.class.getDeclaredConstructor();
  25. declaredConstructor.setAccessible(true);
  26. // 反射获取count变量
  27. Field countField = SimpleSingleton.class.getDeclaredField("count");
  28. countField.setAccessible(true);
  29. // new了一次构造方法,count=1
  30. SimpleSingleton instance = SimpleSingleton.getInstance();
  31. // 重新设置count的值为0
  32. //countField.set(instance, 0);
  33. // newInstance底层原理也是调用构造方法来进行对象的实例化的
  34. // count=1
  35. SimpleSingleton simpleSingleton = declaredConstructor.newInstance();
  36. simpleSingleton.say();
  37. System.out.println(instance == simpleSingleton);
  38. }
  39. }

serialVersionUID 版本号

serialVersionUID 需要显示指定,默认的指定是不可以的。

  • 通过显示指定serialVersionUID ,那么不同端相同的类,虽然结构发生了变化,但是仍然可以正常解析,
  • 如果不想的UID就无法解析了
    在这里插入图片描述

对象的深浅复制

浅复制(复制引用但不复制引用的对象)深复制(复制对象和其引用对象)

通过clone方法实现深浅拷贝

clone实现拷贝,对象需要实现clonable接口,并且重写clone方法,如果内部存在引用类型,引用类型也需要实现clonable接口,并且重写clone方法。这种方法很复杂,需要保证所有的引用类型都复制到了父类object对象上

  1. class Address implements Cloneable {
  2. private String type;
  3. private String value;
  4. public String getType() {
  5. return type;
  6. }
  7. public void setType(String type) {
  8. this.type = type;
  9. }
  10. public String getValue() {
  11. return value;
  12. }
  13. public void setValue(String value) {
  14. this.value = value;
  15. }
  16. @Override
  17. protected Object clone() throws CloneNotSupportedException {
  18. return super.clone();
  19. }
  20. }
  21. class Personr implements Cloneable {
  22. private String name;
  23. private Integer age;
  24. private Address address;
  25. // @Override
  26. // protected Object clone() throws CloneNotSupportedException {
  27. // return super.clone();
  28. // }
  29. @Override
  30. protected Object clone() throws CloneNotSupportedException {
  31. Object obj=super.clone();
  32. Address a=((Personr)obj).getAddress();
  33. ((Personr)obj).setAddress((Address) a.clone());
  34. return obj;
  35. }
  36. public String getName() {
  37. return name;
  38. }
  39. public void setName(String name) {
  40. this.name = name;
  41. }
  42. public Integer getAge() {
  43. return age;
  44. }
  45. public void setAge(Integer age) {
  46. this.age = age;
  47. }
  48. public Address getAddress() {
  49. return address;
  50. }
  51. public void setAddress(Address address) {
  52. this.address = address;
  53. }
  54. }
  55. public class Tclone {
  56. public static void main(String[] args) throws CloneNotSupportedException {
  57. Personr p1=new Personr();
  58. p1.setAge(31);
  59. p1.setName("Peter");
  60. Personr p2=(Personr) p1.clone();
  61. System.out.println(p1==p2);//false
  62. p2.setName("Jacky");
  63. System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
  64. System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
  65. }
  66. @Test
  67. public void testShallowCopy() throws Exception{
  68. Address address=new Address();
  69. address.setType("Home");
  70. address.setValue("北京");
  71. Personr p1=new Personr();
  72. p1.setAge(31);
  73. p1.setName("Peter");
  74. p1.setAddress(address);
  75. Personr p2=(Personr) p1.clone();
  76. System.out.println(p1==p2);//false
  77. p2.getAddress().setType("Office");
  78. System.out.println("p1="+p1);
  79. System.out.println("p2="+p2);
  80. }
  81. }

通过序列反序列直接深拷贝

反序列和序列化天然支持深拷贝

其他方法

深拷贝方法总结

发表评论

表情:
评论列表 (有 0 条评论,125人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java 序列序列

    java 序列化和反序列化在文件上传,文件传输等方面都能用到,参考数据《java疯狂讲义》,同时结合自身工作经验的使用场景 基本概念 序列化机制允许将实现序列化的 Ja

    相关 Java序列序列

    遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题 a,什么叫序列化和反序列化 b,作用。为啥要实现这个 Serializable 接