เกี่ยวกับ Java ที่สามารถโคลนได้


96

ฉันกำลังมองหาแบบฝึกหัดที่อธิบายเกี่ยวกับ Java Cloneableแต่ไม่ได้รับลิงก์ที่ดีเลยและ Stack Overflow ก็กลายเป็นตัวเลือกที่ชัดเจนมากขึ้น

ฉันต้องการทราบสิ่งต่อไปนี้:

  1. Cloneableหมายความว่าเราสามารถมีโคลนหรือสำเนาของวัตถุได้โดยใช้Cloneableอินเทอร์เฟซ ข้อดีข้อเสียของการทำแบบนั้นคืออะไร?
  2. การโคลนแบบวนซ้ำจะเกิดขึ้นได้อย่างไรหากวัตถุนั้นเป็นวัตถุผสม

2
ข้อดีข้อเสียเทียบกับอะไร?
Galactus

4
ฉันอ่านว่าเพื่อหมายถึงข้อได้เปรียบของคลาสที่สามารถโคลนได้เทียบกับไม่ ไม่แน่ใจว่าจะตีความได้อย่างไร: S
allyourcode

คำตอบ:


159

สิ่งแรกที่คุณควรรู้Cloneableคือ - อย่าใช้มัน

เป็นการยากมากที่จะใช้การโคลนอย่างCloneableถูกต้องและความพยายามนั้นไม่คุ้มค่า

แทนที่จะใช้ตัวเลือกอื่น ๆ เช่น apache-commons SerializationUtils(โคลนลึก) หรือBeanUtils(โคลนนิ่งตื้น) หรือใช้ตัวสร้างการคัดลอก

ดูที่นี่สำหรับมุมมองของ Josh Bloch เกี่ยวกับการโคลนนิ่งCloneableซึ่งอธิบายถึงข้อบกพร่องหลายประการของแนวทางนี้ ( Joshua Blochเป็นพนักงานของ Sun และเป็นผู้นำในการพัฒนาคุณลักษณะ Java มากมาย)


1
ฉันเชื่อมโยงคำพูดของ Bloch (แทนที่จะอ้างถึง)
Bozho

3
โปรดทราบว่า Block บอกว่าห้ามใช้ Cloneable เขาไม่ได้บอกว่าอย่าใช้การโคลนนิ่ง (หรืออย่างน้อยฉันก็หวังว่าจะไม่) มีหลายวิธีในการใช้การโคลนนิ่งที่มีประสิทธิภาพมากกว่าคลาสเช่น SerializationUtils หรือ BeanUtils ที่ใช้การสะท้อน ดูโพสต์ของฉันด้านล่างสำหรับตัวอย่าง
Charles

ทางเลือกอื่นของตัวสร้างการคัดลอกเมื่อกำหนดอินเทอร์เฟซคืออะไร? เพียงแค่เพิ่มวิธีการคัดลอก?
benez

@benez ฉันจะตอบว่าใช่ ตั้งแต่ Java-8 คุณสามารถมีstaticวิธีการในการเชื่อมต่อเพียงเพื่อให้static WhatEverTheInterface copy(WhatEverTheInterface initial)? แต่ฉันสงสัยว่าสิ่งนี้ให้อะไรคุณเนื่องจากคุณคัดลอกฟิลด์จากวัตถุในขณะที่โคลน แต่อินเทอร์เฟซกำหนดวิธีการเท่านั้น สนใจที่จะอธิบาย?
Eugene

40

Cloneable นั้นน่าเสียดายที่เป็นเพียงอินเทอร์เฟซของ marker นั่นคือมันไม่ได้กำหนดวิธีการ clone ()

คืออะไรคือเปลี่ยนพฤติกรรมของเมธอด Object.clone () ที่ได้รับการป้องกันซึ่งจะโยน CloneNotSupportedException สำหรับคลาสที่ไม่ใช้ Cloneable และดำเนินการคัดลอกตื้นที่ชาญฉลาดสำหรับสมาชิกสำหรับคลาสที่ทำ

แม้ว่านี่จะเป็นพฤติกรรมที่คุณกำลังมองหา แต่คุณก็ยังต้องใช้เมธอด clone () ของคุณเองเพื่อให้เป็นแบบสาธารณะ

