这就是我开始撰写本文的方式。 我已经读过很多次这样的声明: “当对象引用可变的最终字段时,克隆变得很困难。” 每次我在Google上搜索它时,都要了解它的确切含义,并且在此过程中也忘了它。 因此以为我会在此撰写博客,以便将其作为我的直接参考。

克隆对象(我可以从我的研究生课程的OOP课程中回想起)正在创建对象的类似副本,该副本基本上应符合以下规则:

  1. x.clone()!= x
  2. x.clone()。getClass()== x.getClass()
  3. x.clone()。equals(x)

请注意,在所有情况下都必须始终满足条件(1)。 尽管条件(2)和(3)并非绝对要求,但最好以这样的方式设计克隆方法,使其保持良好状态。 在继续讨论之前,这是Object类中clone方法的方法签名:

protected native Object clone() throws CloneNotSupportedException;

因此,正如您注意到protected修饰符一样,我们不可能直接在任何对象上调用clone()方法。 我们必须重写此方法作为公共方法,并在我们的类中为其提供实现才能访问它。 如果不需要特定的实现,我们可以返回super.clone()。 由于在Java 5之后可以进行协变返回,因此我们可以修改clone的返回值以返回类的对象。 因此,如果我们正在编写员工类,则这是clone()方法的方式:

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

但是请注意,Object类中的clone方法检查我们的类是否实现Cloneable接口。 如果未实现,则抛出CloneNotSupportedException。 否则,它将创建一个新副本。 但是请注意,克隆方法从不调用构造函数来创建对象的副本。 因此,如果您想通过增加构造函数内部的静态计数器来跟踪为类创建的实例数量,则此方法将不会起作用,因为永远不会调用构造函数。 相反,clone方法从对象内存中逐实例复制实例属性,然后将其返回给调用方。 因此,如果类必须提供一个用于克隆它的选项而不导致CloneNotSupportedException,则必须实现该类的标记接口Cloneable。 但是请注意,调用clone()的代码应处理此异常。 否则会导致编译器错误。 是的,这是一个痛点,对此受到批评。

现在让我们举一个例子: 案例(1)

public class Employee implements Cloneable{private String name;private String identifier;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getIdentifier() {return identifier;}public void setIdentifier(String identifier) {this.identifier = identifier;}@Overridepublic Employee clone() throws CloneNotSupportedException {return (Employee)super.clone();}public void print() {System.out.println(Objects.toStringHelper(this).add("name:", name).add("id:", identifier).toString());}public static void main(String[] args) throws CloneNotSupportedException {Employee employee1 = new Employee();employee1.setName("Ram");employee1.setIdentifier("1");System.out.println("1: "+employee1);employee1.print();Employee employee2 = employee1.clone();System.out.println("2: "+employee2);employee2.print();}
}

这是此的输出:

1: com.pramati.test.Employee@19821f
Employee{name:=Ram, id:=1}
2: com.pramati.test.Employee@de6ced
Employee{name:=Ram, id:=1}

从上面的示例可以看出,clone()方法创建了一个新Employee,其值是从现有对象中复制的。 这很简单,并且可以正常工作,因为Employee类中没有对象引用。 让我们这样修改类: 案例(2):

