实现函数克隆

这就是我开始撰写本文的方式。 我已经读过很多次这样的声明: “当对象引用可变的最终字段时,克隆变得很困难。” 每次我在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检查,但这不是正确的方法。 相反,最好回到先前的解决方案,即在使用final字段的情况下使用copy构造函数,并对具有继承层次结构的类使用clone方法(案例(5)的解决方案)。

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

参考: 哪个更好的选择:克隆或复制构造函数? 从我们的JCG合作伙伴 Prasanth Gullapalli在prasanthnath博客上获得。

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

实现函数克隆

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

  1. move std 函数 示例_确保(值类型)可拷贝类有默认构造函数

    C.43: Ensure that a copyable (value type) class has a default constructor C.43:确保(值类型)可拷贝类有默认构造函数 Re ...

  2. 虚拟机克隆_了解Java中的可克隆接口

    虚拟机克隆 什么是对象克隆? 对象克隆是生成具有不同名称的对象的精确字段到字段副本的过程. 克隆的对象在内存中有自己的空间,可在其中复制原始对象的内容. 这就是为什么在克隆后更改原始对象的内容时,所做 ...

  3. unity 克隆_使用Unity开发Portal游戏克隆

    unity 克隆 Learn game development principles by coding a Portal-like game using Unity and C#. The prin ...

  4. OpenCV函数简记_第三章数字图像的滤波处理(方框,均值,高斯,中值和双边滤波)

    系列文章目录 OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间) OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制) Op ...

  5. python自定义函数画图_利用Python绘图和可视化(长文慎入)

    Python有许多可视化工具,但是我主要讲解matplotlib(http://matplotlib.sourceforge.net).此外,还可以利用诸如d3.js(http://d3js.org/ ...

  6. php 函数传值_传址_函数参数,php函数的传值与传址(引用)详解

    在php中我们函数传值就比较简单了,但可能有些朋友地天真无邪函数传址或引用搞不明白,下面小编来给各位介绍在php中函数传值与传址(引用)介绍,希望对各位有所帮助. php中引用的用法: 1. 变量的引 ...

  7. python中的array函数作用_数据分析的python基底(3)——array、Series、DataFrame笔记...

    <利用python进行数据分析>的第4.5章介绍了两个非常重要的包,NumPy和Pandas,这篇文章是我看这两章做的笔记,只列了要点和我觉得要留意的地方. 电子书和配套代码,还有一些学习 ...

  8. php 函数传值_传址_函数参数,php函数的传值与传址(引用)详解_PHP教程

    在php中我们函数传值就比较简单了,但可能有些朋友地天真无邪函数传址或引用搞不明白,下面小编来给各位介绍在php中函数传值与传址(引用)介绍,希望对各位有所帮助. php中引用的用法: 1. 变量的引 ...

  9. python程序在函数内执行得更快

    http://www.cnblogs.com/nepaul/archive/2012/07/15/2592179.html 为什么Python程序在函数内执行得更快?(来源StackOverflow) ...

最新文章

  1. mysql高可用_mysql高可用方案
  2. 在多台机器上搭建Hadoop的分布式环境
  3. window7环境下ZooKeeper的安装运行及监控查看
  4. java 数组排序论文_Java 7是否对方法Arrays.Sort使用Tim Sort?
  5. jeecg集成实现websocket
  6. 从零开始,教初学者如何征战全球最大机器学习竞赛社区Kaggle竞赛
  7. go语言的struct
  8. 【原创】轻量级移动设备即时通讯技术MobileIMSDK的常见问题解答
  9. PIC单片机开发环境搭建
  10. SpringMVC一路总结(一)
  11. 怎样修复IE浏览器 IE浏览器修复方法
  12. 广度搜索和深度搜索的分析
  13. 2022年Google I/O 大会即将举行,可领取 2022 年 I/O 大会参会开发者资料徽章。
  14. matlab读取nc数据的某一列数据库,科学网—.nc数据读取详细资料matlab2010a及后面的版本 - 张凌的博文...
  15. oracle之Number类型小数转字符串丢精度
  16. 计算机知识在小学教学中的应用,计算机在小学教学中的应用
  17. win10虚拟机安装linux
  18. vue渲染大量数据优化_vue大数据表格卡顿问题的完美解决方案
  19. note_10:surface laptop2遇到的问题和解决方案
  20. 【微信小程序】页面返回且带回数据

热门文章

  1. Dubbo(六)之属性配置
  2. Spring Boot 自动配置的 “魔法” 是如何实现的?
  3. 面试中经常会问的智力题,来看看你会做几道
  4. 一文搞懂 Java 线程中断
  5. 选择大公司还是小公司
  6. 想要玩转实现负载均衡,你知道这些吗?
  7. JAVA面试常考系列十一
  8. Redis(案例一:注册登录-图形验证码+谷歌开源Kaptcha)
  9. JSP 统计网站访问人数
  10. JS中闭包的应用自定义JS模块