对象序列化

写在前面:

关于序列化的概念我曾在《Intent传递数据》这篇博文中第一次使用到,但是我并没有对它进行讲解,只是为了传递对象而直接使用了它。因此在这里有必要简单地说下何为序列化。

Serializable接口

在传递对象的时候,我们让对象去实现了一个Serializable接口,但是如果我们点开这个接口呢,就会发现它其实是个空的接口。实际上,仅仅实现Serializable接口虽然可行但不完整,完整的序列化方式应该如下:

public class User implements Serializable {
    private static final long serialVersionUID = 195773L;
    public int userId;
    public String userName;
    public boolean isMale;
    ......
}

可以看到我们还声明了一个静态常量serialVersionUID,而这个serialVersionUID是用来辅助序列化和反序列化过程的。原则上来讲,序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。

serialVersionUID的详细工作机制是这样,序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统就会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候就可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候反序列化就会报错InvalidClassException。

简单点说serialVersionUID就是类结构的标识,关系到我们反序列化能否成功,那么我们在传递对象的时候没有声明它为啥也没出问题呢?当然,一般来说我们应该手动指定serialVersionUID的值,但是在没有指定的情况下,系统会根据类的结构自动去生成它的hash值,从而保证了序列化和反序列化的可行性。因此,当我们没有手动指定serialVersionUID的时候,反序列化时当前类有所改变,比如增加或删除了某些成员变量,那么系统就会重新计算当前类的hash值并赋给serialVersionUID,这么一来就会和序列化数据中的serialVersionUID不一致,于是反序列化失败。所以手动指定serialVersionUID的值,可以很大程度避免反序列化过程失败。

值得注意的是,手动指定serialVersionUID也不是万能的,当成员变量在发生增删的情况下,虽然可以反序列化成功,但是也只是尽最大限度的去恢复数据。另一方面,如果类结构发生了非常规改变,比如修改类类名,修改了成员变量的类型,这种时候即便serialVersionUID验证通过,反序列化过程仍然会失败。最后在使用序列化时还应注意:

  • 静态成员变量属于类不属于对象,所以不会参与序列化过程
  • transient标记的成员变量不参与序列化过程

Parcelable接口

相比于Serializable的序列化方式, Parcelable的序列化方式使用起来较为繁琐。但Serializable是Java中的序列化接口,在序列化和反序列化过程中有大量I/O操作,因此效率较低,而Parcelable是Android中的序列化方式,效率较高。

下面是一个实现了序列化的User类,并且它含有一个实现了序列化的Book成员变量:

public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
        book = in.readParcelable(Book.class.getClassLoader());
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
        dest.writeParcelable(book, flags);
    }
}
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

虽然比起Serializable序列化方式要多了很多代码,但这些都可以通过AndroidStudio自动生成,因此也没有繁琐多少。我们先来看一下Parcel,Parcel对象内部包装了可序列化的数据,可以在Binder中自由传输。从上述代码中可以看出,在序列化过程中需要实现以下三个功能:

  • 通过writeToParcel方法来实现序列化功能,实际上的操作是借助Parcel中的一系列write方法来完成的
  • 通过CREATOR来实现反序列化功能,实际上的操作是借助Parcel中的一系列read方法来完成的,并在其内部标明了如何创建序列化对象和数组
  • 通过describeContents方法来完成内容描述功能,通常返回值都是0,仅当当前对象中存在文件描述符时,此方法返回1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值