java.io.Serializable

一个标示性接口,接口中没有定义任何的方法或字段,仅用于标示可序列化的语义。


序列化时只对对象的状态进行保存,而不管对象的方法! 序列化前和序列化后的对象的关系为深拷贝

  •  Serialization(序列化)是一种将对象以一连串的字节描述的过程:首先要创建OutputStream(eg. FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中,调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。
  • Deserialization(反序列化)是一种将这些字节重建成一个对象的过程反序列的过程(即将一个序列还原成为一个对象):
    将一个InputStream(eg. FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()。

序列化 ID

序列化使用一个 hash,该 hash 是根据给定源文件中几乎所有东西(类路径、 方法名称、字段名称、字段类型、访问修改方法等) 通过运行 JDK serialver 命令计算出的,反序列化时将该 hash 值与序列化流中的 hash 值相比较,serialVersionUID 相同才能够被序列化。如果serialVersionUID类中没有指定,JVM将重新计算出serialVersionUID; 如果类几乎所有东西都相同仍然能够反序列化,否则不能。

java.io.InvalidClassException: com.noob.Person; local class incompatible: stream classdesc serialVersionUID = 7763748706987261198, local class serialVersionUID = 1279018472691830503
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:41)
	at com.noob.TestSerializable.main(TestSerializable.java:18)

如果在反序列化前修改了类路径(或者说JVM没有加载到原有的类),将报错:

eg. 修改了Person的类路径 由 com.noob 改为 com.noob.a

java.lang.ClassNotFoundException: com.noob.Person
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:677)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.a.TestSerializable.read(TestSerializable.java:41)
	at com.noob.a.TestSerializable.main(TestSerializable.java:18)

反序列化对象类型是序列化对象的父类或本身

经测试发现: 在反序列化时,仍旧用的是序列化时的对象类型
7b3dc37f2152c3cf7da947cfd195b92ea3a.jpg

package com.noob;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import lombok.Data;

import com.noob.JSONUtils;

public class TestSerializable {

    public static void main(String[] args) {
        try {
            /* 深复制 */
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream readIn = new ObjectOutputStream(bo);
            A testA = new A();
            testA.setA("a");
            testA.setB("b");
            testA.setC("c");
            readIn.writeObject(testA);
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream writeOut = new ObjectInputStream(bi);
            B b = B.class.cast(writeOut.readObject()); // 反序列化对象类型是序列化对象的父类或本身
            System.out.println(JSONUtils.toFormatJsonString(b));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Getter
@Setter
class A extends B implements Serializable {

    public A(String a) {
        super(a);
    }

    private static final long serialVersionUID = 1L;
    private String            a, b, c;
}

@Getter
@Setter
class B implements Serializable {
    private String a, b, c;

    public B(String a) {
        this.a = a;
    }
}

如果A、B 两个类没有父子关系,程序异常:

java.lang.ClassCastException: Cannot cast com.noob.A to com.noob.B
	at java.lang.Class.cast(Class.java:3369)
	at com.noob.TestSerializable.main(TestSerializable.java:27)

即使没有无参的构造方法,也是可以序列化及反序列化的!

父类的序列化与 Transient 关键字

  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
  • 如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错
    a0f596248ff9897ef36e14708723e82c7db.jpg

如果父类不实现序列化接口则需要有默认的无参的构造函数。 在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值。

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值
静态变量、成员方法、声明transient的变量 都不能被序列化和反序列化!

案例分析

eg. 使用 Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?
     根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化,形成类图如图 2 所示。
     图 2. 案例程序类图

    上图中可以看出,attr1、attr2、attr3、attr5 都不会被序列化,放在父类中的好处在于当有另外一个 Child 类时,attr1、attr2、attr3 依然不会被序列化,不用重复抒写 transient,代码简洁。

序列化允许重构

即使代码有一定的变化,但是serialVersionUID相同,仍然可以被反序列化当出现新字段时会被设为缺省值。

eg. 将原有的Person写入到文件中。再修改Person类:staS改为非 static,firstName 改为非 transient , 增加属性addField。(static与transient 是/非 可互相转换,经测试都不能被正确序列化和反序列化)

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.zhongan.fcp.pre.allin.common.utils.JSONUtils;

public class TestSerializable {
    public static void main(String[] args) {
        /* write(); */
        read();

    }

    private static void write() {
       /* try {
            Person ted1 = new Person("firstName", "lastName", 39);

            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(ted1);
            oos.close();
            System.out.println("---serialize obj end.----");
        } catch (Exception ex) {
            ex.printStackTrace();
        }*/
    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person ted2 = (Person) ois.readObject();
            ois.close();
            System.out.println(JSONUtils.toFormatJsonString(ted2));
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    private static final long serialVersionUID = -5941751315700344441L;
    private/**static**/String    staS             = "xxx"; // static 改为非 static 

    private/**transient**/String firstName;               // transient 改为非 transient 
    private String                lastName;
    private int                   age;
    private String                addField;                // 增加属性

}

b14a1bb4e66d7fc68cb0189c18f0d594c95.jpg

对象引用的序列化

一个类要能被序列化,该类中的所有引用对象也必须是可以被序列化的否则整个序列化操作将会失败,并且会抛出一个NotSerializableException,除非将不可序列化的引用标记为transient

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private static String     staS             = "xxx";                // static 改为非 static 

    private transient String  firstName;                               // transient 改为非 transient 
    private String            lastName;
    private int               age;
    private final Attribute   attribute        = new Attribute();

}

@Data
class Attribute {
    private String lock;
}
java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1539)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:36)
	at com.noob.TestSerializable.main(TestSerializable.java:18)
Caused by: java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)