เมื่อใช้การโคลนของคุณเอง () แนวคิดคือการเริ่มต้นด้วยวัตถุที่สร้างโดย super.clone () ซึ่งรับประกันว่าเป็นคลาสที่ถูกต้องจากนั้นทำการเติมข้อมูลฟิลด์เพิ่มเติมในกรณีที่สำเนาตื้นไม่ใช่สิ่งที่ คุณต้องการ. การเรียกตัวสร้างจาก clone () จะเป็นปัญหาเนื่องจากจะทำลายการสืบทอดในกรณีที่คลาสย่อยต้องการเพิ่มตรรกะที่สามารถโคลนได้เพิ่มเติมของตัวเอง ถ้าจะเรียก super.clone () มันจะได้รับวัตถุของคลาสที่ไม่ถูกต้องในกรณีนี้

วิธีนี้จะข้ามตรรกะใด ๆ ที่อาจกำหนดไว้ในตัวสร้างของคุณซึ่งอาจเป็นปัญหาได้

ปัญหาอีกประการหนึ่งคือคลาสย่อยใด ๆ ที่ลืมแทนที่ clone () จะสืบทอดสำเนาตื้นเริ่มต้นโดยอัตโนมัติซึ่งอาจไม่ใช่สิ่งที่คุณต้องการในกรณีที่สถานะไม่แน่นอน (ซึ่งตอนนี้จะแชร์ระหว่างซอร์สและสำเนา)

นักพัฒนาส่วนใหญ่ไม่ใช้ Cloneable ด้วยเหตุผลเหล่านี้และใช้ตัวสร้างการคัดลอกแทน

สำหรับข้อมูลเพิ่มเติมและข้อผิดพลาดที่อาจเกิดขึ้นของ Cloneable ฉันขอแนะนำหนังสือ Effective Java โดย Joshua Bloch


12
  1. การโคลนนิ่งก่อให้เกิดวิธีการสร้างวัตถุที่เป็นภาษาพิเศษโดยไม่ต้องใช้ตัวสร้าง
  2. การโคลนต้องการให้คุณดำเนินการกับ CloneNotSupportedException หรือรบกวนรหัสไคลเอ็นต์ในการรักษา
  3. ประโยชน์มีน้อย - คุณไม่จำเป็นต้องเขียนตัวสร้างการคัดลอกด้วยตนเอง

ดังนั้นให้ใช้ Cloneable อย่างรอบคอบ ไม่ได้ให้ประโยชน์เพียงพอเมื่อเทียบกับความพยายามที่คุณต้องใช้เพื่อทำทุกอย่างให้ถูกต้อง


อย่างที่ Bozho บอกอย่าใช้ Cloneable ให้ใช้ตัวสร้างการคัดลอกแทน javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane ถ้าคุณไม่ทราบประเภทของวัตถุที่จะโคลนคุณจะรู้ได้อย่างไรว่าตัวสร้างการคัดลอกของคลาสใดที่จะเรียกใช้
Steve Kuo

@ สตีฟ: ฉันไม่ทำตาม หากคุณกำลังจะโคลนวัตถุฉันคิดว่าคุณรู้อยู่แล้วว่ามันเป็นประเภทไหน - หลังจากนั้นคุณมีวัตถุอยู่ในมือที่คุณกำลังวางแผนจะโคลนนิ่ง และหากมีสถานการณ์ที่วัตถุของคุณสูญเสียเป็นประเภทเฉพาะไปยังวัตถุทั่วไปคุณไม่สามารถประเมินโดยใช้ 'อินสแตนซ์' แบบง่ายๆได้หรือไม่ ???
Bane

