先上通过 clone 深拷贝的使用步骤:
- model 先实现 Cloneable, 重写 clone 方法.
- model 调用 clone 就会生成一个 全新的对象.
- new 一个新数组 copyList, 遍历 原始数组 oriList, 一个一个添加到 copyList 即可.
talk is cheap, show me the code:
class MainKotlin {
@Test
fun test0() {
val oriList = ArrayList<Model>()
val model0 = Model(0, "name0")
val model1 = Model(1, "name1")
val model2 = Model(2, "name2")
oriList.add(model0)
oriList.add(model1)
oriList.add(model2)
val copyList = ArrayList<Model>()
oriList.forEach {
copyList.add(it.clone())
}
println("list = $oriList")
println("copyList = $copyList")
println("======== change name ========")
model0.name = "name0 changed"
println("list = $oriList")
println("copyList = $copyList")
}
class Model(): Cloneable {
var id: Int? = null
var name: String? = null
constructor(id: Int, name: String) : this() {
this.id = id
this.name = name
}
// 添加 try catch, 防止奇怪的异常
public override fun clone(): Model {
return try {
return super.clone() as? Model ?: Model()
} catch (e: CloneNotSupportedException) {
Model()
}
}
override fun toString(): String {
return "Model(id=$id, name=$name)"
}
}
}
输出结果:
可以看到 model0 更改 name 时, list 随之改变, copyList 没有改变, 成功~
起因:
今天在做单例的时候, 被数据的一些状态整懵了, 虽然想到了可以通过深拷贝解决问题, 但竟然直接使用了 newList.addAll(oldList) ... 结果又排查问题查了半天...才想到 list 中的 model 也是要完全 new 出来才行, model 里面那么多属性, 一个一个赋值太不优雅啦, 在网上查了下可以通过:
- clone 实现
- 实现序列化, 通过反序列化 new 出来对象
- 转成 json 字符串, 再转成对象(解析最好在子线程)
序列化深拷贝实现见 后续补充.
注意:
java的一个类,如果要使用 Cloneable 实现拷贝功能, 需要先实现这个接口, 然后重写Object的clone方法. 对于类中的引用类型的属性, 需要在clone方法中实现深拷贝, 否则就是浅拷贝.
属性中有引用对象的话, 引用对象也要实现 Cloneable, 然后这个引用对象也要调用 clone 方法才行...有点麻烦, 得封装一下才好用...后续带封装...
后续待优化:
可以把 集合 的深拷贝封装一个函数, 这样调用起来比较方便, 待执行任务...
后续补充
序列化实现, 有两种, Serializable 和 Parcelable
Serializable 存在文件读写的过程, Parcelable 是在内存中操作, 建议使用 Parcelabel.
1. Serializable 实现
当前需要类实现 Serializable 接口, 并且如果成员变量有引用类型, 此引用类型也需要实现 Serializable 接口, 这样深拷贝才能成功
java 代码
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class DeepCopyUtil {
public static Object deepClone(Serializable obj) {
Object anotherObj = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
anotherObj = ois.readObject();
} catch (Exception ex) {
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException localIOException3) {
Logger.e("deepClone error:"+localIOException3.getMessage());
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException localIOException4) {
}
}
}
return anotherObj;
}
}
kotlin 代码
fun <T : Serializable> T.deepCopy(obj: T?): T? {
if (obj == null) return null
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(obj)
oos.close()
val bais = ByteArrayInputStream(baos.toByteArray())
val ois = ObjectInputStream(bais)
@Suppress("unchecked_cast")
return ois.readObject() as T
}
2. Parcelable 实现
注意 parcelabel 需要在 android 环境中测试, 在 java 中会报异常 java.lang.RuntimeException: Stub!...
参考 https://farhanpatel.dev/index.php/2020/06/14/deep-clones-with-android-parcelable/
fun <T : Parcelable> deepClone(objectToClone: T): T? {
var parcel: Parcel? = null
return try {
parcel = Parcel.obtain()
parcel.writeParcelable(objectToClone, 0)
parcel.setDataPosition(0)
parcel.readParcelable(objectToClone::class.java.classLoader)
} finally {
//it is important to recyle parcel and free up resources once done.
parcel?.recycle()
}
}