Android的序列化之Serializable

一、Android/Java中为啥需要将对象进行序列化?

1、什么是序列化和反序列化?

序列化:将java堆内存中的对象转为二进制字节码;

反序列化:将二进制字节码转为java对象;

2、为什么要序列化(序列化转成二进制码的目的)?

一句话解释:可传输、可存储、持久化。

将内存中的对象的状态信息转换为可以存储或者传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或者持久性存储区。以后,可以从存储区中读取或者反序列化对象的状态,重新创建该对象。简单地说,序列化就是讲运行时的对象状态转换成为二进制,然后保存到流、内存或者通过网络传输给其他端。

(用通俗易懂的话讲:java对象只是针对java开发而言的,对象保存着一些属性等信息,我现在就需要保存这个对象的信息到我的如电脑硬盘上(因为对象存储在JVM堆内存中,如果JVM停止运行了,对象就会不存在了,体现了持久性),或者我要将这个对象的信息通过网络或其他方式传到其他地方(包括服务器,进程等等,体现了可传输性),这时候我不可能将对这个对象本身来进行这些操作吧,即使可以进行这些操作,别的操作系统认识你这个java对象的东西吗?但是呢,二进制不同,二进制在计算机的世界是通用的,这就解释了为什么一定要序列化了)

 

3、JDK提供的序列化方式有哪几种?

java中提供了Serializable、Exteranlizable两种接口;Android中提供了Serializable、Parcelabel

二、Serializable如何进行序列化的?

Serializable接口是一个空接口,其实就是给实现了该接口的对象打上一个标记,标记为该对象可被序列化。

具体的实现方法:JDK提供的ObjectOutputStream和ObjectInputStream来实现序列化和反序列化。

代码简单实现以下序列化和反序列化:(将我们的堆内存中的user对象持久化保存到电脑硬盘上)

User类实现了Serializable接口:

public class User implements Serializable {
    private String name;
    private int age;

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

    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class SerializableTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serializeUser();
        deserializeUser();
    }
    /**
     * 序列化
     * @throws IOException
     */
    private static void serializeUser() throws IOException {
        User user = new User("alive", 18);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test"));
        objectOutputStream.writeObject(user);
        objectOutputStream.close();
    }
    /**
     * 反序列化
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static void deserializeUser() throws IOException, ClassNotFoundException {
        File file = new File("D:\\test");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        User user = (User) objectInputStream.readObject();
        System.out.println("反序列化拿到对象状态:" + user.getName() + "  " + user.getAge());
    }
}

代码中"D:\\test"为电脑上的一个文件,可以不需要提前创建好,运行代码自动生成test文件;我们将user对象转成二进制流的方式保存到电脑上了,如下图:

用编辑器打开这个序列化保存硬盘的文件看看,如下图:(其实这已经是二进制码了)

运行以上代码,看看反序列化是否能拿到序列化保存的信息,打印日志如下:

 

三、Serializable序列化中需要注意的点之serialVersionUID

1、可序列化类如何生成serialVersionUID常量?

当我们在Android studio中对一个类实现Serializable的时候,是不会显示的生成serialVersionUID这个变量。那么如何生成一个serialVersionUID呢?

Android studio-->File-->Settings

设置好了之后再你实现了Serializable接口的类上按快捷键Alt+enter:就可以生成这个变量了。

这个long类型的常量随机生成的。(当然我们也可以自己手动写一个这个常量赋值)

2、serialVersionUID的作用?

序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

3、代码验证serialVersionUID的作用

操作如下:先注释上面的反序列方法,运行代码序列化保存到硬盘上,再把序列化的方法注释掉打开反序列的方法,并且修改user类的serialVersionUID值,加个1就行,然后在运行,就会报错:

四、Serializable序列化中需要注意的点之transient关键字

1、transient的作用?

transient的作用就是把这个字段的生命周期仅仅存于调用者的内存中而不会写入到磁盘里持久化。

2、代码测试transient的作用

上面代码仅仅给name字段增加transient关键字,忽略serialVersionUID的改动:

运行main方法:(序列化和反序列的两个方法代码在上面第二大点中有)

打印日志如下:

3、被transient关键字修饰过得变量真的不能被序列化嘛?

不是的。以上是基于serializable进行的序列化,实现这个类就可以进行序列化了。Java提供了另外一个接口:Exteranlizable

需要重写writeExternal和readExternal方法,手动指定哪些属性进行序列化,它的效率比Serializable高一些,并且可以决定哪些属性需要序列化(即使是transient修饰的,也能被序列化

4、静态变量能被序列化吗?被transient关键字修饰之后呢?

不能,不能

静态变量保存在内存的堆内存的方法区也叫静态区。被所有线程共享的一块区域。(static修饰的归类所有,序列化的是对象)

代码验证static修饰的变量:

同时执行序列化和反序列化:

执行结果能拿到name的值:(能拿到并不代表静态变量可以被序列化,继续代码验证:)

现在不同时执行序列化和反序列化,先执行序列化,后执行反序列化:

执行结果拿不到name的值了。

结论:为啥出现这种情况:唯一的解释就是同时执行序列化和反序列化方法的时候,序列化方法的时候给静态变量赋值了,而并不是序列化到二进制字节流,反序列化的时候直接从静态区取值。先运行执行序列化,再后运行执行反序列方法,后面执行并没有给静态变量赋值,所以取回null,就说明static不能被序列化。

五、Serializable序列化后增加或者减少字段能否反序列化成功?

1、思路一:把User中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。

结果:报错反序列化不成功

2、思路二:指定生成一个serialVersionUID,序列化保存,修改字段后反序列化

结果:反序列化成功

结论:如果没有明确指定serialVersionUID,序列化的时候会根据字段和特定的算法生成一个serialVersionUID,当属性有变化时这个id发生了变化,所以反序列化的时候就会失败。抛出“本地classd的唯一id和流中class的唯一id不匹配”。所以我们代码中最好指定一个自己的serialVersionUID。

六、一个对象要进行序列化,如果该对象成员变量是引用类型的,那这个引用类型也一定要是可序列化的,否则报错。

代码演示:增加一个Person类,先不实现serializable

在user类中增加Person引用类型变量:

运行如下:

结果报错:(给Person implement Serializable就可以了)

七、如果类是可序列化的,但是其父类是不可序列化的,那么反序列化后能否拿到父类的属性值?

不能,并且如果有有参构造,需要提供无参构造函数,才能通过编译,否则报错。上代码:

public class Person{//父类未实现Serializa

    private int height;
    private String hobby;
    public Person() {}//提供无参构造

    public Person(int height, String hobby) {
        this.height = height;
        this.hobby = hobby;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public String getHobby() {
        return hobby;
    }
    public void setHobby(String hobby) {
        this.hobby = hobby;
    }
}
public class User extends Person implements Serializable {
    private String name;
    private int age;
    public User() {}//提供无参构造

    public User(String name, int age) {
        super(160,"1111");
        this.name = name;
        this.age = age;
    }
    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;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

运行main方法的序列化和反序列化结果:

当父类Person implement Serializable,运行结果:

引申一个问题:如果父类实现了Serializable接口,子类继承了父类,没有实现Serializable接口,那么子类同样也是可被序列化的。

猜你喜欢

转载自blog.csdn.net/sunbinkang/article/details/110188999