为什么需要复制构造函数以及在Java中何时应使用复制构造函数

34
我正在学习复制构造函数,我已经阅读了 stack over flow 和其他链接,但是以下几点我还不清楚:
  1. 为什么需要复制构造函数
  2. 何时需要复制构造函数
我的意思是,在什么情况下我们需要使用复制构造函数。有人能举个例子或者提供链接以便我可以理解得更清楚吗?
以下是我已经阅读过的一些链接,以便了解什么是复制构造函数。

http://www.programmerinterview.com/index.php/java-questions/how-copy-constructors-work/

https://deepeshdarshan.wordpress.com/2013/12/05/copy-constructors-in-java/

第二个链接解释了何时和哪里需要使用复制构造函数。但我仍然不太清楚。
以下是我的Employee.java类:
package com.test;

/**
 * @author avinashd
 *
 */
public class Employee {

    private String rollNo;
    private String name;

    //constructor
    public Employee(String rollNo, String name){

        this.rollNo = rollNo;
        this.name = name;
    }

    //copy constructor
    public Employee(Employee employee){

    this.rollNo = employee.rollNo;
    this.name = employee.name;

    }

    public String getRollNo() {
        return rollNo;
    }

    public void setRollNo(String rollNo) {
        this.rollNo = rollNo;
    }

    public String getName() {
        return name;
    }

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

复制构造函数用于创建一个与现有对象具有相同值的精确副本。

例如,假设我们有一个员工,其值为rollNo: 1name: avinash。复制构造函数将创建一个类似的对象,其值为rollNo: 1name: avinash。但是这两个对象是不同的,对一个对象值的更改不会影响另一个对象。

问题在于:

当我们有一个构造函数时

public Employee(String rollNo, String name){
    this.rollNo = rollNo;
    this.name = name;
}

创建一个对象,我们可以调用同样的构造函数来创建另一个对象。但是为什么我们需要调用复制构造函数?在什么情况下需要调用它?请解释。


1
如果您不认为创建这样的构造函数有意义,那么您就不需要它。 - Alex
1
就算价值不大,上述示例中的复制构造函数试图直接访问私有字段。应该使用employee.getRollNo()代替employee.rollNo,使用employee.getName()代替employee.name - Makaveli84
  1. 我们不这样做。
  2. 在我21年的Java编程生涯中,我从未使用过它。
  3. 在Java中,真正没有所谓的“复制构造函数”。复制构造函数是C++编译器在按值传递或返回对象或对其进行赋值时可以使用的一种方法。但在Java中,这些都不会发生。
- user207421
@Makaveli84 在同一类中的方法访问私有字段没有任何问题。 - Jim Balter
@JimBalter 这是合法的,但不是良好的实践。据我所知,通常建议调用getset方法,因为这些方法可能包含特定和/或额外的实现细节,超出了简单的字段赋值。 - Makaveli84
在这种情况下,构造函数可能希望避免这样的“附加实现细节”。无论如何,在这里没有这样的额外操作。在类内部使用getter和setter是毫无必要和理由的,这是错误的。 - Jim Balter
8个回答

38

使用复制构造函数而不是将所有参数传递给构造函数有两个好处:

  1. 当您拥有许多属性的复杂对象时,使用复制构造函数要简单得多
  2. 如果您向类中添加属性,则只需更改复制构造函数以考虑此新属性,而不是更改其他构造函数的每个出现

34

按照惯例,复制构造函数应该提供对象的深拷贝。正如其他答案已经提到的那样,复制构造函数提供的主要便利是当您的对象变得过于复杂时。请注意,java.lang.Cloneable 提供了一个(几乎)类似的声明。

但是使用复制构造函数有许多优点,优于 Cloneable 接口。

  1. Cloneable 作为接口实际上并未提供任何方法。为了使其有效,您仍需要覆盖 java.lang.Objectclone 方法。这对于接口来说是相当反直觉的用法。

  2. clone 返回 Object。为了它有用,您仍需要进行类型转换。这很笨拙,并可能导致运行时错误。

  3. clone 方法的文档记录较差。对于 clone,如果您具有指向可变对象的最终字段,则可能会出现问题。

