leftso 50 0 2021-09-08
版权申明:本文为博主原创文章,未经博主允许不得转载。 https://www.leftso.com/blog/889.html
一个克隆是原始的精确副本。在java中,它本质上意味着能够创建一个与原始对象具有相似状态的对象。javaclone()方法提供此功能。
在这篇文章中,我们将探讨Java clone 的大部分重要方面。
 

1.什么是Java克隆?

所以克隆是关于创建原始对象的副本。它的字典意思是:“制作一个相同的副本”。
默认情况下,java 克隆是“逐字段复制”,即因为Object类不知道将调用clone()方法的类的结构。
因此,JVM 在调用克隆时,请执行以下操作:
  1. 如果类只有原始数据类型成员,则将创建对象的一个​​全新副本,并返回新对象副本的引用。
  2. 如果类包含任何类类型的成员,则仅复制对这些成员的对象引用,因此原始对象和克隆对象中成员引用都引用同一个对象
除了上述默认行为外,您始终可以覆盖此行为并指定您自己的行为。这是使用覆盖clone()方法完成的。让我们看看它是如何完成的。
 

2. Java Cloneable接口和clone()方法

每种支持对象克隆的语言都有自己的规则,java 也是如此。在java中,如果一个类需要支持克隆,它必须做以下事情:
  1. 你必须实现Cloneable接口。
  2. 您必须覆盖clone()Object 类中的方法。[有点奇怪。clone()方法应该在Cloneable接口中。]
