Java-Serializable

Serializable关键字平时我们应该很少有机会去使用到,平时的业务中不需要我们去做对象状态的保存,而且一般都是中间件或者框架内才使用到。但是关于Serializable应该要去理解和学习。

1. 怎么理解序列化

1.首先,在我们平时运行项目时,JVM处于运行中,此时工程内的对象是存活状态,因此这些对象的生命周期是会比JVM的生命周期短的。然后有的情况下,我们需要持久化一些特殊对象,以便将来能够读取被保存的对象。Serializable就是Java给出的解决方案。

2. 当我们去进行一些简单试验时发现,序列化时,会将状态保存为一组字节,在反序列化时又将字节组装成对象。因此整个过程是对字节的操作,不能使用字符操作API。同时,序列化时是保存的对象的瞬时状态,也就是成员变量的值,因此,静态变量就不会被保存下来。

2. 示例

2.1 Person类。

public class Person implements Serializable {

    private String name;

    private int age;

    private String phone;

    public Person(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    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 getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "[ name = " + name
                +"  age = " + age
                +"  phone = " + phone + " ]";
    }
}

2.2 序列化操作。写到本地txt文件中

public class OutputTest {

    public static void main(String[] args) throws Exception{
        File file = new File("D:" + File.separator + "person.txt");

        ObjectOutputStream oos = null;

        OutputStream outputStream = new FileOutputStream(file);

        oos = new ObjectOutputStream(outputStream);

        oos.writeObject(new Person("小刚", 23,"152444444444"));

        outputStream.close();

        oos.close();

    }
}

2.3 反序列化读取。

public class InputTest {

    public static void main(String[] args) throws Exception{
        File file = new File("D:" + File.separator + "person.txt");

        ObjectInputStream ois = null;

        InputStream inputStream = new FileInputStream(file);

        ois = new ObjectInputStream(inputStream);

        Object obj = ois.readObject();
        Person person = (Person) obj;

        ois.close();

        inputStream.close();

        System.out.println(person.toString());

    }

}
[ name = 小刚  age = 23  phone = 152444444444 ]