4
@Bane: สมมติว่าคุณมีรายการวัตถุทั้งหมดที่มาจากประเภท A อาจจะมี 10 ประเภทที่แตกต่างกัน คุณไม่รู้ว่าวัตถุแต่ละประเภทเป็นอย่างไร การใช้อินสแตนซ์ของกรณีนี้เป็นความคิดที่แย่มาก หากคุณเพิ่มประเภทอื่นทุกครั้งที่คุณทำสิ่งนี้คุณจะต้องเพิ่มการทดสอบอินสแตนซ์อื่น และจะเกิดอะไรขึ้นถ้าคลาสที่ได้รับอยู่ในแพ็คเกจอื่นที่คุณไม่สามารถเข้าถึงได้? การโคลนนิ่งเป็นรูปแบบทั่วไป ใช่การใช้งาน java ไม่ดี แต่มีหลายวิธีที่จะใช้งานได้ดี ตัวสร้างสำเนาไม่ใช่การดำเนินการที่เทียบเท่า
Charles

@ ชาร์ลส์: ในกรณีที่ไม่มีตัวอย่างโดยละเอียดและขาดประสบการณ์ล่าสุดในการจัดการกับปัญหาประเภทนี้ฉันจะต้องเลื่อนไปที่ Bloch รายการ # 11. มันยาวและอ่านยากไปหน่อย แต่โดยพื้นฐานแล้วมันบอกว่า "หลีกเลี่ยงการโคลนนิ่งเมื่อใดก็ตามที่ทำได้ผู้สร้างการคัดลอกคือเพื่อนของคุณ"
สารพิษ

7

การโคลนเป็นกระบวนทัศน์การเขียนโปรแกรมขั้นพื้นฐาน ความจริงที่ว่า Java อาจใช้งานได้ไม่ดีในหลาย ๆ ด้านไม่ได้ช่วยลดความจำเป็นในการโคลนลงเลย และมันเป็นเรื่องง่ายที่จะใช้การโคลนนิ่งที่จะทำงานตามที่คุณต้องการให้ทำงานตื้นลึกและหลากหลายอะไรก็ได้ คุณยังสามารถใช้ชื่อโคลนสำหรับฟังก์ชั่นและไม่ใช้ Cloneable ได้หากต้องการ

สมมติว่าฉันมีคลาส A, B และ C โดยที่ B และ C มาจาก A ถ้าฉันมีรายการวัตถุประเภท A ดังนี้:

ArrayList<A> list1;

ตอนนี้รายการดังกล่าวอาจมีวัตถุประเภท A, B หรือ C คุณไม่รู้ว่าวัตถุประเภทใด ดังนั้นคุณไม่สามารถคัดลอกรายการได้ดังนี้:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

หากวัตถุเป็นประเภท B หรือ C จริงคุณจะไม่ได้รับสำเนาที่ถูกต้อง แล้วถ้า A เป็นนามธรรมล่ะ? ตอนนี้มีบางคนแนะนำสิ่งนี้:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

นี่เป็นความคิดที่แย่มาก จะเกิดอะไรขึ้นถ้าคุณเพิ่มประเภทที่ได้รับใหม่? จะเกิดอะไรขึ้นถ้า B หรือ C อยู่ในแพ็คเกจอื่นและคุณไม่สามารถเข้าถึงได้ในคลาสนี้

สิ่งที่คุณต้องการทำคือ:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

ผู้คนจำนวนมากระบุว่าเหตุใดการใช้งาน Java พื้นฐานของการโคลนจึงมีปัญหา แต่มันเอาชนะได้อย่างง่ายดายด้วยวิธีนี้:

ในคลาส A:

public A clone() {
    return new A(this);
}

ในคลาส B:

@Override
public B clone() {
    return new B(this);
}

ในคลาส C:

@Override
public C clone() {
    return new C(this):
}

ฉันไม่ได้ใช้ Cloneable เพียงแค่ใช้ชื่อฟังก์ชันเดียวกัน ถ้าไม่ชอบก็ตั้งชื่ออย่างอื่น


ฉันเพิ่งเห็นสิ่งนี้หลังจากตอบกลับความคิดเห็นของคุณในคำตอบแยกต่างหาก ฉันเห็นว่าตอนนี้คุณกำลังไปที่ไหน แต่มี 2 สิ่ง: 1) OP ถามเป็นพิเศษเกี่ยวกับการใช้ Cloneable (ไม่เกี่ยวกับแนวคิดทั่วไปของการโคลนนิ่ง) และ 2) คุณกำลังแยกเส้นขนเล็กน้อยเพื่อพยายามแยกความแตกต่างระหว่างตัวสร้างสำเนา และแนวคิดทั่วไปของการโคลนนิ่ง ความคิดที่คุณถ่ายทอดที่นี่นั้นถูกต้อง แต่ที่รากนั้นคุณแค่ใช้ตัวสร้างสำเนา ;)
สารพิษ