/*
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 x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
 
protected native Object clone() throws CloneNotSupportedException;

 

  1. 第一条语句保证克隆的对象将具有单独的内存地址分配。
  2. 第二个声明建议原始对象和克隆对象应该具有相同的类类型,但这不是强制性的。
  3. 第三个语句表明原始对象和克隆对象应该使用 equals() 方法相等,但这不是强制性的。
让我们通过示例了解Java 克隆。我们的第一个类是Employee具有 3 个属性的类 - idnamedepartment Departmentclass 有两个属性 -idname.
public class Employee implements Cloneable{
 
    private int empoyeeId;
    private String employeeName;
    private Department department;
 
    public Employee(int id, String name, Department dept)
    {
        this.empoyeeId = id;
        this.employeeName = name;
        this.department = dept;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
     
    //Getters and Setters
}
$title(Department.java)
public class Department
{
    private int id;
    private String name;
 
    public Department(int id, String name)
    {
        this.id = id;
        this.name = name;
    }
 
    //Getters and Setters
}


所以,如果我们需要克隆 Employee 类,那么我们需要做这样的事情。

$title(TestCloning.java)
public class TestCloning 
{
 
    public static void main(String[] args) throws CloneNotSupportedException
    {
        Department dept = new Department(1, "Human Resource");
        Employee original = new Employee(1, "Admin", dept);
 
        //Lets create a clone of original object
        Employee cloned = (Employee) original.clone();
 
        //Let verify using employee id, if cloning actually workded
        System.out.println(cloned.getEmpoyeeId());
 
        //Verify JDK's rules
 
        //Must be true and objects must have different memory addresses
        System.out.println(original != cloned);
 
        //As we are returning same class; so it should be true
        System.out.println(original.getClass() == cloned.getClass());
 
        //Default equals method checks for references so it should be false. If we want to make it true,
        //then we need to override equals method in Employee class.
        System.out.println(original.equals(cloned));
    }
}
 
Output:
 
1
true
true
false


太好了,我们成功克隆了Employee对象。但是,请记住我们有两个对同一个对象的引用,现在这两个引用都会在应用程序的不同部分更改对象的状态。想看看怎么样?让我们来看看。

public class TestCloning {
 
    public static void main(String[] args) throws CloneNotSupportedException {
        Department hr = new Department(1, "Human Resource");
        Employee original = new Employee(1, "Admin", hr);
        Employee cloned = (Employee) original.clone();
 
        //Let change the department name in cloned object and we will verify in original object
        cloned.getDepartment().setName("Finance");
 
        System.out.println(original.getDepartment().getName());
        System.out.println(cloned.getDepartment().getName());
    }
}
 
Output:
 
Finance
Finance

糟糕,克隆对象的更改在原始对象中也是可见的。如果允许的话,这样克隆的对象可以在系统中造成严重破坏。任何人都可以来克隆您的应用程序对象并做任何他喜欢做的事情。我们可以防止这种情况吗??
答案是肯定的,我们可以。我们可以通过创建Java 深拷贝和使用复制构造函数来防止这种情况发生。我们将在本文后面了解它们。我们先来看看Java中什么是深克隆和浅克隆
 

3.Java浅拷贝

浅克隆是Java 中的“默认实现”。在重写clone方法中,如果您没有克隆所有对象类型(不是基元),那么您正在制作一个浅拷贝。
以上所有例子都是浅拷贝,因为我们没有DepartmentEmployee类的clone方法上克隆对象。现在,我将进入下一节,我们将看到深度克隆。
 

4.Java深拷贝

在大多数情况下,深度克隆是所需的行为。在深拷贝中,我们创建了一个独立于原始对象的克隆,并且在克隆对象中进行更改不应影响原始对象。

//Modified clone() method in Employee class
@Override
protected Object clone() throws CloneNotSupportedException {
    Employee cloned = (Employee)super.clone();
    cloned.setDepartment((Department)cloned.getDepartment().clone());   
    return cloned;
}


让我们看看在 Java 中是如何创建深拷贝的。 我修改了Employeeclassesclone()方法并cloneDepartmentclass 中添加了以下方法。

$title(Department.java)
//Defined clone method in Department class.
@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}
public class TestCloning 
{
    public static void main(String[] args) throws CloneNotSupportedException 
    {
        Department hr = new Department(1, "Human Resource");
 
        Employee original = new Employee(1, "Admin", hr);
        Employee cloned = (Employee) original.clone();
 
        //Let change the department name in cloned object and we will verify in original object
        cloned.getDepartment().setName("Finance");
 
        System.out.println(original.getDepartment().getName());
        System.out.println(cloned.getDepartment().getName());
    }
}
 
Output:
 
Human Resource
Finance

现在测试我们的克隆代码给出了预期的结果并且部门名称不会被修改。 这里,克隆对象的状态变化不会影响原始对象。
所以深度克隆需要满足以下规则——
  • 无需单独复制原语。
  • 原始类中的所有成员类都应该支持克隆,并且上下文中原始类的 clone 方法应该调用super.clone()所有成员类。
  • 如果任何成员类不支持克隆,则在 clone 方法中,必须创建该成员类的新实例,并将其所有属性一一复制到新成员类对象中。这个新的成员类对象将被设置在克隆对象中。
 

5. Java 复制构造函数

复制构造函数是类中的特殊构造函数,它为自己的类类型接受参数。因此,当您将类的实例传递给复制构造函数时,构造函数将返回一个新的类实例,其值是从参数 instance 复制的。它可以帮助您使用 Cloneable界面克隆对象
让我们在示例中看到这一点:

PointOne.java
public class PointOne 
{
    private Integer x;
    private Integer y;
 
    public PointOne(PointOne point){
        this.x = point.x;
        this.y = point.y;
    }
}


这个方法看起来很简单,直到继承。当您通过扩展上面的类来定义一个类时,您还需要在那里定义一个类似的构造函数。在子类中,您需要复制子特定属性并将参数传递给超类的构造函数。让我们看看如何?

PointTwo.java
public class PointTwo extends PointOne
{
    private Integer z;
 
    public PointTwo(PointTwo point){
        super(point); //Call Super class constructor here
        this.z = point.z;
    }
}
那么,我们现在还好吗?不。继承的问题在于准确的行为只能在运行时识别。所以,在我们的例子中,如果某个类通过了PointTwoin 的构造函数的实例PointOne
在这种情况下,您将获得作为参数PointOne传递实例的PointTwo作为回报的实例。让我们在代码中看到这一点:
Test.java
class Test
{
    public static void main(String[] args)
    {
        PointOne one = new PointOne(1,2);
        PointTwo two = new PointTwo(1,2,3);
 
        PointOne clone1 = new PointOne(one);
        PointOne clone2 = new PointOne(two);
 
        //Let check for class types
        System.out.println(clone1.getClass());
        System.out.println(clone2.getClass());
    }
}
 
Output:
 
class corejava.cloning.PointOne
class corejava.cloning.PointOne

创建复制构造函数的另一种方法是使用静态工厂方法。它们采用类类型作为参数,并使用该类的另一个构造函数创建一个新实例。然后这些工厂方法会将所有状态数据复制到上一步刚刚创建的新类实例中,并返回这个更新后的实例。
PointOne 带复制方法


public class PointOne implements Cloneable
{
    private Integer x;
    private Integer y;
 
    public PointOne(Integer x, Integer y)
    {
        this.x = x;
        this.y = y;
    }
 
    public PointOne copyPoint(PointOne point) throws CloneNotSupportedException
    {
        if(!(point instanceof Cloneable))
        {
            throw new CloneNotSupportedException("Invalid cloning");
        }
 
        //Can do multiple other things here
        return new PointOne(point.x, point.y);
    }
}

6. 带序列化的Java深拷贝

序列化是另一种深度克隆的简单方法。在此方法中,您只需序列化要克隆的对象并对其进行反序列化。显然,需要克隆的对象应该实现Serializable接口。
在继续之前,我应该警告不要轻易使用这种技术。
  1. 首先,序列化非常昂贵。它很容易比该clone()方法贵一百倍。
  2. 其次,并非所有对象都是Serializable.
  3. 第三,创建一个类Serializable很棘手,并不是所有的类都可以依靠它来做对
带序列化的深拷贝

@SuppressWarnings("unchecked")
public static  T clone(T t) throws Exception {
    //Check if T is instance of Serializeble other throw CloneNotSupportedException
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
 
    //Serialize it
    serializeToOutputStream(t, bos);
    byte[] bytes = bos.toByteArray();
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
 
    //Deserialize it and return the new instance
    return (T)ois.readObject();
}

7. Java 克隆 – SerializationUtils

在Apache commons 中,SerializationUtils类也有用于深度克隆的实用功能。如果您有兴趣,请关注他们的官方文档。

pom.xml
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>
SerializationUtils 示例
SomeObject cloned = org.apache.commons.lang.SerializationUtils.clone(someObject);

8. Java 克隆最佳实践

  1. 当您不确定是否可以调用clone()特定类的方法,因为不确定该类中是否实现了该方法时,可以检查该类是否为“ Cloneable”接口的实例,如下所示。
              
if(obj1 instanceof Cloneable){
    obj2 = obj1.clone();
}
 
//Dont do this. Cloneable dont have any methods
obj2 = (Cloneable)obj1.clone();
  1. 没有在被克隆的对象上调用构造函数。因此,您有责任确保所有成员都已正确设置。此外,如果您通过计算构造函数的调用来跟踪系统中的对象数量,您将获得一个新的额外位置来增加计数器。
我希望这篇文章对您有所帮助,并帮助您获得有关Java 8 克隆方法及其正确用法的更多信息。它还有助于回答Java 克隆面试问题

 
提示:本文最后更新于【 2021-09-08 21:11:00 】,某些文章具有时效性,若有错误或已失效,请在下方留言

评论区域