Java 中除了 调用构造函数来创建以外,还可以通clone来创建一个对象,而克隆又分为浅克隆和深度克隆,那么两者有什么区别呢?
浅克隆
我们先举一个例子,我们定义一个Person类,有 名字、年龄、地址对象
public class Address {
// 公司地址
private String companyAddress;
// 家庭地址
private String homeAddress;
}
public class Person implements Cloneable{
private String name;
private int age;
private Address address;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
那么怎样克隆一个对象呢,很简单 只要实现clone接口接口,然后调用clone()方法即可,下面我们看测试类:
public class PersonTest {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.setName("zhangsan");
person.setAge(20);
Address address = new Address();
address.setCompanyAddress("hefei");
address.setHomeAddress("shanghai");
person.setAddress(address);
System.out.println("person" + person);
//下面我们开始克隆一个对象
Person person2 = person.clone();
System.out.println("cloneperson" + person2);
}
}
测试结果:person 对象被克隆出来了。
personPerson{name='zhangsan', age=20, address=Address{companyAddress='hefei', homeAddress='shanghai'}}
clonepersonPerson{name='zhangsan', age=20, address=Address{companyAddress='hefei', homeAddress='shanghai'}}
````java
我们试着修改clone的对象的地址,看原始的person会不会改变:
```java
person2.getAddress().setCompanyAddress("beijing");
System.out.println("person" + person);
原始的person 的 companyAddress 也被修改了地址。
其实原理非常简单,clone的对象根据原始对象在堆中开辟一块同等代销的内存,然后把原始对象的数据都复制到新的内存地址,对于基本类型,可以把原始的值直接复制过来,但是对于引用类型,其保存的只是一个地址,复制的也是对象的地址,最终还是指向同一个对象,所以修改任何一个都会影响另外一个。如下图:
我们从打印出来的两个Address对象的hashcode看,指向同一个对象
我们看下底层的的clone源码:
// 判断复制的类是否实现Cloneable接口
if (!klass->is_cloneable() ||
(klass->is_instance_klass() &&
InstanceKlass::cast(klass)->reference_type() != REF_NONE)) {
ResourceMark rm(THREAD);
THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
}
// Make shallow object copy
const int size = obj->size();
oop new_obj_oop = NULL;
if (obj->is_array()) {
const int length = ((arrayOop)obj())->length();
new_obj_oop = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
} else {
new_obj_oop = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
}
HeapAccess<>::clone(obj(), new_obj_oop, size);
Handle new_obj(THREAD, new_obj_oop);
实现浅拷贝的步骤是:
- 被复制的类需要实现Cloneable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常)改接口为标记接口(不含任何方法)
- 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)
那么怎么对一个对象中的引用对象也进行拷贝呢?下面我们介绍深拷贝
深拷贝
我们还以上面的例子来讲解,我们在Address类中实现 clone接口:
public class Address implements Cloneable {
// 公司地址
private String companyAddress;
// 家庭地址
private String homeAddress;
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
然后再修改Person 类中的 clone 方法
@Override
protected Person clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
p.setAddress(p.getAddress().clone());
return p ;
}
测试结果如下:
但是这种方法存在一个问题,那就是一个对象中如果嵌套的对象比较多,我们还得把每个对象都实现Cloneable对象,那么有没有更简单的办法呢?
我们先将对象序列化,然后再反序列化新的对象。具体示例如下:
我们在Person 类中添加如下方法:
public Object deepCopy(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();
}
但是 Address 类 和 Person都要实现 Serializable 接口,否则序列化失败;
测试代码如下:
public class Person implements Serializable{
private String name;
private int age;
private Address address;
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 Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
public Object deepCopy(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();
}
}
测试结果:
personcom.md.Address@1540e19d
clonepersoncom.md.Address@6acbcfc0
深拷贝和浅拷贝的区别?
浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。除非像我们今天实战中举的那个例子,需要从数据库中加载 10 万条数据并构建散列表索引,操作非常耗时,这种情况下比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。
克隆 和 new 的区别
- java 中 clone 和 new 都可以创建 对象
- clone不会调用构造方法;new 会调用构造方法
- clone能快速创建一个已有对象的副本,即创建对象并且将已有对象中的属性值克隆;new只能在JVM中申请一个空的内存区域,对象的属性 值要通过构造方法赋值