哪个更好的选择:克隆或复制构造函数?
這就是我開始撰寫本文的方式。 我已經讀過很多次這樣的聲明: “當對象引用可變的最終字段時,克隆變得很困難。” 每次我在Google上搜索它時,都要了解它的確切含義,并且在此過程中也忘了它。 因此以為我會在此撰寫博客,以便將其作為我的直接參考。
克隆對象(我可以從我的研究生課程的OOP課程中回想起)正在創建對象的類似副本,該副本基本上應符合以下規則:
請注意,在所有情況下都必須始終滿足條件(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)的解決方案)。
結論:正如我們在本文中一直看到的那樣,以正確的方式實現克隆方法非常復雜。 因此最好盡量遠離克隆。 只要復制對象沒有任何繼承層次結構,最好使用復制構造函數。
翻譯自: https://www.javacodegeeks.com/2014/01/which-is-better-option-cloning-or-copy-constructors.html
總結
以上是生活随笔為你收集整理的哪个更好的选择:克隆或复制构造函数?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站如何防御ddos洪水攻击(网站如何防
- 下一篇: 苹果屏幕分屏快捷键(苹果mac分屏快捷键