Java 克隆对象

  今天我们将研究java克隆对象或java中的克隆。 Java Object类附带了原生 clone()方法,该方法返回现有实例的副本。

Java克隆对象

这里写图片描述

  要使用java对象clone方法,我们必须实现标记java.lang.Cloneable接口,使它不会在运行时抛出CloneNotSupportedException异常。同时对象克隆是受保护的方法,因此我们必须重写它方能在其他类中使用。

  我们来看一个例子吧。

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Employee implements Cloneable {

    private int id;

    private String name;

    private Map<String, String> props;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Map<String, String> getProps() {
        return props;
    }

    public void setProps(Map<String, String> p) {
        this.props = p;
    }

     @Override
     public Object clone() throws CloneNotSupportedException {
     return super.clone();
     }

}

  请注意,我们正在使用Object clone()的实现,因此我们必须实现Cloneable接口。

  让我们用一个简单的程序测试我们的java clone对象示例。

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

    public static void main(String[] args) throws CloneNotSupportedException {

        Employee emp = new Employee();

        emp.setId(1);
        emp.setName("Pankaj");
        Map<String, String> props = new HashMap<>();
        props.put("salary", "10000");
        props.put("city", "Bangalore");
        emp.setProps(props);

        Employee clonedEmp = (Employee) emp.clone();

        // Check whether the emp and clonedEmp attributes are same or different
        System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));

        System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));

        // Lets see the effect of using default cloning

        // change emp props
        emp.getProps().put("title", "CEO");
        emp.getProps().put("city", "New York");
        System.out.println("clonedEmp props:" + clonedEmp.getProps());

        // change emp name
        emp.setName("new");
        System.out.println("clonedEmp name:" + clonedEmp.getName());

    }

}

  上面的克隆示例将产生以下输出。

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj

  如果我们的Employee类不实现Cloneable接口,则上面的程序将抛出java.lang.CloneNotSupportedException的运行时异常。

Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
    at java.lang.Object.clone(Native Method)
    at com.journaldev.cloning.Employee.clone(Employee.java:41)
    at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
java clone对象,java CloneNotSupportedException

这里写图片描述
  让我们看看第一个输出,并了解Object clone()方法发生了什么,以及它是否存在问题。

  1. emp and clonedEmp == test: false
    emp和clonedEmp是两个不同的对象,而不是指同一个对象。这符合java克隆对象的要求。

  2. emp and clonedEmp HashMap == test: true
    emp和clonedEmp对象变量都引用同一个对象。这带来了克隆的严重问题,接下来我们将看到。

  3. clonedEmp props:{city=New York, salary=10000, title=CEO}
    请注意,我们没有对clonedEmp属性进行任何更改,但它们仍然被更改,因为emp和clonedEmp变量都引用了同一个对象。这是一个严重的问题,因为java中的默认克隆不会创建完全分离的对象。这可能会导致不需要的结果,因此需要正确重写java克隆对象方法。

  4. clonedEmp name:Pankaj
    我们更改了emp名称,但是clonedEmp名称没有更改。这是因为String是不可变的。因此,当我们设置emp名称时,会创建一个新字符串并更改emp名称引用this.name = name。因此clonedEmp名称保持不变。您也会发现所有的基本类型有类似行为。因此,只要我们在对象中只有基本类型不可变变量,我们就可以使用java clone对象的默认方法。

若对此不太了解的同学可参考深复制与浅复制图解理解

浅克隆

  java clone对象的默认实现是使用浅拷贝,类似下面使用反射

@Override
 public Object clone() throws CloneNotSupportedException {

     Employee e = new Employee();
     e.setId(this.id);
     e.setName(this.name);
     e.setProps(this.props);
     return e;
}

深克隆

  在深克隆中,我们必须逐个字段进行复制。我们可以如下述代码重写克隆方法进行深克隆。

public Object clone() throws CloneNotSupportedException {

    Object obj = super.clone(); //utilize clone Object method

    Employee emp = (Employee) obj;

    // deep cloning for immutable fields
    emp.setProps(null);
    Map<String, String> hm = new HashMap<>();
    String key;
    Iterator<String> it = this.props.keySet().iterator();
    // Deep Copy of field by field
    while (it.hasNext()) {
        key = it.next();
        hm.put(key, this.props.get(key));
    }
    emp.setProps(hm);

    return emp;
}

  使用此克隆方法,我们的测试程序将产生以下输出。

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj

  这就是我们想要的,克隆应该返回一个完全脱离原始对象的新对象。因此,如果您正在考虑在程序中使用对象克隆和克隆,请明智地执行此操作并通过处理不可变实例来正确覆盖它。请注意,如果您的类扩展了其他类,而后者又扩展了其他类,那么这可能是一项艰巨的任务。您将不得不一直处理所有不可变字段的深层副本。