序列化对象的引用关系

如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取。
 

JAVA的序列化机制采用了一种特殊的算法来保证序列化对象的关系:

所有保存到磁盘中的对象都有一个序列化编号。当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象(在本次虚拟机中)从未被序列化,系统才会将该对象转换成字节序列并输出如果对象已经被序列化,程序将直接输出一个序列化编号,而不是重新序列化。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

public class TestSerializable {
    public static void main(String[] args) {
        try {
            Person person1 = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person1);
            oos.flush(); //可选
            System.out.println(String.format("第一次将person1写入后的长度: %s", new File("D:/tempdata.ser").length()));
            person1.setAge(5555); //修改属性值
            oos.writeObject(person1);
            System.out.println(String.format("再次将person1写入后的长度: %s", new File("D:/tempdata.ser").length()));
            
            Person person2 = new Person(1000);
            oos.writeObject(person2);
            oos.close();
            System.out.println(String.format("初始化新person2写入后的长度:%s ", new File("D:/tempdata.ser").length()));
            
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/tempdata.ser"));
            System.out.println("---deserialization obj begin.----");

            Person deserialization_person1 = (Person) ois.readObject();
            System.out.println("deserialization_person1的age: " + deserialization_person1.getAge());
            System.out.println("deserialization_person1的内存: " + deserialization_person1);
            
            Person deserialization_person2 = (Person) ois.readObject();
            System.out.println("deserialization_person2的age: " + deserialization_person2.getAge());
            System.out.println("deserialization_person2的内存: " + deserialization_person2);
            System.out.println(String.format("deserialization_person1与deserialization_person2是否一致:%s ",
                    deserialization_person1 == deserialization_person2));
            
            Person deserialization_person3 = (Person) ois.readObject();
            System.out.println("deserialization_person3的内存: " + deserialization_person3);
            System.out.println(String.format("deserialization_person1与deserialization_person3是否一致:%s ",
                    deserialization_person1 == deserialization_person3));

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}

@Getter
@Setter
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
 * 
 */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

c031664efbd28414d1c72b3d717890cbc0d.jpg

测试结果发现:

  1. 即使修改了age的属性值,但是deserialization_person2反序列化时仍然是原初始化值。
  2. 第二次写入对象时文件只增加了 5 字节(存储新增引用和一些控制信息),并且反序列化时恢复引用关系, 两个对象是相等的。 

案例分析

流只能被读取一次。

eg. 如果序列化一个对象,反序列化时多次readObject,报错