3. 从示例入手,思考和验证几个问题(都是个人观点和总结

3.1 为什么在序列化时都需要实现Serializable接口?

Serializable接口里没有任何代码,因此可以说Serializable接口只是一个标识作用,代表了你想序列化就要实现这个接口,只有实现这个接口之后才知道这个对象能被序列化。通过源码我们能发现什么呢?是可以发现一点东西的。

在序列化操作ObjectOutputStream里的writeObject中有如下代码:

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 {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }

我们可以发现几个事实:

1. 序列化操作会根据对象的实际类型不同执行不同的序列化操作。

2. String,数组,枚举可以直接进行序列化。

3. 那么自定义对象如何才能被识别可以进行序列化呢?就是靠是否实现了Serializable接口识别的,所以它是一个标识功能。

3.2 readObject时这个对象是怎么出来的?构造函数被执行了么?

我们在Person类里的够造函数加一句输出,来看看。

1. 当序列化时,输出如下语句:

构造函数被执行了

2. 反序列化时,输出如下语句:

[ name = 小刚  age = 23  phone = 152444444444 ]

可以看见在反序列化时构造函数没有被执行,反序列化时读取的是保存对象的完整字节数据,这个数据就是一个完整的对象,不需要再进行构造函数构造出来了。

3.3 序列化和反序列化之后的对象之间是什么关系,== 或者 equals?

基于此我们来做下试验。代码如下:

Person p1 = new Person("小刚", 23,"152444444444");

        //序列化到person1.txt
        File file1 = new File("D:" + File.separator + "person1.txt");

        ObjectOutputStream oos = null;

        OutputStream outputStream = new FileOutputStream(file1);

        oos = new ObjectOutputStream(outputStream);
        //写两次
        oos.writeObject(p1);
        oos.writeObject(p1);

        outputStream.close();

        oos.close();

        //序列化到person2.txt
        File file2 = new File("D:" + File.separator + "person2.txt");

        ObjectOutputStream oos2 = null;

        OutputStream outputStream2 = new FileOutputStream(file2);

        oos2 = new ObjectOutputStream(outputStream2);

        oos2.writeObject(p1);

        outputStream.close();

        oos2.close();

        //读取person1.txt中的内容
        ObjectInputStream ois1 = null;
        InputStream is1 = new FileInputStream(file1);
        ois1 = new ObjectInputStream(is1);
        Person p2 = (Person)ois1.readObject();
        Person p3 = (Person)ois1.readObject();
        ois1.close();
        is1.close();

        //读取person2.txt中的内容
        ObjectInputStream ois2 = null;
        InputStream is2 = new FileInputStream(file2);
        ois2 = new ObjectInputStream(is2);
        Person p4 = (Person) ois2.readObject();
        ois2.close();
        is2.close();

        //读取person1.txt中的内容
        ObjectInputStream ois3 = null;
        InputStream is3 = new FileInputStream(file1);
        ois3 = new ObjectInputStream(is3);
        Person p5 = (Person)ois3.readObject();
        ois3.close();
        is3.close();


        System.out.println("序列化前的对象: "+ p1);
        System.out.println("序列化Person1.txt对象p2: "+ p2);
        System.out.println("序列化Person1.txt对象p3: "+ p3);
        System.out.println("序列化Person2.txt对象p4: "+ p4);
        System.out.println("序列化Person1.txt对象p5: "+ p5);

        System.out.println("前后比较: " + (p1 == p2));
        System.out.println("前后比较: " + (p1 == p3));
        System.out.println("前后比较: " + (p1 == p4));
        System.out.println("前后比较: " + (p1 == p5));

        System.out.println("读取相同文件-----同一个流-----: " + (p2 == p3));
        System.out.println("读取相同文件-----不同流读-----: " + (p2 == p5));
        System.out.println("读取不同文件-----不同流读-----: " + (p2 == p4));
序列化前的对象: [ name = 小刚  age = 23  phone = 152444444444 ] Person@6d6f6e28
序列化Person1.txt对象p2: [ name = 小刚  age = 23  phone = 152444444444 ] Person@58372a00
序列化Person1.txt对象p3: [ name = 小刚  age = 23  phone = 152444444444 ] Person@58372a00
序列化Person2.txt对象p4: [ name = 小刚  age = 23  phone = 152444444444 ] Person@4dd8dc3
序列化Person1.txt对象p5: [ name = 小刚  age = 23  phone = 152444444444 ] Person@6d03e736
前后比较: false
前后比较: false
前后比较: false
前后比较: false
读取相同文件-----同一个流-----: true
读取相同文件-----不同流读-----: false
读取不同文件-----不同流读-----: false

可以总结几个结论:

1. 序列化前后的对象地址不同了。内容保持一致。

2. 同一个流中序列化时的对象再读取出来时是同一个对象。

3.4 引用类型变量是深拷贝还是浅拷贝?

添加一个引用类型变量Data

public class Data implements Serializable {

    private String num;

    public Data(String num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "num: " + num + "[ " + super.toString()+" ]";
    }
}
序列化前的对象: [ name = 小刚  age = 23  phone = 152444444444 Data = num: 第一个[ Data@45ee12a7 ] ] Person@135fbaa4
序列化Person1.txt对象p2: [ name = 小刚  age = 23  phone = 152444444444 Data = num: 第一个[ Data@2d98a335 ] ] Person@16b98e56
序列化Person1.txt对象p3: [ name = 小刚  age = 23  phone = 152444444444 Data = num: 第一个[ Data@2d98a335 ] ] Person@16b98e56
序列化Person2.txt对象p4: [ name = 小刚  age = 23  phone = 152444444444 Data = num: 第一个[ Data@7ef20235 ] ] Person@27d6c5e0
序列化Person1.txt对象p5: [ name = 小刚  age = 23  phone = 152444444444 Data = num: 第一个[ Data@4f3f5b24 ] ] Person@15aeb7ab
前后比较: false
前后比较: false
前后比较: false
前后比较: false
读取相同文件-----同一个流-----: true
读取相同文件-----不同流读-----: false
读取不同文件-----不同流读-----: false

从输出可以看出:是深拷贝,每一个的地址都不同,但是值是一样的。也就是说序列化是序列化的对象整个的关系网(个人理解)。

3.5 UID的理解?

3.5.1 测试方法级别的变动

Person类在序列化之后添加了一个普通的方法,代码如下:

    public void testMethod(){

    }
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -1060766909201190420, local class serialVersionUID = -1321389278186723799
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at InputTest.main(InputTest.java:23)

可以看见此时会出现反序列化失败的场景,因为UID改变了。

3.5.2 测试属性级别的变动

    private String data1;

    private int data2;
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -1060766909201190420, local class serialVersionUID = 5010219192652467572
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at InputTest.main(InputTest.java:23)

一样的场景。

3.5.3 添加一个默认的UID

    private static final long serialVersionUID = 1813544648148214900L;

    private String name;

    private int age;

    private String phone;

    private Data data;

    private String address;

    private int num;

在反序列化手动添加address和num两个成员变量。

[ name = 小刚  age = 23  phone = 152444444444 Data = num: 第二个[ Data@7cca494b ] address = null num = 0 ] Person@7ba4f24f

可以看出针对本地新增的改变的成员变量的值会有一个默认值给出。

3.5 序列化中的重构?

       当序列化之后,我们只要新增一个UID就可以对此类进行重构了,3.4 内可以看出可以新增成员变量,会给出默认值。那么如果是针对静态变量的改变呢?我们知道静态变量属于类,不属于对象,所以静态变量是不会被序列化到文件中去的。那么我们将静态变量改变为非静态变量,其实也就相当于进行了3.4的操作,是可以的。

那么我们可以将之前的非静态的成员变量改变为静态变量吗?代码如下:

[ name = null  age = 23  phone = 152444444444 Data = num: 第二个[ Data@7cca494b ] num = 0 ] Person@7ba4f24f

发现是可以的,依然是存在一个默认值。

3.5 transient?

transient字段标注了这个字段是不需要序列化到文件中的,具体不去操作了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值