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

实现函数克隆

这就是我开始撰写本文的方式。 我已经读过很多次这样的声明: “当对象引用可变的最终字段时,克隆变得很困难。” 每次我在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

实现函数克隆

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/343841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[渝粤教育] 西南科技大学 服务管理 在线考试复习资料

服务管理——在线考试复习资料 一、单选题 1.不属于服务组织排队结构的是( ) A.随机排队 B.单一排队 C.多条排队 D.领号排队 2.服务接触的三元组合没有下列哪项是( ) A.服务组织 B.与客户接触服务人员 C.顾客 D.服务流程 3.下列哪种服务属于复杂性高而且标准化程度高的( ) A.…

光纤收发器元件级和整机测试内容介绍

对光纤收发器的测试可分为元件级和整机测试,元件级测试主要包括对光纤收发器内部关键器件在电工作的电性能测试。整机测试主要指将光纤收发器接入到以太局域网中,测试整机的功能、性能和特性。那么,具体要怎样测试光纤收发器才是一次完整的测…

java中使用okhttpsoap,Android okHttp网络请求之Retrofit+Okhttp+RxJava组合

Retrofit介绍:Retrofit和okHttp师出同门,也是Square的开源库,它是一个类型安全的网络请求库,Retrofit简化了网络请求流程,基于OkHtttp做了封装,解耦的更彻底:比方说通过注解来配置请求参数,通过…

Java中的证书透明度验证

因此,我有一个幼稚的想法,即除了证书有效性检查(在Java中)之外,将证书透明性验证作为每个请求的一部分也很容易。 牺牲了整个周末的一半时间,我可以证明这并不是一件小事。 但是, 证书透明性是…

[渝粤教育] 西南科技大学 电子商务原理及应用 在线考试复习资料

电子商务原理及应用——在线考试复习资料 一、单选题 1.( )接受商家的送货要求,将商品送到消费者手中。 A.邮局 B.快递公司 C.送货公司 D.物流中心 2.卓越属于( )类型的B2C电子商务企业: A.经营着离线商店的零售商 B.没有离线商店的虚拟零售企业 C.商品制造商 D.网络交易服务公…

如何判断光纤收发器是否有问题?

一般情况下,光纤收发器或光模块的发光功率如下:多模在10db--18db之间;单模20公里在-8db--15db之间;而单模60公里则在-5db--12db之间。但如是光纤收发器的发光功率出现在-30db--45db之间,那么,很有可能这个光…

[渝粤教育] 西南科技大学 经济数学1 在线考试复习资料

经济数学1——在线考试复习资料 一、单选题 1.求曲线在点处的切线方程( )。 A. B. C. D. 2.求函数的导数( )。 A. B. C. D. 3.设(都是常数),则( )。 A.0 B. C. D. 4.如果函数在区间上的导数( ),那么在区间上是一个常数( )。 A.恒为常数 B.可能为常数 C.恒为零 5.设,则( )。…

matlab 列表 剪切,利用Matlab进行文件批量复制、剪切和修改文件名

文件批量复制、剪切和修改文件名电脑环境文件批量复制文件批量剪切批量修改文件名批量修改文件名和复制(剪切)电脑环境MATLAB:2018aWindows:win10文件批量复制close all;clear all;clc;%目的文件目录DST_PATH_t C:\Users\fatflower\Desktop\2018BBC精听…

[渝粤教育] 西南科技大学 行政法学与行政诉讼法学 在线考试复习资料

行政法学与行政诉讼法学——在线考试复习资料 一、单选题 1.某省工商局与税务局联名对某公司作出处罚,吊销其营业执照,罚款100万元。该公司提起复议,复议机关是( ) A.国家工商总局 B.国家税务总局 C.国务院 D.省政府 2.行政相对人对下列行为不能申请行政复议的是哪一种?( ) A…

activemq 实例_在一台计算机上运行多个ActiveMQ实例

activemq 实例几周前,我再次通过Mule ESB解决方案将Apache ActiveMQ用作JMS提供程序。 由于使用ActiveMQ已经有几年了,所以我认为最好检查一些(新)功能,例如故障转移传输和其他群集功能 。 为了能够测试这些最后的东西…

什么是光纤收发器?光纤收发器作用是什么?

许多朋友一听到光纤收发器这五个大字总会有困惑,比如说什么是光纤收发器,光纤收发器又有什么作用等等疑问。那么,什么是光纤收发器呢?光纤收发器又有什么作用呢?接下来我们就跟随飞畅科技的小编一起来详细了解下吧&…

[渝粤教育] 西南科技大学 质量与可靠性管理 在线考试复习资料

质量与可靠性管理——在线考试复习资料 一、单选题 1.下列关于PDCA的内容说法正确的是( ) A.第一阶段进行申请、提交项目等 B.第二阶段按计划实地去做,去落实具体对策。 C.第三阶段实施标准化,以后就按标准进行,该阶段是实施PDCA的关键。 D.第四阶段对策实施后,把握对策的效果 …

php网站点击按钮更新程序,php页面 点击按钮执行更新操作

代码如下:此页面名称为updateScoreNew.php,点击按钮后获得id和score,然后执行更新数据库操作,不知道怎么写,点击按钮怎么都不调用方法,愁死了都。。。 html head ?php if(isset($_POST[update])) { echo &…

【渝粤教育】电大中专品牌管理与推广_1作业 题库

1.品牌是产品或商品的牌子,而商标是商家和商品的标志,是商品经济发展到一定阶段的产物。该说法() A.正确 B.错误 正确 正确答案:左边查询 学生答案:A 2.企业品牌化,是指品牌的核心不是个别的产品…

在15分钟内使用Spring Boot和Spring Security构建一个Web应用程序

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。 开发人员知道保护Web应用程序安全可能会很麻烦。 正确地做是很难的。 最糟糕的是&…

音频光端机的必备要素有哪些?

音频光端机就是发射端把传统的音频模拟信号转换成光信号,通过光纤传输到接收端,在接收端再转换成模拟信号的一种音频设备。那么,音频光端机有哪些必备要素呢?音频光端机的原理是什么呢?接下来我们就一起来详细了解下吧…

php vue联动查询,使用vue.js实现联动效果的示例代码

摘要:本篇文章主要介绍了使用vue.js实现联动效果的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧想用vue.js写一个联动效果,按照自己的思路实验了下,并没有成功。请…

【渝粤教育】电大中专学前儿童语言教育 (4)作业 题库

作业视频教务托管,壹叁路路贰陆陆壹〇肆〇 认为儿童天生就有学习语言能力且体现在一种语言获得装置(LAD)中的教育家是( )。 A.斯金纳 B.乔姆斯基 C.皮亚杰 D.伍顿 错误 正确答案:左边查询 学生答案:未作答 2.下面哪个选…

Spring Setter依赖注入示例

学习如何编写Spring Setter依赖注入示例 。 Setter注入是Spring依赖注入的一种 。 Spring支持字段注入,Setter注入以及构造函数注入,以将依赖项注入Spring托管的bean中。 本教程的范围仅限于Setter注入。 有关Spring依赖注入的更多信息: Sp…

【渝粤教育】电大中专幼儿园课程论 (8)作业 题库

作业视频教务托管,壹叁路路贰陆陆壹〇肆〇 显性课程是 ( )的课程 A.指定的 B.随机的 C.有计划的 D.无计划的 错误 正确答案:左边查询 学生答案:未作答 2.幼儿园课程目标要素不包括( ) A.特征 B.目标 C.评价 D.内容 错误 正确答案:…