[TOC]

1.从Serilizable说到transient

我们知道,如果一个对象需要序列化,那么需要实现Serilizable接口,那么这个类的所有非静态属性,都会被序列化。

注意:上面说的是非静态属性,因为静态属性是属于类的,而不是属于类对象的,而序列化是针对类对象的操作,所以这个根本不会序列化。下面我们可以实验一下:
实体类Teacher.class:

import java.io.Serializable;class Teacher implements Serializable {    public int age;    public static String SchoolName;    public Teacher(int age) {        this.age = age;    }    @Override    public String toString() {        return "Teacher{" +                "age=" + age +                '}';    }}

测试代码SerialTest.java,基本思路就是初始化的时候,静态属性SchoolName为"东方小学",序列化对象之后,将静态属性修改,然后,反序列化,发现其实静态变量还是修改之后的,说明静态变量并没有被序列化。

import java.io.*;public class SerialTest {    public static void main(String[] args) {        Teacher.SchoolName = "东方小学";        serial();        Teacher.SchoolName = "西方小学";        deserial();        System.out.println(Teacher.SchoolName);    }    // 序列化    private static void serial(){        try {            Teacher teacher = new Teacher(9);            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);            objectOutputStream.writeObject(teacher);            objectOutputStream.flush();        } catch (Exception exception) {            exception.printStackTrace();        }    }    // 反序列化    private static void deserial() {        try {            FileInputStream fis = new FileInputStream("Teacher.txt");            ObjectInputStream ois = new ObjectInputStream(fis);            Teacher teacher = (Teacher) ois.readObject();            ois.close();            System.out.println(teacher.toString());        } catch (IOException | ClassNotFoundException e) {            e.printStackTrace();        }    }}

输出的结果,证明静态变量没有被序列化!!!

Teacher{age=9}西方小学

2.序列化属性对象的类需要实现Serilizable接口?

突然想到一个问题,如果有些属性是对象,而不是基本类型,需不需要改属性的类型也实现Serilizable呢?

问题的答案是:需要!!!

下面是实验过程:

首先,有一个Teacher.java,实现了Serializable,里面有一个属性是School类型:

import java.io.Serializable;class Teacher implements Serializable {    public int age;    public School school;    public Teacher(int age) {        this.age = age;    }    @Override    public String toString() {        return "Teacher{" +                "age=" + age +                '}';    }}

School类型,不实现Serializable:

public class School {    public String name;    public School(String name) {        this.name = name;    }    @Override    public String toString() {        return "School{" +                "name='" + name + '\'' +                '}';    }}

测试代码,我们只测试序列化:

import java.io.*;public class SerialTest {    public static void main(String[] args) {        serial();    }    private static void serial(){        try {            Teacher teacher = new Teacher(9);            teacher.school = new School("东方小学");            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);            objectOutputStream.writeObject(teacher);            objectOutputStream.flush();        } catch (Exception exception) {            exception.printStackTrace();        }    }}

会发现报错了,报错的原因是:School不能被序列化,也就是没有实现序列化接口,所以如果我们想序列化一个对象,那么这个对象的属性也必须是可序列化的,或者它是transient修饰的。

java.io.NotSerializableException: com.aphysia.transienttest.School    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.aphysia.transienttest.SerialTest.serial(SerialTest.java:18)    at com.aphysia.transienttest.SerialTest.main(SerialTest.java:9)

当我们将School实现序列化接口的时候,发现一切就正常了...问题完美解决

3.不想被序列化的字段怎么办?

但是如果有一个变量不是静态变量,但是我们也不想序列化它,因为它可能是一些密码等敏感的字段,或者它是不那么重要的字段,我们不希望增加报文大小,所以想在序列化报文中排除该字段。或者改字段存的是引用地址,不是真正重要的数据,比如ArrayList里面的elementData

这个时候就需要使用transient 关键字,将改字段屏蔽。

当我们用transient修饰School的时候:

