Android对象的深浅克隆及传递

有没有这样的需求?

  • 服务器端返回多级嵌套的引用类型数据
  • 客户端需要将数据再作处理,需要两份不同的数据

要怎样才能同时保存两份数据呢?

  • 不可能遍历一篇,重新一个对象一个对象new吧,这样很麻烦吧!!!
  • 直接赋值到另一个对象,再修改,可是引用类型赋值的只是内存引用地址,修改后会同步修改的,这必会导致数据错乱啊!!!

嗯,那我们来谈谈对象克隆及界面之间的对象传递吧!


对象克隆(复制)

Cloneable使用介绍

需求分析

我们有这样的一组数据

  • SchoolInfo 学校对象,包含了班级对象

    1
    2
    3
    4
    5
    6
    public class SchoolInfo{
    private int schoolID;
    private String schoolName;
    private List<ClassInfo> classInfos;
    //*** 省略 get set等方法
    }
  • ClassInfo 班级对象

    1
    2
    3
    4
    5
    public class ClassInfo{
    private int classID;
    private String className;
    //*** 省略 get set等方法
    }
  • 数据mSchoolInfo1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SchoolInfo mSchoolInfo1 = new SchoolInfo();
    mSchoolInfo1.setSchoolID(1);
    mSchoolInfo1.setSchoolName("测试第一大学");
    List<ClassInfo> datas = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
    ClassInfo data = new ClassInfo();
    data.setClassID(i + 1);
    data.setClassName("(第" + (2017 + i) + "级)");
    data.setStudentInfos(getStudentInfos());
    datas.add(data);
    }
    mSchoolInfo1.setClassInfos(datas);

现在我们要克隆一份mSchoolInfo1给mSchoolInfo2,来看看深浅克隆有什么区别吧!

