Android序列化之 子类与父类
目录
# Java序列化之Serializable注意事项:
- 序列化时,只对对象的状态进行保存,不会对对象的方法进行保存
- 当一个父类实现序列化,子类自动实现序列化,不需要再次实现Serializable接口,但是,子类实现序列化,父类未实现序列化,父类数据不被保存
- 当一个对象的实例有变量引用其他对象,序列化该对象时也会把所引用对象进行序列化,如果引用对象没有实现序列化,则会抛出NotSerializableException异常
- 并非所有的对象都可以序列化
- 安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等,在序列化进行传输的过程中,这个对象的private等域是不受保护的
- 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配。
- serialVersionUID对类的标识,在序列化与反序列化的时候使用,序列化时会将此标识写入到序列化数据中,反序列化时会检测此标识,如果标识一致就说明序列化的类和当前类的版本是相同的,可以成功过反序列化,否则就说明当前类和序列化的类相比发生了某些变换,比如变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的,因此会抛出java.io.InvalidClassException。
- 如果不指定serialVersionUID则系统就会根据当前类的结构生成serialVersionUID,如果是两个版本的应用只是添加或删除了变量,而没有发生类似改变了类名,修改了类变量类型的非常规性改变则仍可反序列化成功
由上可知,父类实现了序列化编写一个可序列化的子类,可自动实现。那么父类未实现序列化子类该如何实现序列化。下面我们就来讲述这件事情。
对象被序列化的候其实就是将对象写入到文件中,写入的时候会调用writeObject(java.io.ObjectOutputStream out)方法,其中out就是写入的文件流,所以将想要保存的父类变量通过out写入文件就可以正常的实现序列化。
读取的时候会调用readObject(java.io.ObjectInputStream in)方法,其中in就是读取的文件流,从中就可以拿到序列化的时候写入的数据。
除此之外父类还需要实现一个无参的构造函数,否则会crash
例子
public abstract class SuperC {
int supervalue;
public SuperC(int supervalue) {
this.supervalue = supervalue;
}
public SuperC() {
}//增加一个无参的constructor
public String toString() {
return "supervalue: " + supervalue;
}
}
public class SubC extends SuperC implements Serializable {
int subvalue;
public SubC(int supervalue, int subvalue) {
super(supervalue);
this.subvalue = subvalue;
}
public String toString() {
return super.toString() + " sub: " + subvalue;
}
private void writeObject(java.io.ObjectOutputStream out)
throws IOException {
out.defaultWriteObject();//先序列化对象
out.writeInt(supervalue);//再序列化父类的域
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();//先反序列化对象
supervalue = in.readInt();//再反序列化父类的域
}
}
由上可知,如果父类未实现序列化,子类需要实现序列化,那么父类必须存在一个无参的构造函数,父类的变量,子类需要负责序列化
## Java语言实现Serializable public class Person implements Serializable { private static final long serialVersionUID = 123456789L; private int name;
public Person() {
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
## kotlin语言实现Serializable class Person() : Serializable{ companion object { private const val serialVersionUID = 123456789L }
var name: String? = null
}
# Android序列化之Parcelable
对于Android平台来说,还有一个自己的序列化机制:Parcelable,就性能而言Parcelable比Serializable要优胜很多,因为Parcelable毕竟是亲儿子,Android在底层给起做了优化,至于做了什么优化,下面我们再详细阐述,在此加一句,Google对自己的语言kotlin在1.1.4版本中新加了一个@Parcelize注解,用来快速的实现Parcelable序列化,可见Google对自己语言的重视。
Parcelable对于子类与父类的序列化机制跟Serializable是一样的
讲一讲Parcelable中的几个重要方法
writeToParcel
/**
* Flatten this object in to a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
public void writeToParcel(Parcel dest, @WriteFlags int flags);
该方法通过参数传回一个parcel对象,你需要的就是将要序列化的数据写入parcel中,Flags可能是0或者PARCELABLE_WRITE_RETURN_VALUE亦或者PARCELABLE_ELIDE_DUPLICATES
describeContents
/**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
* the return value of this method must include the
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
*
* @return a bitmask indicating the set of special object types marshaled
* by this Parcelable object instance.
*/
public @ContentsFlags int describeContents();
通过方法名就可以知道这个方法是负责文件描述的,通过阅读注释可以知道当文件在writeToParcel方法中传入的数据包含特殊类型的对象例如一个文件描述符,那么就必须包含CONTENTS_FILE_DESCRIPTOR位掩码。光看注释和解释是不是云里雾里的,那是肯定的,毕竟这涉及到文件类型封装,没有接触的小伙伴必然会蒙。其实可以这么理解,如果你所使用的parcel对象需要特殊的描述类型,那么这个方法你就需要传入CONTENTS_FILE_DESCRIPTOR才能将使用该描述。
Creator
/**
* Interface that must be implemented and provided as a public CREATOR
* field that generates instances of your Parcelable class from a Parcel.
*/
public interface Creator<T> {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
*
* @param source The Parcel to read the object's data from.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel source);
/**
* Create a new array of the Parcelable class.
*
* @param size Size of the array.
* @return Returns an array of the Parcelable class, with every entry
* initialized to null.
*/
public T[] newArray(int size);
}
可以看到Creator是一个接口,定义了createFromParcel和newArray两个方法,它是作为一个加载器存在,当反序列化的时候就需要用到它,在实现Parcelable序列化的时候必须在要实现序列化的类中添加一个名称为CREATOR的内部静态变量继承Parcelable.Creator
例:
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
createFromParcel
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
*
* @param source The Parcel to read the object's data from.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel source);
可以看到createFromParcel就是将序列化打包后的数据重新转成需要的对象。
newArray
/**
* Create a new array of the Parcelable class.
*
* @param size Size of the array.
* @return Returns an array of the Parcelable class, with every entry
* initialized to null.
*/
public T[] newArray(int size);
需要创建对应类的数组,数组的元素为null。
## Java语言实现Parceablepublic class Person implements Parcelable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
}
public Person() {
}
protected Person(Parcel in) {
this.name = in.readString();
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
## Kotlin语言实现Parcelable
@Parcelize
@SuppressLint("ParcelCreator")
data class Person(var name: String) : Parcelable
区别
- Parcelable是Android特有的,Serializable是Java的
- Parcelable性能比Serializable好,Serializable是通过IO实现的序列化,存储在硬盘上;Parcelable是读写在内存上,速度上就要优于Serializable,而且Serializable的实现是利用了反射技术,产生了大量的临时对象
- 使用Java语言的话Parcelable代码量比Serializable大很多,但是使用Kotlin语言使用Parcelable要远远比使用Serlizable代码量小
- 如果要将数据存储到磁盘上,在外界有变化的情况下Parcelable不能很好的保证数据的持续性,此时可以使用Serializable虽然性能低点
原则
- 尽量使用Parcelable进行序列化保证性能,而且Java有专门的差价进行Parcelable序列化已经很大的解决了代码量上的问题
- 如果涉及到在内存变动,外部持续编写等情况要使用Serializable以保证数据的准确性