您的位置 首页 java

深入浅出Java中的clone克隆方法

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的对象根据原始对象在堆中开辟一块同等代销的内存,然后把原始对象的数据都复制到新的内存地址,对于基本类型,可以把原始的值直接复制过来,但是对于引用类型,其保存的只是一个地址,复制的也是对象的地址,最终还是指向同一个对象,所以修改任何一个都会影响另外一个。如下图:

深入浅出Java中的clone克隆方法


我们从打印出来的两个Address对象的hashcode看,指向同一个对象

深入浅出Java中的clone克隆方法


我们看下底层的的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 ;
    }
  

测试结果如下:

深入浅出Java中的clone克隆方法


但是这种方法存在一个问题,那就是一个对象中如果嵌套的对象比较多,我们还得把每个对象都实现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中申请一个空的内存区域,对象的属性 值要通过构造方法赋值

文章来源:智云一二三科技

文章标题:深入浅出Java中的clone克隆方法

文章地址:https://www.zhihuclub.com/181798.shtml

关于作者: 智云科技

热门文章

评论已关闭

11条评论

  1. You actually reported this exceptionally well.
    write a conclusion for an essay need someone to write my essay

  2. Thank you! I appreciate it.
    what should i write my descriptive essay about someone write my essay for me

  3. Nicely put, Regards!
    help me write a song for my boyfriend can someone write my essay for me

  4. You made your point.
    how to write a descriptive essay about my school help me write a thesis statement for free

  5. You actually revealed this well.
    essay writing services are essay writing services any good

网站地图