Android对象序列化

来源:互联网 发布:mac口红德国买便宜吗 编辑:程序博客网 时间:2024/06/05 01:19

简述

在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。

0 0
原创粉丝点击