简述
在android系统中,一个对象的可序列化可以通过两种方式来实现:一种是通过实现Serializable接口,另一种是通过实现Parcelable接口。其中Serializable接口是在Java中已有的,而Parcelable则是Android系统自有的。
首先我们先看看通过Serializable和Parcelable接口来实现对象的可序列化。然后在看看Serializable和Parcelable接口实现序列化有什么不同。
一、Serializable的使用
在这里我们实现了User类的可序列化,代码如下:
public class User implements Serializable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{ name = " + name + ", age = " + age + "}";
}
}
从上述的代码可以看到,如果需要通过Serializable接口来实现对象的可序列化,那是相当的简单,直接实现这个接口就行。由于Serializable接口是一个空接口,它只是起到标志一个类对象为可序列化的作用。
我们知道,通过Serializable接口实现的可序列化对象是可以保存在一个文件里的,并且可以通过读取这个文件将这个对象反序列化成对象。下面,我们就看看这个简单的例子。
//序列化操作,既将对象存入文件
private void serializable() {
User user = new User("lxb", 4); //创建一个对象
File file = new File(getFilesDir(), "user");
ObjectOutputStream oos = null ;
try {
if (!file.exists()) {// 如果这个文件不存在,则创建这个文件
file.createNewFile();
}
oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(user); //将上面创建的对象保存在这个文件中
} catch (Exception e) {
e.printStackTrace();
}finally {
if (null != oos) {
try {
oos.close();
oos = null;
file = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//反序列化操作,既将对象从这个文件中恢复(注意,这里对象实际上已经不是原来的对象,只不过它的值和原来一致)
private void deserializable() {
File file = new File(getFilesDir(), "user");
ObjectInputStream ois = null;
try {
if (file.exists()) {
ois = new ObjectInputStream(new FileInputStream(file));
User user = (User) ois.readObject();//读取对象
System.out.println(user); //显示这个对象
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (null != ois) {
try {
ois.close();
ois = null;
file = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
然后我们定义一个Activity来调用这两个函数
public class SerializableActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_serializable);
findViewById(R.id.btn_serializable).setOnClickListener(this);
findViewById(R.id.btn_deserializable).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_serializable:
serializable(); //点击这个按钮,将对象存入文件
break;
case R.id.btn_deserializable:
deserializable(); //点击这个按钮,将文件内存反序列化成一个对象
break;
default:
break;
}
}
}
在这里,我们先点击序列化这个按钮来将这个对象存入文件,然后点击反序列化按钮取出这个文件。结果如下。
org.alesjia.huntingapp I/System.out: User{ name = lxb, age = 4}
二、使用Serializable需要注意的问题
如果这个时候,我们需要给这个User添加一个地址字段,代码如下
public class User implements Serializable {
private String name;
private int age;
private String address; //新增
public User(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;//新增
}
//新增
public String getAddress() {
return address;
}
//新增
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{ name = " + name + ", age = " + age + ", address = " + address + "}"; //改动,新增地址
}
}
这个时候将序列化代码做如下改动
private void serializable() {
//User user = new User("lxb", 4);//原先代码改为如下
User user = new User("lxb", 4, "shanghai");
**** 其他代码不变
}
这个时候我们直接点击反序列化按钮,结果出现如下错误
org.alesjia.huntingapp W/System.err: java.io.InvalidClassException: org.alesjia.huntingapp.bean.User; Incompatible class (SUID): org.alesjia.huntingapp.bean.User: static final long serialVersionUID =-3061720370958576609L; but expected org.alesjia.huntingapp.bean.User: static final long serialVersionUID =-2709034500598358200L;
通过这个错误,我们可以看到,是由于类的兼容性问题导致对象不能从这个文件中反序列化出来。而类的兼容性是通过serialVersionUID的相同与否来判断的。
那么什么是serialVersionUID呢?通过查看Serializable接口的说明文档,我们发现有这么一段话。
* <p>Every serializable class is assigned a version identifier called a {@code
* serialVersionUID}. By default, this identifier is computed by hashing the
* class declaration and its members. This identifier is included in the
* serialized form so that version conflicts can be detected during
* deserialization. If the local {@code serialVersionUID} differs from the
* {@code serialVersionUID} in the serialized data, deserialization will fail
* with an {@link InvalidClassException}.
从上述的说明可以看到,这个serialVersionUID是一个版本标识符,它是通过对这个类成员进行hash计算生成的。也就是说,如果一个类的定义发生了变化,那么这个serialVersionUID会自动发生变化。那么有没有办法不让它发生变化呢?有的,我们可以自己在代码中指定serialVersionUID的值,如下所示。
private static final long serialVersionUID = 0L;//这个值可以自己指定
通过代码指定serialVersionUID 值,我们可以做到在序列化前后,即使类成员变量的变化(增加或者删除),也可以尽可能的将这个对象反序列化出来。
那么下面我们还是通过代码来看看如何实现。
1)修改User类实现,指定serialVersionUID
public class User implements Serializable {
private static final long serialVersionUID = 1L;//这里我们自己指定版本号,跟之前代码唯一区别。
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{ name = " + name + ", age = " + age + "}";
}
}
2)修改序列化的相关代码,然后将这个对象存入文件中。
private void serializable() {
User user = new User("lxb", 4);
***********
}
3)然后由于版本原因,我们需要给User类增加一个address字段,代码如下
public class User implements Serializable {
private static final long serialVersionUID = 1L;//注意,这里虽然增加了address字段,但是我们版本号不变
private String name;
private int age;
private String address;
public User(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "User{ name = " + name + ", age = " + age + ", address = " + address + "}";
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4)然后修改序列化相关代码,点击反序列化按钮生成对象。
private void serializable() {
User user = new User("lxb", 4, "shanghai");
***********
}
最后可以看到结果如下,从这个结果可以看到,我们可以从文件中反序列化成这个对象。虽然我们在序列化时(将对象写入文件时)类的成员和反序列化时类的成员不一致(增加了一个address成员),不过这个对象还是可以被反序列化出来的。
org.alesjia.huntingapp I/System.out: User{ name = lxb, age = 4, address = null}
三、Parcelable的使用
现在来看一个简单的例子。在这里定义一个Car类,Car类里面包含一个Engine类变量。Engine类和Car都实现了Parcelable接口。代码如下
public class Engine implements Parcelable {
private int type;
public Engine(int type) {
this.type = type;
}
@Override
public int describeContents() {
return 0;
}
//通过这个方法序列化该对象
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
}
public static final Parcelable.Creator<Engine> CREATOR = new Creator<Engine>() {
//通过这个方法来反序列化对象
@Override
public Engine createFromParcel(Parcel source) {
return new Engine(source);
}
@Override
public Engine[] newArray(int size) {
return new Engine[size];
}
};
private Engine(Parcel in) {
this.type = in.readInt();
}
@Override
public String toString() {
return "Engine { type = " + type + "}";
}
}
public class Car implements Parcelable {
private String brand;
private int speed;
private Engine engine;
public Car(String brand, int speed, Engine engine) {
this.brand = brand;
this.speed = speed;
this.engine = engine;
}
@Override
public int describeContents() {
return 0;
}
//通过该方法来序列化对象
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(brand);
dest.writeInt(speed);
dest.writeParcelable(engine, 0);
}
public static final Parcelable.Creator<Car> CREATOR = new Creator<Car>() {
//通过该方法来反序列化对象
@Override
public Car createFromParcel(Parcel source) {
return new Car(source);
}
@Override
public Car[] newArray(int size) {
return new Car[size];
}
};
private Car(Parcel in) {
this.brand = in.readString();
this.speed = in.readInt();
//注意,对于成员变量类型为非基本数据类型的(非内置类型)的,需要该类也实现Parcelable接口。
this.engine = in.readParcelable(this.getClass().getClassLoader());
}
@Override
public String toString() {
return "Car { brand = " + brand + ", speed = " + speed + ", engine = " + engine + "}";
}
}
四、Serializable和Parcelable的不同点
1.通过Serializable实现对象序列化非常简单,而Parcelable则需要写大量的代码
2.通过Serializable接口实现对象的反序列化需要通过类反射来进行对象变量的赋值,同时会产生大量的临时对象,可能引起频繁的GC,所以效率比较低
3.由于Parcelabe是通过自定义代码来实现序列化和反序列化的,所以效率比较高
4.Serializable为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Parcelable为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,在内存间数据传输时推荐使用Parcelable。