java中的数据类型分为两种:基本类型,引用类型。基本类型分为四类八种,分别如下:
- 整型 byte short int long
- 浮点型 float double
- 逻辑型 boolean
- 字符型 char
除这四类八种之外的所有对象都属于引用类型,其实本质上引用类型是一大堆有组织有结构的基本类型的集合。引用类型追究其本源,也是由基本类型所组成的。
简单复制
简单复制从字面上就很容易解释,将一个值或者一个对象,不加处理的赋值给另一个值或者对象,就是简单复制。例如:
int a = 10;
int b = a;
a = 100;
System.out.println("a: " + a);
System.out.println("b: " + b);
输出的结果显然为:
a: 100
b: 10
这是因为在基本类型在内存里面是开辟一个小空间,将基本类型的值直接存放在该空间里的。例如 int a = 10; 即是开辟一个4字节的空间,将10转换成二进制的形式存在该4字节的空间中的(存的是补码)。而 int b = a; 即是另外开辟4字节的空间,将a空间里面的二进制原封不动的复制到b空间里面去。a = 100; 将a空间里面的值改变成100的二进制形式(补码)。此时输出a、b,则输出的是a、b自己空间的值。也就是100、10。
简单复制对于基本类型的数据来说很easy,但是真正放到引用类型上面还会如基本类型一样的简单吗?不妨看看下面一段代码:
//假设已经有一个对象SimpleCope,里面有一个属性 int a
SimpleCope s1 = new SimpleCope();
s1.a = 10;
SimpleCope s2 = s1;
s1.a = 100;
System.out.println("s1.a: " + s1.a);
System.out.println("s2.a: " + s2.a);
输出结果为:
s1.a: 100
s2.a: 100
咦?很奇怪,不应该还是100和10吗?为什么这次两个都变成了100?要想弄清楚这个原因,还是要从内存的角度上考虑。引用类型实际上分成两个部分,一个是引用,一个是对象,我们在程序中用的其实是引用,我们通过引用去控制、改变对象的值。对象存在于堆中(大部分如此)。SimpleCope s1 = new SimpleCope(); 这一步其实做了两件事,第一件事是在堆上开辟了一个空间,里面存了SimpleCope这个对象。另一件事是开辟了一个小空间(一般在栈上)用于存放对象的引用,引用指向对象(类似于C中的指针)。s1.a = 10; 实际上是在对象的空间里面将其中属于a的那部分空间,写入10的二进制形式(补码)。SimpleCope s2 = s1; 新建一个引用,该引用指向s1的引用,也即是指向和s1所指向的对象。此时s2所指的对象的a属性也为10;s1.a = 100;将对象里面a的值从10换成100;注意此时s2指向的对象也是这个对象,那么s2.a 也是修改以后的100;这就合理的解释了为什么结果是100、100。
从上述两个例子可以看得出来,简单复制只能复制基本类型,如果真的想去复制引用类型,那么可能想要的结果并不能满足。
浅复制
在介绍浅复制前,先将结论告诉大家,方便等等讲解的时候思考。浅复制其实就是将对象里面所有的基本类型的值都复制,将引用类型的引用复制。也即浅复制只能复制一个对象里面的基本类型,而不能复制对象里面的引用类型属性。
public class ShallowCopy implements Cloneable {
private int a;
ShallowCopy shallowCopy;
ShallowCopy(int a) {
this.a = a;
shallowCopy = new ShallowCopy();
shallowCopy.a = a;
}
ShallowCopy() {
}
public static void main(String[] args) throws CloneNotSupportedException {
ShallowCopy s1 = new ShallowCopy(10);
ShallowCopy s2 = (ShallowCopy) s1.clone();
s1.a = 100;
s1.shallowCopy.a = 100;
System.out.println("s1.a: " + s1.a + "; s1.shallowCopy.a: " + s1.shallowCopy.a);
System.out.println("s2.a: " + s2.a + "; s2.shallowCopy.a: " + s2.shallowCopy.a);
}
}
输出的结果为:
s1.a: 100; s1.shallowCopy.a: 100
s2.a: 10; s2.shallowCopy.a: 100
解释一下:首先写了一个类ShallowCopy,该类实现Cloneable接口,主要是想通过Object.clone方法的,该方法需要子类实现Cloneable接口,否则将会抛出CloneNotSupportedException异常。其中如果子类不去修改Object的clone方法,那么该方法是java自身实现的对象浅复制。我们还是先new 出一个ShallowCopy对象,另ShallowCopy对象中的属性a赋值为10,同时将ShallowCopy对象中的属性ShallowCopy对象的a属性赋值为10(有点绕口,哈哈~)。ShallowCopy s2 = (ShallowCopy) s1.clone();开始浅复制。复制的对象赋值给s2,也即s2现在指向新的对象。s1.a = 100,将s1指向的对象中的a属性赋值成100。由于s2现在所指向的对象和s1所指向的对象不是同一个对象,所以s1改变a的值并不影响s2中a的值,这时候s2的值还是10。s1.shallowCopy.a = 100;改变s1对象中的shallowCopy对象的属性a,注意,注意shallowCopy在s1中存的不是对象,而是引用,其真正的对象在内存的另一块区域。这句话执行了之后,远在另一块区域的shallowCopy对象的属性a由一开始的10改变成了100。同时由于是浅复制,所以对于复制的对象中的引用类型的属性,只复制其引用,这意味了在s2对象中的引用类型shallowCopy也是指向s1对象中的引用类型shallowCopy的对象。所以此时s2.shallowCopy.a也发生了改变,从10变成了100;
深复制
其实介绍到这里了,如果前面的知识都听明白了,那么深复制也就不言而喻了。深复制其实就是真正的复制,完全的复制,不仅复制了对象里面的基本数据类型,也完全复制了对象里面的引用类型。实现深度复制的办法有很多,例如可以通过IO流去深度复制对象,也可以通过一些特殊对象的特殊方法进行深复制,例如Collections有一个copy方法。这里就不一一介绍了。记住,深度复制是将对象的所有全部都复制。