public class PayPackDetails{private double basicSalary = 500000d;private double incentive = 50000d;public double getSalary() {return getBasicSalary()+getIncentive();}public double getBasicSalary() {return basicSalary;}public double getIncentive() {return incentive;}public void setBasicSalary(double basicSalary) {this.basicSalary = basicSalary;}public void setIncentive(double incentive) {this.incentive = incentive;}
}public class Employee implements Cloneable {private String name;private String identifier;private PayPackDetails packDetails;public Employee(String name, String identifier, PayPackDetails packDetails) {this.name = name;this.identifier = identifier;this.packDetails = packDetails;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getIdentifier() {return identifier;}public void setIdentifier(String identifier) {this.identifier = identifier;}public PayPackDetails getPackDetails() {return packDetails;}@Overridepublic Employee clone() throws CloneNotSupportedException {return (Employee)super.clone();}public void print() {System.out.println(Objects.toStringHelper(this).add("name:", name).add("id:", identifier).add("package:", packDetails.getSalary()).toString());}public static void main(String[] args) throws CloneNotSupportedException {Employee employee1 = new Employee("Ram","1",new PayPackDetails());System.out.println("1: "+employee1);employee1.print();Employee employee2 = employee1.clone();System.out.println("2: "+employee2);employee2.print();}
}

运行main方法时,我们将得到以下结果:

1: com.pramati.clone.Employee@addbf1
Employee{name:=Ram, id:=1, package:=550000.0}
2: com.pramati.clone.Employee@de6ced
Employee{name:=Ram, id:=1, package:=550000.0}

这可以。 现在说,我们像这样修改了我们的主要方法: 案例(3):

public static void main(String[] args) throws CloneNotSupportedException {Employee employee1 = new Employee("Ram","1",new PayPackDetails());Employee employee2 = employee1.clone();employee2.setName("Krish"); employee2.setIdentifier("2");employee2.getPackDetails().setBasicSalary(700000d);employee1.print();employee2.print();
}

现在您认为employee1的薪水是多少? 随着我们增加了克隆员工的薪水,我们自然希望为他增加薪水。 但是这里出乎意料的转折是,employee1的薪水也增加了。 这是输出或此:

Employee{name:=Ram, id:=1, package:=750000.0}
Employee{name:=Krish, id:=2, package:=750000.0}

请注意,当我们克隆对象时,不会调用构造函数。 宁愿对原始对象的地址位置中存在的所有成员变量进行逐域复制。 现在,当有对象引用时,该引用将被复制,而不是原始对象。 因此,原始对象和克隆对象都指向同一成员对象。 因此,对一个对象所做的更改将自动对另一对象可见。 那么如何解决这个问题呢?

最简单的解决方案是也为PayPackDetails实现克隆方法,并从Employee的克隆方法中调用它。 情况(4):

@Override
public Employee clone() throws CloneNotSupportedException {Employee employee = (Employee)super.clone();employee.packDetails = packDetails.clone();return employee;
}

现在运行main()方法,它将按预期给出正确的结果:

Employee{name:=Ram, id:=1, package:=550000.0}
Employee{name:=Krish, id:=2, package:=750000.0}

但是,如果PayPackDetails由其他对象引用组成,则我们也必须重写该对象的克隆方法,并在PayPackDetails内部调用其克隆方法。 同样,当我们在PayPackDetails中组成新对象时,除了在新组成的对象中实现clone()方法外,我们还必须在PayPackDetails中修改clone方法。 组合对象类还应该实现Cloneable接口。 与往常一样,我们还必须处理CloneNotSupportedException。

现在考虑将PayPackDetails声明为final的另一种情况,这将使情况更加糟糕: 情况(5):

public class Employee implements Cloneable {private String name;private String identifier;private final PayPackDetails packDetails;// -- Rest of the methods
}

由于该字段被声明为final,因此无法在clone方法中为其分配新值,因为它被声明为final。 那么如何处理呢? 解决方案如下:使用复制构造函数并从克隆中返回新实例。

public class Employee implements Cloneable {private String name;private String identifier;private final PayPackDetails packDetails;public Employee(String name, String identifier, PayPackDetails packDetails) {this.name = name;this.identifier = identifier;this.packDetails = packDetails;}protected Employee(Employee emp) throws CloneNotSupportedException{name = emp.name;identifier = emp.identifier;packDetails = emp.packDetails.clone();}@Overridepublic Employee clone() throws CloneNotSupportedException {return new Employee(this);}public void print() {System.out.println(Objects.toStringHelper(this).add("name:", name).add("id:", identifier).add("package:", packDetails.getSalary()).toString());}
}

请注意,复制构造函数访问修饰符受到保护。 现在问题来了:为什么我们也不能将复制构造函数用于PayPackDetails而不是克隆方法? 答案是:是的,我们可以使用它。 情况(6):

public class PayPackDetails {private double basicSalary = 500000d;private double incentive = 50000d;public PayPackDetails(PayPackDetails details){basicSalary = details.getBasicSalary();incentive = details.getIncentive();}public static void main(String[] args) {Employee employee1 = new Employee("Ram","1",new PayPackDetails());employee1.print();Employee employee2 = new Employee(employee1);employee2.print();}
}
public class Employee {private String name;private String identifier;private final PayPackDetails packDetails;protected Employee(Employee emp) {name = emp.name;identifier = emp.identifier;packDetails = new PayPackDetails(emp.packDetails);}// .. Other methods}

到目前为止,这是最好的情况,这是此程序的输出:

Employee{name:=Ram, id:=1, package:=550000.0}
Employee{name:=Ram, id:=1, package:=550000.0}

实际上,这是最好的方法,因为它解决了有缺陷的克隆方法的许多问题:

1.没有一个类必须实现标记接口Cloneable
2.由于不需要克隆,因此无需捕获CloneNotSupportedException
3.由于不需要克隆,因此无需在调用super.clone()时对对象进行类型转换。

但是问题来了:假设您有一个PayPackDetails的子类。 案例(7):

public class AdvancedPayPackDetails extends PayPackDetails {private double flexiblePayPercent = 10d;public AdvancedPayPackDetails(AdvancedPayPackDetails details) {super(details);flexiblePayPercent = details.getFlexiblePayPercentage();}@Overridepublic double getSalary() {return super.getSalary()+(getBasicSalary()*getFlexiblePayPercentage()/100);}public double getFlexiblePayPercentage() {return flexiblePayPercent;}public void setFlexiblePayPercent(double flexiblePayPercent) {this.flexiblePayPercent = flexiblePayPercent;}public static void main(String[] args) throws CloneNotSupportedException {Employee employee1 = new Employee("Ram","1",new AdvancedPayPackDetails());employee1.print();Employee employee2 = employee1.clone();employee2.print();}}

现在运行main方法,它将为我们提供输出:

Employee{name:=Ram, id:=1, package:=600000.0}
Employee{name:=Ram, id:=1, package:=550000.0}

原因很明显。 Employee的副本构造函数不知道创建的这个新类(AdvancedPayPackDetails)。 实际上,我们可以修改Employee构造函数以包括对PayPackDetails的instanceOf检查,但这不是正确的方法。 相反,最好返回到我们先前的解决方案,在最终字段的情况下使用复制构造函数,并对具有继承层次结构的类使用克隆方法(案例(5)的解决方案)。

结论:正如我们在本文中一直看到的那样,以正确的方式实现克隆方法非常复杂。 因此最好尽量远离克隆。 只要复制对象没有任何继承层次结构,最好使用复制构造函数。

参考: 哪个更好的选择:克隆或复制构造函数? 来自我们的JCG合作伙伴 Prasanth Gullapalli,位于prasanthnath博客上。

翻译自: https://www.javacodegeeks.com/2014/01/which-is-better-option-cloning-or-copy-constructors.html

哪个更好的选择:克隆或复制构造函数?相关推荐

  1. 实现函数克隆_哪个更好的选择:克隆或复制构造函数?

    实现函数克隆 这就是我开始撰写本文的方式. 我已经读过很多次这样的声明: "当对象引用可变的最终字段时,克隆变得很困难." 每次我在Google上搜索它时,都要了解它的确切含义,并 ...

  2. C++拷贝构造函数(复制构造函数)详解

    link 复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用. 如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数.大多数情况下,其作用是实现从源对象到目 ...

  3. 拷贝构造函数c语言,C++拷贝构造函数(复制构造函数)详解

    复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用. 复制构造函数的参数可以是 const 引用,也可以是非 const 引用. 一般使用前者,这样既能以常量对象(初 ...

  4. 克隆或复制网站的 7 个最佳 WordPress 插件(比较)

    您想将您的网站迁移新的虚拟主机吗?或者,您可能想创建站点的临时版本,您可以在其中试用新插件而不会影响您的实时站点. 您可以手动迁移和复制网站,但这是一个非常耗时且容易出错的技术过程. 在本文中,我们将 ...

  5. Java提高—对象克隆(复制)/对象属性拷贝

    对象克隆(复制)假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byt ...

  6. [VSCode插件推荐] REST Client: 也许是比Postman更好的选择

    在测试REST API的时候,想必大家都会有不同的工具选择.如果是基于CLI的话,大家应该会选择cURL.如果是GUI工具的话,相信很多人都会使用Postman.不过今天,笔者要推荐的是REST Cl ...

  7. Java进阶之对象克隆(复制)

    转载自   Java进阶之对象克隆(复制) 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bo ...

  8. 4月想跳槽的同学,没有更好的选择,可以去美团

    在美团干了半年,说一下自己的感受,美团是一家福利中等,工资待遇中上,高层管理团队强大,加班强度一般,技术不错,办公环境一般,工作氛围中上,部门差距之间工作体验差距巨大的公司,我恰好在一个工作体验比较好 ...

  9. 软件开发平台之争:NET VS Java,谁是更好的选择?

    全文共3370字,预计学习时长9分钟 作为软件开发新手,为自己选择未来职业生涯中将使用的平台并不容易.掌握任何一种编程语言都会花费大量的时间和精力,而这些恰恰是你最不想浪费的.选错平台可能会需要改变知 ...

最新文章

  1. 本科生如何自学机器学习?
  2. 送你200+篇论文,学习图或图神经网络必读!(附下载)
  3. Agile Use Cases in Four Steps
  4. java peek方法_Java ArrayDeque peek()方法与示例
  5. Python Train_出乎意料的简单!10分钟用python建立人工智能预测模型
  6. centos7-安装redis-教程190923-精准版
  7. Oracle日期和时间总结
  8. hadoop3.1集成tez和tez-ui
  9. openwrt 需要高级浏览器_树莓派 + OpenWrt 实现 BT 下载机
  10. windows10,忘记密码,不用u盘就可以修改密码
  11. 什么是搜索引擎?有什么作用?
  12. python使用turtle库、绘制一个八边形_【Python】turtle八边形绘制
  13. [论文翻译]V-Net:Fully Convolutional Neural Networks for Volumetric Medical Image Segmentation
  14. 利用mammoth.js将doc文档转为html
  15. zk4元年拆解_耐克ZK5 Protro 科五复刻“减配”?可能你根本不懂曼巴心意!
  16. 云场景实践研究第50期:咕咚
  17. 弘辽科技:直通车成交率怎么算?成交率低怎么办?
  18. 从零开始人脸识别:face-recognition库
  19. 全国计算机职称考试难点,计算机职称考试模块考试难点autocad2004.doc
  20. 04年IM软件评测:表情贴图(转)

热门文章

  1. android 监听安装来源_Flutter插件开发之APK自动安装
  2. php面试心得,php面试题的总结
  3. springboot 订单重复提交_瞬间几千次的重复提交,我用Spring Boot+Redis扛住了
  4. spring boot测试_测试Spring Boot有条件的合理方式
  5. java客户端api文档_Java 11:新的HTTP客户端API
  6. tomee_OpenLiberty:注入错误,适用于TomEE和Wildfly
  7. spring @lazy_Spring @Lazy批注用例
  8. java实现资源监视器_实现Java监视的12个步骤程序存在缺陷
  9. jooq_jOOQ星期二:拉斐尔·温特豪德(Rafael Winterhalter)正在与字节好友合作字节码...
  10. javaone_JavaOne演讲者选择了您不容错过的10个会话