import java.io.Serializable;class Teacher implements Serializable {    public int age;    public transient School school;    public Teacher(int age) {        this.age = age;    }    @Override    public String toString() {        return "Teacher{" +                "age=" + age +                ", school=" + school +                '}';    }}
import java.io.Serializable;public class School implements Serializable {    public String name;    public School(String name) {        this.name = name;    }    @Override    public String toString() {        return "School{" +                "name='" + name + '\'' +                '}';    }}

执行下面序列化和反序列化的代码:

import java.io.*;public class SerialTest {    public static void main(String[] args) {        serial();        deserial();    }    private static void serial(){        try {            Teacher teacher = new Teacher(9);            teacher.school = new School("东方小学");            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);            objectOutputStream.writeObject(teacher);            objectOutputStream.flush();        } catch (Exception exception) {            exception.printStackTrace();        }    }    private static void deserial() {        try {            FileInputStream fis = new FileInputStream("Teacher.txt");            ObjectInputStream ois = new ObjectInputStream(fis);            Teacher teacher = (Teacher) ois.readObject();            ois.close();            System.out.println(teacher.toString());        } catch (IOException | ClassNotFoundException e) {            e.printStackTrace();        }    }}

执行结果如下,可以看到teacher字段反序列化出来,其实是null,这也是transient起作用了。
但是注意,transient只能修饰变量,但是不能修饰类和方法,

4.ArrayList里面的elementData都被transient 关键字修饰了,为什么ArrayList还可以序列化呢?

这里提一下,既然transient修饰了ArrayList的数据节点,那么为什么序列化的时候我们还是可以看到ArrayList的数据节点呢?
这是因为序列化的时候:

如果仅仅实现了Serializable接口,那么序列化的时候,肯定是调用java.io.ObjectOutputStream.defaultWriteObject()方法,将对象序列化。然后如果是transient修饰了该属性,肯定该属性就不能序列化。
但是,如果我们虽然实现了Serializable接口,也transient修饰了该属性,该属性确实不会在默认的java.io.ObjectOutputStream.defaultWriteObject()方法里面被序列化了,但是我们可以重写一个writeObject()方法,这样一来,序列化的时候调用的就是writeObject(),而不是java.io.ObjectOutputStream.defaultWriteObject()