  4. 最重要的是,复制构造函数可以接受并深度复制子类实例。在我看来,这就是复制构造函数真正闪耀的地方。

还有更多优点(请参见 Joshua Bloch 的 Effective Java 2e),但这些是我迄今为止发现最相关的要点。

[1]Java 语言中没有任何东西实际上提供了默认的深拷贝构造函数。在大多数情况下,对象仅可以告诉程序员它们可以通过实现 Cloneable 或提供一个复制构造函数来进行深度复制。


5
如果您想要另一个与已有的完全相同值的Employee实例,该怎么办?您会调用什么?
setName(oldEmployee.getName())..
setRollNumber(oldEmployee.getRollNumber())..
etc..

不要那样做,使用这个。
Employee copyOfEmployeeOne=new Employee(employeeOneInstance);
// no need of a sequence of setters..

我可以直接调用常规构造函数。Employee copyOfEmployee = new Employee(1,avinash); 这也创建了一个具有第一个对象相同值的Employee对象。 - Avinash Reddy
4
如果有20个属性怎么办?如果你不知道5个属性的值,而必须从其他实例中获取它们呢? - TheLostMind

4

复制构造函数相对于Object.clone()方法给我们带来了很多优势,因为它们:

  1. 不强制我们实现任何接口或抛出异常。
  2. 不需要任何强制类型转换。
  3. 不要求我们依赖于未知的对象创建机制。
  4. 不需要父类遵循任何契约或实现任何内容。
  5. 允许我们修改final字段。
  6. 允许我们完全控制对象的创建,我们可以在其中编写初始化逻辑。

Java Cloning - Copy Constructor versus Cloning上阅读更多


2

另一个情况是存储对象的“历史”值。

你有一个单一的对象,但每当你改变它的状态时,你想把它添加到ArrayList或者适合你的任何数据结构中,而不是手动复制数据,你只需在进一步修改之前使用“复制构造函数”。


0

复制构造函数实现了浅拷贝和深拷贝机制,但使用复制构造函数而不是克隆(使用Cloneable接口)的主要优点是:

  1. 我们不需要像使用Object.clone()方法那样进行任何类型转换。
  2. 我们将能够修改最终字段以进行复制目的,而在使用Object.clone()机制时无法修改或访问最终字段。
  3. 它允许我们完全控制对象创建,我们可以在其中编写初始化逻辑。
  4. 如果我们想要克隆我们类的对象,该对象持有其他依赖类的引用变量,我们不需要实现该类的克隆方法。只需通过初始化我们的复制构造函数,即可实现该目的。

0

通过复制构造函数,我们可以进行克隆操作,而无需使用像实现Cloneable接口和重写克隆方法这样的复杂操作。此外,我们也不需要特别担心深度克隆的问题。

但需要注意以下几点:

1.当我们实现Cloneable时,这是向其他类/用户发出的信号,表明该类的对象可以进行克隆。如果没有这个接口,其他类可能没有关于可克隆性的明确信息。

2.如果我们将复制构造函数设置为私有,则可以限制该类对象的克隆。然后,该复制构造函数只能用于在类本地初始化新创建的对象,而不能用于其他类中的克隆目的。

3.当您不想使您的类可克隆,但如果您编写了访问修饰符为public的复制构造函数,则会导致其他类可以创建您的类的对象的不安全性。


0
考虑这样一个例子,超类提供了一个复制构造函数,强制开发人员手动将字段复制到父类。 这对于向下转型非常有用:
public class Employee {
    private String name;
    private int age;

    // regular constructor
    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // copy constructor
    public Employee(Employee that) {
        this.name = that.name;
        this.age = that.age;
    }
    // getters & setters
}

public class SeniorMgmt extends Employee {
    private boolean secureAccess;

    public SeniorMgmt(Employee employee, boolean secureAccess) {
        super(employee);
        this.secureAccess = secureAccess;
    }
    // getters & setters
}

public class TestDrive {
    public static void main(String[] args) {
        Employee programmer = new Employee("John", 34);
        SeniorMgmt promotedProgrammer = new SeniorMgmt(programmer, true);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接