前言
对象的拷贝是java开发中绕不过去的话题,实际开发中运用到的也很多。
有的时候,需要一份对象的复制结果,然后对复制结果进行裁剪、修改等,可是又不能影响到原对象,那么一个简单的'b=a'肯定就再难堪大任(只针对可变对象哦,对于不可变对象这种场景压根儿不存在),于是我们的主角clone就要闪亮登场。
clone()方法
clone()是object里的protected方法:
clone()是protected 的,即包可见和可继承。猜想应该是由于不知道子类具体的clone后的返回类型,于是设置成protected的,让子类来重写该方法。
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
clone()方法要求子类实现Cloneable接口
public interface Cloneable {
}
Cloneable接口是java四大接口之一,如上面代码所示,Cloneable接口只是一个标记接口,它的出现就是让使用者知道要对clone方法重写。如果没有implement这个接口,又使用了clone()方法,就会抛出异常。
转过头来,我们再看一看clone()方法的注释,这里截取了一部分。
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
里面提到:准确的copy含义也许取决于该对象的类。这样做的意图是,对于任意的一个对象,以下表达式:
x.clone() != x为真
x.clone().getClass() == x.getClass()为真
但是它们并非必须满足的要求。
通常情况下,x.clone().equals(x)为真(依旧非必须满足)。
按照惯例,clone()方法返回的对象应该由调用super.clone获得,
如果一个类和其所有除了object的超类都遵循了此惯例,那么x.clone().getClass() == x.getClass()为真。
害,绕过来绕过去的,说白了,具体情况具体对待
总的来说,要拷贝就要implement Cloneable这个接口,重写clone()方法并在其中合理地调用super.clone()。那么clone()方法返回的是当前对象的一个拷贝。而这个拷贝是浅拷贝的。
浅拷贝
前面提到了浅拷贝,辣么什么是浅拷贝呢?
这里有百度百科的一串概念:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
结合自己的理解,我来通俗地翻译并且转化一下这段话:
在进行浅拷贝时,会为新对象分配一块空间来进行对象的拷贝。浅拷贝得到的新对象和旧对象的内存地址已经不同,但成员属性完全相同。
而这种属性分为两类:
1.基本类型
2.对象(可变对象、不可变对象(典型的就是String,在稍后的例子中我们会看到))
所以浅拷贝得到的成员属性,对于基本类型来说,就是复制一下值。第二种,则只是复制了引用哦,只是将新对象的对应属性的引用指向原引用。所以就会存在互相影响的问题(然而不可变对象是个例外,由于它的特殊性,这种互相影响就不存在)
示意图大概就是下面这样(图片来自百度百科):
下面我们来看个例子:
这是一个类,名叫人,它拥有三个成员:age(基本类型int)、name(不可变对象String)、House(可变对象)。它就覆盖了我们上面所谈到的情况。
public class Person implements Cloneable{
private String name ;
private int age;
private House house;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString(){
return "[Person: "+this.hashCode()+" name: "+name+" age: "+age+" house:"+house+" ]";
}
}
public class House {
private int money;
private String type;
public House(int money ,String type){
this.money=money;
this.type=type;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString(){
return "[House: "+this.hashCode()+" money: "+money+" type: "+type+" ]";
}
}
测试:
public class copyTest {
public static void main(String[] args){
Person person1 = new Person();
person1.setAge(18);
person1.setHouse(new House(20,"small house"));
person1.setName("alex");
Person person2 =person1;//=测试,其实这个完全不需要,但还是做个对比
person2.setName("alice");
person2.setAge(20);
House house2 = person2.getHouse();
house2.setMoney(40);
System.out.println(person1.toString());
System.out.println(person2.toString());
System.out.println("华丽丽的分割线");
Person person3 = (Person) person1.clone();//浅拷贝
person3.setName("tom");
person3.setAge(28);
House house3 = person3.getHouse();
house3.setMoney(60);
System.out.println(person1.toString());
System.out.println(person3.toString());
}
}
运行结果:
[Person: 1163157884 name: alice age: 20 house:[House: 1956725890 money: 40 type: small house ] ]
[Person: 1163157884 name: alice age: 20 house:[House: 1956725890 money: 40 type: small house ] ]
华丽丽的分割线
[Person: 1163157884 name: alice age: 20 house:[House: 1956725890 money: 60 type: small house ] ]
[Person: 356573597 name: tom age: 28 house:[House: 1956725890 money: 60 type: small house ] ]
显然,浅拷贝确实新创建了一个对象,但是对于该对象内的可变对象成员的拷贝只是复制了引用。
所以person3和person1并非独立,对其中的house对象的修改会互相影响。
那么问题来了,我现在希望这两个person是完全独立的,又该怎么做呢?
这时就涉及到深拷贝。
深拷贝
说白了,就是在浅拷贝的达到的基础上再把要复制的对象里的可变对象也新建对象空间复制一份,而不是复制引用。
达到深拷贝的方式主要有两种:
1.层层浅拷贝(每一层的可变对象都给它clone)
2.序列化
下面我们先试第一种方法:
public class Person implements Cloneable{
private String name ;
private int age;
private House house;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
}
@Override
public Object clone() {
try {
Person person=(Person) super.clone();
person.house=(House) house.clone();
return person;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString(){
return "[Person: "+this.hashCode()+" name: "+name+" age: "+age+" house:"+house+" ]";
}
}
public class House implements Cloneable{
private int money;
private String type;
public House(int money ,String type){
this.money=money;
this.type=type;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString(){
return "[House: "+this.hashCode()+" money: "+money+" type: "+type+" ]";
}
}
测试:
public class copyTest {
public static void main(String[] args){
Person person1 = new Person();
person1.setAge(18);
person1.setHouse(new House(20,"small house"));
person1.setName("alex");
Person person3 = (Person) person1.clone();
person3.setName("tom");
person3.setAge(28);
House house3 = person3.getHouse();
house3.setMoney(60);
System.out.println(person1.toString());
System.out.println(person3.toString());
}
}
结果:
[Person: 1163157884 name: alex age: 18 house:[House: 1956725890 money: 20 type: small house ] ]
[Person: 356573597 name: tom age: 28 house:[House: 1735600054 money: 60 type: small house ] ]
下面看一下序列化的使用方法,一个类要能序列化,就必须实现Serializable这个标记接口。
这里我们对person和House分别implements上Serializable接口后进行测试:
public class copyTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person1 = new Person();
person1.setAge(18);
person1.setHouse(new House(20,"small house"));
person1.setName("alex");
Person person3 = (Person) deepClone(person1);
person3.setName("tom");
person3.setAge(28);
House house3 = person3.getHouse();
house3.setMoney(60);
System.out.println(person1.toString());
System.out.println(person3.toString());
}
public static Object deepClone(Object object) throws IOException, ClassNotFoundException {
//写入对象
ByteArrayOutputStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(object);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
[Person: 325040804 name: alex age: 18 house:[House: 1173230247 money: 20 type: small house ] ]
[Person: 2065951873 name: tom age: 28 house:[House: 1791741888 money: 60 type: small house ] ]
后记
个人觉得浅拷贝、深拷贝的理解还是蛮重要,有时候莫名其妙的值的改变的问题很大程度就是多线程或者对拷贝的理解不透彻的问题。