1、拷贝与深拷贝
- 对象的拷贝,就是将原对象中的属性值拷贝到一个同类型(一般来说)的对象中去;
深拷贝是指在拷贝原对象当中属性值的同时,如果属性是引用类型,那么将该引用类型指向一块新的内存,避免新旧对象引用类型数据修改而相互影响
2、java的Cloneable接口
java对于所有的对象父类Object,提供了一个protected的clone方法,如果我们自己的类需要提供clone功能,需要实现一个空接口:Serializable并重写Object的clone方法,否则会报:CloneNotSupportedException
示例代码
public class MyInt implements Cloneable {
public int i;
public MyInt(int i) {
this.i = i;
}
/**
* 在jdk1.5之前,这里只能返回Object
* 由于jdk1.5之后引入的泛型,所以这里可以返回最终类型
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected MyInt clone() throws CloneNotSupportedException {
return (MyInt) super.clone();
}
@Override
public String toString() {
return "MyInt{" +
"i=" + i +
'}';
}
}
3、深拷贝的实现
1. 实现Cloneable接口并重写clone()方法
对于引用
需要正确的重写引用对象的clone()方法
public class MyInteger implements Cloneable {
MyInt myInt = new MyInt(0);
public MyInteger(int i) {
myInt = new MyInt(i);
}
@Override
protected MyInteger clone() throws CloneNotSupportedException {
return (MyInteger) super.clone();
}
@Override
public String toString() {
return "MyInteger{" +
"myInt=" + myInt +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
MyInteger myInteger = new MyInteger(1);
MyInteger myInteger1_clone = myInteger.clone();
myInteger.myInt.i = 15;
System.out.println(myInteger);
System.out.println(myInteger1_clone);
}
}
上面这段代码,输出结果为:
MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=15}}
为何调用clone后创建的对象的值,随着原对象的值的改变而改变了?
由于在MyInteger的clone()方法中,只是简单的调用了super.clone()创建了一个新对象返回,这样,myInteger.myInt与myInteger1_clone.myInt指向的是同一个对象
所以修改myInteger.myInt.i属性的值,自然会导致myInteger1_clone.myInt.i的值随着一起改变
将上述代码略微修改:
public class MyInteger implements Cloneable {
MyInt myInt = new MyInt(0);
public MyInteger(int i) {
myInt = new MyInt(i);
}
@Override
protected MyInteger clone() throws CloneNotSupportedException {
MyInteger clone = (MyInteger) super.clone();
clone.myInt = new MyInt(this.myInt.i);
return clone;
}
@Override
public String toString() {
return "MyInteger{" +
"myInt=" + myInt +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
MyInteger myInteger = new MyInteger(1);
MyInteger myInteger1_clone = myInteger.clone();
myInteger.myInt.i = 15;
System.out.println(myInteger);
System.out.println(myInteger1_clone);
}
}
程序输出:
MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=1}}
debug:
OJBK
需要循环调用引用对象的正确的clone()方法,一直到基本数据类型或者String
数组对象
一维数组
对于原始数据类型或者String的一维数组,可以直接调用数组的clone()方法来完成对象的copy
public static void main(String[] args) {
String[] strs = {"aa", "bb", "cc"};
String[] strs_clone = strs.clone();
strs[1] = "dd";
System.out.println(Arrays.toString(strs));
System.out.println(Arrays.toString(strs_clone));
}
程序输出:
[aa, dd, cc]
[aa, bb, cc]
OK,没有问题,但是对于引用类型的数组,这样类似的修改会使原对象和新对象的属性同时发生变更
public class ArraysClone implements Cloneable {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
@Override
protected ArraysClone clone() throws CloneNotSupportedException {
return (ArraysClone) super.clone();
}
@Override
public String toString() {
return "ArraysClone{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
ArraysClone ac = new ArraysClone();
ArraysClone ac_clone = ac.clone();
ac.myInts[1].i = 5;
System.out.println(ac);
System.out.println(ac_clone);
}
}
程序输出
[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]
[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]
对于引用类型的数组,需要在持有数组引用的对象的clone()方法中,对于新数组对象进行递归创建新对象并拷贝原对象的值----可以调用正确的clone()
public class ArraysClone implements Cloneable {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
@Override
protected ArraysClone clone() throws CloneNotSupportedException {
ArraysClone clone = (ArraysClone) super.clone();
clone.myInts = new MyInt[this.myInts.length];
for (int i = 0; i < this.myInts.length; i++) {
clone.myInts[i] = this.myInts[i].clone();
}
return clone;
}
@Override
public String toString() {
return "ArraysClone{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
ArraysClone ac = new ArraysClone();
ArraysClone ac_clone = ac.clone();
ac.myInts[1].i = 5;
System.out.println(ac);
System.out.println(ac_clone);
}
}
二维数组
public class MultiArraysClone implements Cloneable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "MultiArraysClone{" +
"intss=" + Arrays.deepToString(intss) +
'}';
}
@Override
protected MultiArraysClone clone() throws CloneNotSupportedException {
MultiArraysClone clone = (MultiArraysClone) super.clone();
clone.intss = this.intss.clone();
return clone;
}
public static void main(String[] args) throws CloneNotSupportedException {
MultiArraysClone multiArraysClone = new MultiArraysClone();
MultiArraysClone multiArraysClone_clone = multiArraysClone.clone();
multiArraysClone.intss[1][0] = 99;
multiArraysClone.intss[1][1] = 99;
System.out.println(multiArraysClone);
System.out.println(multiArraysClone_clone);
}
}
程序输出
MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}
MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}
为什么对于基本数据类型,二维数组和一维数组的clone后结果不一致呢
下面是在我电脑上对于二维数组clone后的debug信息
二维数组
通过对象信息可以清晰看到,对于二维数组执行clone(),数组自身引用已经改变,但是数组的第一个维度的引用依旧指向原数组的引用
由于java并不存在真正意义上的二维数组,二维数组的实现是一个以一维数组为元素的一维数组,所以,对于二维数组的clone()行为的理解,与上面对象的一维数组的clone()方式是一致的
其他更高维度的数组同:如果需要对高维数组执行深拷贝,需要最终递归对于构成这个高维数组的每个一维数组执行深拷贝。
2. 拷贝构造器和拷贝工厂
拷贝构造器是一个参数为该类对象的构造器。
拷贝工厂是一个类似于拷贝构造器的静态工厂方法。
public class Foo {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
public Foo() {
}
public Foo(Foo src) {
for (int i=0; i<src.myInts.length; i++) {
this.myInts[i] = new MyInt(src.myInts[i].i);
}
}
public static Foo deepCopy(Foo src) {
Foo dest = new Foo();
for (int i=0; i<src.myInts.length; i++) {
dest.myInts[i] = new MyInt(src.myInts[i].i);
}
return dest;
}
@Override
public String toString() {
return "Foo{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) {
Foo src = new Foo();
Foo constructor_clone = new Foo(src);
Foo factory_clone = Foo.deepCopy(src);
src.myInts[1].i = 99;
System.out.println(src);
System.out.println(constructor_clone);
System.out.println(factory_clone);
}
}
其本质实现与clon()一致,但是其好处是:
(引自effective java --- 谨慎地覆盖clone)
拷贝构造器的做法,及其静态工厂方法的变形,都比Cloneable/clone方法具有更多的优势,它们不依赖于某
一种很有风险的、语言之外的对象创建机制;它们不要求遵守尚未制定好文档的规范;它们不会与final域的
正常使用发生冲突;它们不会抛出不必要的受检异常(check exception);他们不需要进行类型转换
3. 序列化与反序列化
这个先上的代码,第一次看到是在这种实现是在stackOverFlow,现在已经遍地都是了!
当然,具体原创是谁我不知道。
public static <T extends Serializable> T deepCopy(T src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
T dest = (T) in.readObject();
return dest;
}
通过序列化与反序列化操作完成deepcopy动作
优点:不需要覆盖clone(),不需要单独处理每一个引用的clone行为
缺点:
- 需要每一个引用对象实现Serializable接口
不实现Serializable会报java.io.NotSerializableException
- 需要深拷贝的属性不能是transient,或者在readObject和writeObject中有对于transient属性的正确处理
- readObject和writeObject对于序列化过程的控制影响
不干预transient的序列化过程:
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊处理,transient不会序列化,所以会在clone对象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}\n\n";
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------\n\n");
System.out.println(dcbs_clone);
}
}
程序输出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=null,
}
用readObject和writeObject干预对于transient对象的序列化与反序列化过程:
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊处理,transient不会序列化,所以会在clone对象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}";
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
//示例代码,别纠结细节。
oos.writeInt(intss1.length);
for (int i = 0; i < intss1.length; i++) {
oos.writeObject(intss1[i]);
}
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
//示例代码,别纠结细节。
int length = ois.readInt();
this.intss1 = new int[3][2];
for (int i = 0; i < length; i++) {
this.intss1[i] = (int[]) ois.readObject();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------");
System.out.println(dcbs_clone);
}
}
程序输出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=[[10, 11], [20, 21], [30, 31]],
}
- readResolve对于反序列化对象的影响
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊处理,transient不会序列化,所以会在clone对象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}";
}
private Object readResolve() {
//示例代码,别纠结细节。
DeepCloneBySerizalizable deepCloneBySerizalizable = new DeepCloneBySerizalizable();
deepCloneBySerizalizable.intss1[1][1] = -123;
return deepCloneBySerizalizable;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------");
System.out.println(dcbs_clone);
}
}
输出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=[[10, 11], [20, -123], [30, 31]],
}
由于可以通过readResolve()方法修改反序列化后对象属性,所以,如果进行深拷贝涉及的对象有readResolve(),那么需要注意了,如果有特殊值修改,用序列化与反序列化过程来实现深拷贝将不可用
- static变量正常情况下是不参与序列化与反序列化的,虽然大部分时候这个不影响结果。
这个算课后作业? 参见《深入理解java虚拟机》,提醒static变量放在哪?
- etc.
网友评论