下面的源码是ObjectInputStream.writeObject(Object obj),里面底层其实会有反射的方式调用到重写的对象的writeObject()方法,这里不做展开。

    public final void writeObject(Object obj) throws IOException {        // 如果可以被重写,那么就会调用重写的方法        if (enableOverride) {            writeObjectOverride(obj);            return;        }        try {            writeObject0(obj, false);        } catch (IOException ex) {            if (depth == 0) {                writeFatalException(ex);            }            throw ex;        }    }

ArrayList重写的writeOject()方法如下:

    private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException{        // Write out element count, and any hidden stuff        int expectedModCount = modCount;        // 默认的序列化对象的方法        s.defaultWriteObject();        // Write out size as capacity for behavioural compatibility with clone()        s.writeInt(size);        // Write out all elements in the proper order.        for (int i=0; i<size; i++) {            // 序列化对象的值            s.writeObject(elementData[i]);        }        if (modCount != expectedModCount) {            throw new ConcurrentModificationException();        }    }

我们可以看到,writeOject()里面其实在里面调用了默认的方法defaultWriteObject()defaultWriteObject()底层其实是调用改了writeObject0()ArrayList重写的writeOject()的思路主要是先序列化默认的,然后序列化数组大小,再序列化数组elementData里面真实的元素。这就达到了序列化元素真实内容的目的。

5.除了transient,有没有其他的方式,可以屏蔽反序列化?

且慢,问出这个问题,答案肯定是有的!!!那就是Externalizable接口。

具体情况:Externalizable意思就是,类里面有很多很多属性,但是我只想要一部分,要屏蔽大部分,那么我不想在大部分的属性前面加关键字transient,我只想标识一下自己序列化的字段,这个时候就需要使用Externalizable接口。

show me the code!

首先定义一个Person.java,里面有三个属性

  • age:年龄
  • name:名字(被transient修饰)
  • score:分数

实现了Externalizable接口,就必须实现writeExternal()readExternal()方法。

  • writeExternal:将需要序列化的属性进行自定义序列化
  • readExternal:将需要反序列化的属性进行自定义反序列化
import java.io.Externalizable;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectOutput;public class Person implements Externalizable {    public int age;    public transient String name;    public int score;    // 必须实现无参构造器    public Person() {    }    public Person(int age, String name, int score) {        this.age = age;        this.name = name;        this.score = score;    }    @Override    public void writeExternal(ObjectOutput out) throws IOException {        /*         * 指定序列化时候写入的属性。这里不写入score         */        out.writeObject(age);        out.writeObject(name);        out.writeObject(score);    }    @Override    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {        /*         * 指定序列化时候写入的属性。这里仍然不写入年龄         */        this.age = (int)in.readObject();        this.name = (String)in.readObject();    }    @Override    public String toString() {        return "Person{" +                "age=" + age +                ", name='" + name + '\'' +                ", score='" + score + '\'' +                '}';    }}

上面的代码,我们可以看出,序列化的时候,将三个属性都写进去了,但是反序列化的时候,我们仅仅还原了两个,那么我们来看看测试的代码:

import java.io.*;public class ExternalizableTest {    public static void main(String[] args) {        serial();        deserial();    }    private static void serial(){        try {            Person person = new Person(9,"Sam",98);            FileOutputStream fileOutputStream = new FileOutputStream("person.txt");            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);            objectOutputStream.writeObject(person);            objectOutputStream.flush();        } catch (Exception exception) {            exception.printStackTrace();        }    }    private static void deserial() {        try {            FileInputStream fis = new FileInputStream("person.txt");            ObjectInputStream ois = new ObjectInputStream(fis);            Person person = (Person) ois.readObject();            ois.close();            System.out.println(person);        } catch (IOException | ClassNotFoundException e) {            e.printStackTrace();        }    }}

测试结果如下,就可以发现其实前面两个都反序列化成功了,后面那个是因为我们重写的时候,没有自定义该属性的反序列化,所以没有是正常的啦...

Person{age=9, name='Sam', score='0'}

如果细心点,可以发现,有一个字段是transient修饰的,不是说修饰了,就不会被序列化么,怎么序列化出来了。

没错,只要实现了Externalizable接口,其实就不会被transient左右了,只会按照我们自定义的字段进行序列化和反序列化,这里的transient是无效的...

关于序列化的transient暂时到这,keep going~

此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~

技术之路不在一时,山高水长,纵使缓慢,驰而不息。

公众号:秦怀杂货店

©著作权归作者所有:来自51CTO博客作者秦怀杂货店的原创作品,如需转载,请注明出处,否则将追究法律责任

你的鼓励让我更有动力

赞赏

0人进行了赞赏支持

更多相关文章

  1. MySQL Load data多种使用方法
  2. CSS3中的box-sizing属性实例详解
  3. flex容器常用的四个属性
  4. flex的属性描述
  5. css之flex项目属性与商城首页布局实战
  6. flex项目属性,实战案例。
  7. 移动商城首页的页眉和页脚的布局和flex项目三个属性
  8. 关于ServiceNow平台 password 的知识总结
  9. 【案例】学习flex项目上的三个属性并尝试制作移动端京东首页

随机推荐

  1. php 免费的快递查询接口快递100
  2. php导入导出excel表格
  3. 可以将Eclipse配置为防止某些警告出现在P
  4. 在ASP.NET和WordPress之间共享身份验证
  5. Show correct URL without extension & r
  6. 将易趣物品/导入导入ZenCart
  7. Joomla 3.x“错误检出失败,出现以下错误:”
  8. 你能得到一个调用类的变量吗?
  9. 权限被拒绝:/var/www/abc/.htaccess pcfg_
  10. 提交一个表单(后台生成id),点击保存并一步