使用序列化克隆

  轻松执行深克隆的一种方法是通过序列化。但是序列化是一个开销很大的过程,且你的类必须实现Serializable接口。

使用Apache Commons Util克隆

  如果您已在项目中使用Apache Commons Util类,并且您的类是可序列化的,那么请使用以下方法。

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

复制用的构造函数

  我们还可以定义复制用的构造函数来获取对象的副本,而不依赖于克隆。例如,我们可以使用如下的Employee构造函数。

public Employee(Employee emp) {

    this.setId(emp.getId());
    this.setName(emp.getName());

    Map<String, String> hm = new HashMap<>();
    String key;
    Iterator<String> it = emp.getProps().keySet().iterator();
    // Deep Copy of field by field
    while (it.hasNext()) {
        key = it.next();
        hm.put(key, emp.getProps().get(key));
    }
    this.setProps(hm);

}

  每当我们需要employee 对象的副本时,我们都可以使用它Employee clonedEmp = new Employee(emp);

  但是,如果您的类有很多变量,且多数为基本类型和不可变的变量,那么编写复制构造函数可能是一项繁琐的工作。

Java克隆对象的最佳实践

  1. 仅当您的类具有基本类型和不可变的变量时,才使用默认的Object clone()方法。请注意,这也适用于继承。您必须检查所有要扩展的类,直到根对象Object级别。
  2. 如果您的类主要由可变对象组成,您还可以定义复制构造函数。
  3. 通过调用super.clone()重写克隆方法来利用Object clone()方法,然后对深复制中的可变字段进行必要的更改。
  4. 如果您的类是可序列化的,那么您也可以使用序列化进行克隆。然而,它会带来性能损失,因此在使用此方法进行克隆之前,请进行一些基准测试。
  5. 如果所继承的扩展类已正确定义了使用深克隆的clone方法,则可以使用默认克隆方法。例如,我们在Employee类中正确定义了clone()方法,如下所示。
package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Employee implements Cloneable {

    private int id;

    private String name;

    private Map<String, String> props;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Map<String, String> getProps() {
        return props;
    }

    public void setProps(Map<String, String> p) {
        this.props = p;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {

        Object obj = super.clone();

        Employee emp = (Employee) obj;

        // deep cloning for immutable fields
        emp.setProps(null);
        Map<String, String> hm = new HashMap<>();
        String key;
        Iterator<String> it = this.props.keySet().iterator();
        // Deep Copy of field by field
        while (it.hasNext()) {
            key = it.next();
            hm.put(key, this.props.get(key));
        }
        emp.setProps(hm);

        return emp;
    }

}

  我们可以有一个像下面这样的子类。

package com.journaldev.cloning;

public class EmployeeWrap extends Employee implements Cloneable {

    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String t) {
        this.title = t;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {

        return super.clone();
    }
}

  请注意,EmployeeWrap它没有任何可变属性,并且它正在使用父类克隆实现。

  这是一个简单的程序来测试这种克隆方式是否正常。

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

    public static void main(String[] args) throws CloneNotSupportedException {

        EmployeeWrap empWrap = new EmployeeWrap();

        empWrap.setId(1);
        empWrap.setName("Pankaj");
        empWrap.setTitle("CEO");

        Map<String, String> props = new HashMap<>();
        props.put("salary", "10000");
        props.put("city", "Bangalore");
        empWrap.setProps(props);

        EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone();

        empWrap.getProps().put("1", "1");

        System.out.println("empWrap mutable property value = "+empWrap.getProps());

        System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps());

    }

}

  输出的结果是:

empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}

  所以它正如我们预期的那样完美。

  这都是关于java克隆对象或java中的克隆。我希望您对对象克隆方法有一些了解,以及如何正确重写它而不会产生任何不利影响。


原文地址:Java clone object written by Pankaj
完整代码:Github
参考资料:API Doc for Object clone

猜你喜欢

转载自blog.csdn.net/why_still_confused/article/details/82463645
今日推荐