Cloneable浅度克隆测试

  • SchoolInfo实现Cloneable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class SchoolInfo implements Cloneable{
    //省略其他代码
    @Override
    public SchoolInfo clone(){
    SchoolInfo schoolInfo = null;
    try {
    //浅复制
    schoolInfo = (SchoolInfo) super.clone();
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    return schoolInfo;
    }
    }
  • ClassInfo实现Cloneable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ClassInfo implements Cloneable{
    //省略其他代码
    @Override
    protected ClassInfo clone() {
    ClassInfo classInfo = null;
    try {
    classInfo = (ClassInfo) super.clone();
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    return classInfo;
    }
    }
  • 实现克隆操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //克隆
    SchoolInfo mSchoolInfo2 = mSchoolInfo1.clone();
    //
    //这里打印出来和mSchoolInfo1一模一样,没问题。
    //
    //修改mSchoolInfo1
    mSchoolInfo1.setSchoolName("我是修改后的测试第一大学");
    List<ClassInfo> datas = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
    ClassInfo data = new ClassInfo();
    data.setClassID(i + 1);
    data.setClassName("我是修改后的(第" + (2017 + i) + "级)");
    data.setStudentInfos(getStudentInfos());
    datas.add(data);
    }
    mSchoolInfo1.setClassInfos(datas);
    //这时候再打印mSchoolInfo2。
    //
    //结果如下
    //SchoolName = 测试第一大学 //这没问题
    //SchoolID = 1 //这也没问题
    //可是mSchoolInfo2的List<ClassInfo>打印出来却和mSchoolInfo1修改后的List<ClassInfo>一模一样。
    //

Cloneable深度克隆测试

  • SchoolInfo实现Cloneable,并给内部引用类型也调用clone()方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class SchoolInfo implements Cloneable{
    //省略其他代码
    @Override
    public SchoolInfo clone(){
    SchoolInfo schoolInfo = null;
    try {
    //深度复制
    schoolInfo = (SchoolInfo) super.clone();
    //主要和上面浅复制的区别代码如下
    List<ClassInfo> lists = new ArrayList<>();
    for (int i = 0; i < classInfos.size(); i++) {
    lists.add(classInfos.get(i).clone());
    }
    schoolInfo.classInfos = lists;
    //主要和上面浅复制的区别代码如上
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    return schoolInfo;
    }
    }
  • 实现克隆操作
    操作代码和浅复制一模一样。但输出结果
    1
    2
    3
    4
    5
    //结果如下
    //SchoolName = 测试第一大学 //这没问题
    //SchoolID = 1 //这也没问题
    //嗯,这次mSchoolInfo2的List<ClassInfo>打印出来却和mSchoolInfo1修改 《前》 的List<ClassInfo>一模一样。
    //

Cloneable使用总结

直接实现Cloneable获取的super.clone()

  • 如果super.clone()对象属性是基本数据类型,则可以正常复制
  • 如果是引用类型,则需要调用引用类型的clone()才能真正的复制
  • 如果有引用类型的字段,clone方法中没有调用引用类型对象的clone方法那就属于浅复制。

Serializable使用介绍

同样上面的需求,Serializable可以实现么,我们拭目以待?

Serializable复制对象实践

  • 序列化对象

    1
    2
    3
    4
    5
    6
    7
    public class SchoolInfo implements Serializable{
    //省略所有代码
    }

    public class ClassInfo implements Serializable{
    //省略所有代码
    }
  • 将对象序列化成流,将流序列化为对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(mSchoolInfo1);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
mSchoolInfo2 = (SchoolInfo) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

//测试及打印
//在mSchoolInfo1修改之前执行以上代码
//
// 打印结果
// mSchoolInfo2 和 mSchoolInfo1数据一模一样
//
// 修改mSchoolInfo1对象后
//
// 打印结果
// mSchoolInfo2 和 mSchoolInfo1修改前的数据一模一样
// 达到了同样的目的对不对!!!惊喜吧
//

Serializable使用总结

需要操作的对象实现Serializable后

  • 当前对象就可以直接转化为数据流
  • 同样,生成的数据流可以转换为实体对象
  • 对于数据层级比较深的数据,使用Serializable进度对象复制非常实用,推荐使用!!!

对象序列化传递

传递数据Bundle

Serializable实现

  • 实体类实现

    1
    2
    //implements Serializable即可
    class SchoolInfo implements Serializable{}
  • 对象传递

    1
    2
    3
    Bundle mBundle1 = new Bundle();
    mBundle1.putSerializable("SchoolInfo", mSchoolInfo1);//存
    mSchoolInfo2 = mBundle1.getSerializable("SchoolInfo");//取

Parcelable实现

  • 实体类实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    //implements Parcelable 并实现相应方法 (例:如下)

    class SchoolInfo implements Parcelable{

    //省略属性及get set方法

    public SchoolInfo(){}

    protected SchoolInfo(Parcel in) {
    schoolID = in.readInt();
    schoolName = in.readString();
    classInfos = in.createTypedArrayList(ClassInfo.CREATOR);
    }

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

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

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
    parcel.writeInt(schoolID);
    parcel.writeString(schoolName);
    parcel.writeTypedList(classInfos);
    }
    }
  • 对象传递

    1
    2
    3
    4
    5
    6
    Bundle mBundle2 = new Bundle();
    mBundle2.putParcelable("SchoolInfo",mSchoolInfo1);//存
    SchoolInfo2 = (SchoolInfo) mBundle2.getParcelable("SchoolInfo");//取

    Bundle.putParcelableArrayList(ParcelableKey, ArrayList<Parcelable>);// 数据 存
    Bundle.getParcelableArrayList(ParcelableKey);//数据 取

注意事项

  • 使用Bundle传递的对象,重新获取出来的对象还是和原始对象指向同一个地址 (前者修改,后者也会随之更改,需注意!!!)
  • 可结合上面两种方法克隆之后传递就不会出现解决这种问题。

本篇完

(注意:转载文章请注明来源[Android对象的深浅克隆及传递](http://goluck.top/2017/09/08/Android%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%B7%B1%E6%B5%85%E5%85%8B%E9%9A%86%E5%8F%8A%E4%BC%A0%E9%80%92/))