แม้ว่าฉันอยากจะบอกว่าฉันเห็นด้วยกับแนวทางของคุณที่นี่ แต่ก็รวมถึง A # copyMethod () แทนที่จะบังคับให้ผู้ใช้เรียกตัวสร้างสำเนาโดยตรง
สารพิษ

5

A) ไม่มีข้อดีของการโคลนมากกว่าตัวสร้างสำเนา อาจเป็นสิ่งที่ใหญ่ที่สุดคือความสามารถในการสร้างวัตถุใหม่ที่มีประเภทไดนามิกเดียวกันทั้งหมด (สมมติว่าประเภทที่ประกาศนั้นสามารถโคลนได้และมีวิธีการโคลนสาธารณะ)

B) การโคลนเริ่มต้นจะสร้างสำเนาแบบตื้นและจะยังคงเป็นสำเนาตื้น ๆ เว้นแต่การใช้งานการโคลนของคุณจะเปลี่ยนไป อาจเป็นเรื่องยากโดยเฉพาะอย่างยิ่งถ้าชั้นเรียนของคุณมีช่องสุดท้าย

Bozho พูดถูกการโคลนอาจเป็นเรื่องยากที่จะทำให้ถูกต้อง ผู้สร้างสำเนา / โรงงานจะตอบสนองความต้องการส่วนใหญ่


0

ข้อเสียของ Cloneable คืออะไร?

การโคลนนิ่งเป็นสิ่งที่อันตรายมากหากวัตถุที่คุณกำลังคัดลอกมีองค์ประกอบคุณต้องคิดถึงผลข้างเคียงที่อาจเกิดขึ้นด้านล่างในกรณีนี้เนื่องจากการโคลนจะสร้างสำเนาตื้น ๆ :

สมมติว่าคุณมีวัตถุหนึ่งชิ้นที่จะจัดการกับการจัดการที่เกี่ยวข้องกับฐานข้อมูล สมมติว่าวัตถุนั้นมีConnectionวัตถุเป็นหนึ่งในคุณสมบัติ

ดังนั้นเมื่อมีคนสร้างโคลนของวัตถุที่ถูกสร้างขึ้นให้พูดว่าoriginalObject cloneObjectที่นี่originalObjectและcloneObjectถืออ้างอิงเดียวกันสำหรับConnectionวัตถุ

สมมติว่าoriginalObjectปิดConnectionวัตถุดังนั้นตอนนี้cloneObjectจะไม่ทำงานเนื่องจากconnectionวัตถุถูกแชร์ระหว่างพวกเขาและมันถูกปิดโดยอัตโนมัติโดยไฟล์originalObject.

ปัญหาที่คล้ายกันอาจเกิดขึ้นหากคุณต้องการโคลนวัตถุที่มี IOStream เป็นคุณสมบัติ

การโคลนแบบวนซ้ำจะเกิดขึ้นได้อย่างไรหากวัตถุนั้นเป็นวัตถุผสม

Cloneable ทำสำเนาตื้น ความหมายคือข้อมูลของวัตถุดั้งเดิมและวัตถุโคลนจะชี้ไปที่การอ้างอิง / หน่วยความจำเดียวกัน ตรงกันข้ามในกรณีของการคัดลอกแบบลึกข้อมูลจากหน่วยความจำของวัตถุต้นฉบับจะถูกคัดลอกไปยังหน่วยความจำของวัตถุโคลน


ย่อหน้าสุดท้ายของคุณสับสนมาก Cloneableไม่ทำการคัดลอกObject.cloneทำ "ข้อมูลจากหน่วยความจำของวัตถุดั้งเดิมจะถูกคัดลอกไปยังหน่วยความจำของวัตถุโคลน" คือสิ่งที่Object.cloneทำได้อย่างแม่นยำ คุณต้องพูดถึงหน่วยความจำของวัตถุที่อ้างอิงเพื่ออธิบายการคัดลอกแบบลึก
aioobe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.