        try {
            Person person = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.close();
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person person2 = (Person) ois.readObject();
            Person person3 = (Person) ois.readObject();

            ois.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

c258e3390084fbe5e2129cb4967a4a2d74d.jpg

 

eg. 有两个Teacher对象,它们的Student实例变量都引用了同一个Person对象,而且该Person对象还另外一个引用变量引用它。如下图所示: 

                                            

 这里有三个对象per、t1、t2,如果都被序列化,会存在这样一个问题,在序列化t1的时候,会隐式的序列化person对象。在序列化t2的时候,也会隐式的序列化person对象。在序列化per的时候,会显式的序列化person对象。所以在反序列化的时候,会得到三个person对象,这样就会造成t1、t2所引用的person对象不是同一个。显然,这并不符合图中所展示的关系,也违背了java序列化的初衷。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;

public class TestSerializable {
    public static void main(String[] args) {
        write();
        read();

    }

    private static void write() {
        try {
            Person person = new Person(1000);
            Student student = new Student("孙悟空", person);
            Teacher teacher1 = new Teacher("唐僧", student);
            Teacher teacher2 = new Teacher("菩提老祖", student);

            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.flush(); // 可选
            oos.writeObject(student);
            oos.flush();
            oos.writeObject(teacher1);
            oos.flush();
            oos.writeObject(teacher2);
            oos.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            System.out.println("---deserialization obj begin.----");

            Person person = (Person) ois.readObject();
            System.out.println("person的内存: " + person);
            Student student = (Student) ois.readObject();
            System.out.println("student中person内存: " + student.getPerson());
            System.out.println(String.format("student中person与直接反序列化的person是否一致:%s", student.getPerson() == person));
            System.out.println("-------------------------------------------");
            System.out.println("student的内存: " + student);

            Teacher teacher1 = (Teacher) ois.readObject();
            System.out.println("teacher1中student内存: " + teacher1.getStudent());
            System.out
                    .println(String.format("teacher1中student与直接反序列化的student是否一致:%s", teacher1.getStudent() == student));

            Teacher teacher2 = (Teacher) ois.readObject();
            System.out.println("teacher2中student内存: " + teacher2.getStudent());
            System.out
                    .println(String.format("teacher2中student与直接反序列化的student是否一致:%s", teacher2.getStudent() == student));

            ois.close();
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/**
 * 此处没有getter/setter 佐证序列和反序列化与此无关
 */
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

@Getter
@AllArgsConstructor
class Student implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Person            person;
}

@Getter
@AllArgsConstructor
class Teacher implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Student           student;
}

9148227fa8237ba6362af3ae5a308a81d29.jpg

敏感字段加密

在序列化过程中,虚拟机会试图调用对象类里的 writeObject readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream defaultWriteObject 方法以及 ObjectInputStream defaultReadObject 方法。

package com.noob;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;

public class Test implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            password         = "origin_password";

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private void writeObject(ObjectOutputStream out) {
        try {
            PutField putFields = out.putFields();
            System.out.println("原密码:" + password);
            password = "encryption";//此处可以通过公钥进行加密
            putFields.put("password", password);
            System.out.println("加密后的密码" + password);
            out.writeFields();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void readObject(ObjectInputStream in) {
        try {
            GetField readFields = in.readFields();
            Object object = readFields.get("password", "");
            System.out.println("要解密的字符串:" + object.toString());
            password = "origin_password";//此处可以通过私钥进行解密
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/result.obj"));
            out.writeObject(new Test());
            out.close();

            ObjectInputStream oin = new ObjectInputStream(new FileInputStream("D:/result.obj"));
            Test t = (Test) oin.readObject();
            System.out.println("解密后的字符串:" + t.getPassword());
            oin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

转载于:https://my.oschina.net/u/3434392/blog/1604537

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值