Java: โซลูชันที่แนะนำสำหรับการโคลน / คัดลอกอินสแตนซ์


176

ฉันสงสัยว่ามีวิธีแนะนำในการทำโคลนนิ่ง / สำเนาอินสแตนซ์ใน java หรือไม่

ฉันมี 3 โซลูชั่นในใจ แต่ฉันสามารถพลาดบางอย่างและฉันต้องการที่จะมีความคิดเห็นของคุณ

แก้ไข: รวมถึงข้อเสนอ Bohzo และปรับแต่งคำถาม: มันเป็นเรื่องของการโคลนนิ่งลึกกว่าการโคลนนิ่งตื้น

ทำด้วยตัวคุณเอง:

รหัสโคลนด้วยมือคุณสมบัติหลังจากคุณสมบัติและตรวจสอบว่าอินสแตนซ์ที่ไม่แน่นอนจะถูกโคลนเกินไป
โปร:
- การควบคุมสิ่งที่จะดำเนินการ
-
ข้อเสียในการดำเนินการอย่างรวดเร็ว:
- น่าเบื่อในการเขียนและบำรุงรักษา
- ข้อผิดพลาดได้ง่าย (ความล้มเหลวในการคัดลอก / วาง, ทรัพย์สินที่หายไป, คุณสมบัติที่ไม่แน่นอนที่กำหนดใหม่)

ใช้การสะท้อน:

ด้วยเครื่องมือการสะท้อนของคุณเองหรือผู้ช่วยภายนอก (เช่น jakarta common-beans) มันง่ายที่จะเขียนวิธีการคัดลอกทั่วไปที่จะทำงานในบรรทัดเดียว
โปร:
- ง่ายต่อการเขียน
- ไม่มีการบำรุงรักษา
ข้อเสีย:
- การควบคุมที่น้อยลงของสิ่งที่เกิดขึ้น
- ข้อผิดพลาดได้ง่ายกับวัตถุที่ไม่แน่นอนถ้าเครื่องมือการสะท้อนไม่ได้โคลนวัตถุย่อยเกินไป
- การดำเนินการช้าลง

ใช้เฟรมเวิร์กโคลน:

ใช้กรอบงานที่ทำเพื่อคุณเช่น:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

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

ใช้เครื่องมือ bytecode เพื่อเขียนโคลนที่รันไทม์

javassit , BCELหรือcglibอาจถูกใช้เพื่อสร้าง cloner โดยเฉพาะเร็วที่สุดเท่าที่เขียนด้วยมือเดียว มีคนรู้จัก lib โดยใช้หนึ่งในเครื่องมือเหล่านี้เพื่อการนี้

สิ่งที่ฉันพลาดที่นี่?
คุณจะแนะนำแบบไหน

ขอบคุณ


1
เห็นได้ชัดว่า Java Deep Cloning Library ย้ายมาที่นี่: code.google.com/p/cloning
Mr_and_Mrs_D

คำตอบ:


155

สำหรับการโคลนลึก (โคลนลำดับชั้นวัตถุทั้งหมด):

  • คอมมอนส์-lang SerializationUtils - ใช้อนุกรม - Serializableถ้าทุกชั้นอยู่ในการควบคุมของคุณและคุณสามารถบังคับให้ดำเนินการ

  • Java Deep Cloning Library - การใช้การสะท้อน - ในกรณีที่คลาสหรือวัตถุที่คุณต้องการโคลนนั้นอยู่นอกเหนือการควบคุมของคุณ ( ไลบรารี่ของบุคคลที่สาม) และคุณไม่สามารถทำให้พวกมันใช้งานSerializableได้หรือในกรณีที่คุณไม่ต้องการใช้งานSerializable.

สำหรับการโคลนแบบตื้น (โคลนเฉพาะคุณสมบัติระดับแรก):

ฉันตั้งใจละเว้นตัวเลือก "do-it-yourself" - API ข้างต้นให้การควบคุมที่ดีเกี่ยวกับสิ่งที่และสิ่งที่ไม่ควรโคลน (ตัวอย่างเช่นการใช้transientหรือString[] ignoreProperties) ดังนั้นจึงไม่จำเป็นต้องคิดค้นล้อใหม่


ขอบคุณ Bozho มันมีค่า และฉันเห็นด้วยกับคุณเกี่ยวกับตัวเลือก DIY! คุณเคยลอง serialzation ทั่วไปและ / หรือ lib โคลนลึก? สิ่งที่เกี่ยวกับความสมบูรณ์
Guillaume

ใช่ฉันใช้ตัวเลือกทั้งหมดข้างต้นด้วยเหตุผลข้างต้น :) เฉพาะห้องสมุดโคลนเท่านั้นที่มีปัญหาบางอย่างเมื่อผู้รับมอบฉันทะ CGLIB เข้ามาเกี่ยวข้องและพลาดฟังก์ชั่นที่ต้องการ แต่ฉันคิดว่าควรแก้ไขในตอนนี้
Bozho

สวัสดีถ้า Entity ของฉันแนบมาและฉันมีสิ่งที่ขี้เกียจ SerializationUtils จะตรวจสอบฐานข้อมูลเพื่อหาคุณสมบัติของ lazy หรือไม่ เพราะนี่คือสิ่งที่ฉันต้องการและไม่!
Cosmin Cosmin

ถ้าคุณมีเซสชั่นที่ใช้งานอยู่ - ใช่มันจะทำ
Bozho

@Bozho ดังนั้นคุณหมายความว่าถ้าวัตถุทั้งหมดในถั่วกำลังดำเนินการ serialize, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) จะทำสำเนาลึก?
กระโดด

36

หนังสือของโจชัวโบลชมีทั้งบทที่ชื่อว่า"รายการ 10: การแทนที่โคลนอย่างรอบคอบ"ซึ่งทำให้เขาเข้าใจว่าทำไมการแทนที่การโคลนนิ่งส่วนใหญ่จึงเป็นความคิดที่ไม่ดีเพราะ Java spec สำหรับมันสร้างปัญหามากมาย

เขาให้ทางเลือกไม่กี่:

  • ใช้รูปแบบโรงงานแทนคอนสตรัคเตอร์:

         public static Yum newInstance(Yum yum);
  • ใช้ตัวสร้างสำเนา:

         public Yum(Yum yum);

คลาสการรวบรวมทั้งหมดใน Java สนับสนุนตัวสร้างการคัดลอก (เช่น ArrayList ใหม่ (l);)


1
ตกลง ในโครงการของฉันฉันกำหนดCopyableอินเทอร์เฟซที่ประกอบด้วยgetCopy()วิธีการ เพียงใช้รูปแบบต้นแบบด้วยตนเอง
gpampara

ฉันไม่ได้ถามเกี่ยวกับอินเทอร์เฟซ cloneable แต่วิธีการดำเนินการโคลน / คัดลอกลึก ด้วย Constructor หรือ Factory คุณยังจำเป็นต้องสร้างอินสแตนซ์ใหม่จากแหล่งที่มาของคุณ
Guillaume

@ Guillaume ฉันคิดว่าคุณต้องระวังในการใช้คำว่า clone / copy โคลนและคัดลอกใน java ไม่ได้หมายถึงสิ่งเดียวกัน Java spec มีมากกว่าที่จะพูดเกี่ยวกับเรื่องนี้ .... ฉันคิดว่าคุณต้องการสำเนาลึกจากสิ่งที่ฉันสามารถบอกได้
LeWoody

ตกลง Java spec มีความแม่นยำเกี่ยวกับสิ่งที่โคลนคือ ... แต่เรายังสามารถพูดถึงโคลนในความหมายทั่วไป ... ตัวอย่างเช่นหนึ่งใน lib ที่ bohzo แนะนำโดยใช้ชื่อว่า 'Java Deep Cloning Library' ...
Guillaume

2
@LWoodyiii newInstance()วิธีการนี้และผู้Yumสร้างจะทำสำเนาลึกหรือคัดลอกตื้น?
Geek

9

ตั้งแต่รุ่น 2.07 Kryo รองรับการโคลนแบบตื้น / ลึก :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo นั้นรวดเร็วคุณสามารถค้นหารายชื่อ บริษัท ที่ใช้ในการผลิตได้ที่หน้าเพจและของพวกเขา


5

ใช้ XStream toXML / fromXML ในหน่วยความจำ เร็วมากและได้รับรอบเป็นเวลานานและจะแข็งแกร่ง วัตถุไม่จำเป็นต้องต่อเนื่องและคุณไม่ได้ใช้การสะท้อน (แม้ว่า XStream จะทำ) XStream สามารถแยกแยะตัวแปรที่ชี้ไปที่วัตถุเดียวกันและไม่ตั้งใจทำสำเนาสองชุดเต็มของอินสแตนซ์ รายละเอียดมากมายเช่นนั้นถูกทุบออกมาในช่วงหลายปีที่ผ่านมา ฉันใช้มันมาหลายปีแล้วและมันก็เป็นไป มันใช้งานง่ายอย่างที่คุณคิด

new XStream().toXML(myObj)

หรือ

new XStream().fromXML(myXML)

เพื่อโคลน

new XStream().fromXML(new XStream().toXML(myObj))

รัดกุมมากขึ้น:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));

3

สำหรับวัตถุที่ซับซ้อนและเมื่อประสิทธิภาพการทำงานไม่สำคัญฉันใช้gson เพื่อทำให้วัตถุเป็นข้อความ json ให้เป็นอนุกรมแล้วจึงทำการ deserialize ข้อความเพื่อรับวัตถุใหม่

gson ซึ่งอยู่บนพื้นฐานของการสะท้อนจะงานในกรณีส่วนใหญ่ยกเว้นว่าเขตจะไม่สามารถคัดลอกและวัตถุที่มีการอ้างอิงวงกลมที่มีสาเหตุtransientStackOverflowError

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}

2

ขึ้นอยู่กับ

สำหรับความเร็วใช้ DIY สำหรับกระสุนใช้การสะท้อนกลับ

BTW การทำให้เป็นอนุกรมนั้นไม่เหมือนกับ refl เนื่องจากบางอ็อบเจ็กต์อาจมีวิธีการทำให้เป็นอนุกรมที่แทนที่ (readObject / writeObject) และพวกมันสามารถเป็นบั๊กกี้


1
การสะท้อนนั้นไม่ได้เป็นหลักฐานกระสุน: มันสามารถนำไปสู่ในบางสถานการณ์ที่วัตถุโคลนของคุณมีการอ้างอิงถึงแหล่งที่มาของคุณ ... หากการเปลี่ยนแปลงแหล่งที่มาโคลนจะเปลี่ยนเช่นกัน!
Guillaume

1

ฉันขอแนะนำวิธี DIY ซึ่งรวมกับวิธี hashCode () และ equals () ที่ดีควรง่ายต่อการพิสูจน์ในการทดสอบหน่วย


ดีคนขี้เกียจฉันดังมากเมื่อสร้างรหัสจำลอง แต่มันดูเหมือนเส้นทางที่ฉลาด ...
Guillaume

2
ขออภัย DIY เป็นวิธีที่จะไปเท่านั้นหากไม่มีการแก้ปัญหาอื่น ๆ ที่เป็นที่เหมาะสมสำหรับ you..which เกือบจะไม่เคย
Bozho

1

ฉันขอแนะนำให้แทนที่ Object.clone (), เรียก super.clone () ก่อนและดีกว่าเรียก ref = ref.clone () ในการอ้างอิงทั้งหมดที่คุณต้องการคัดลอกลึก มันมากขึ้นหรือน้อยลงใช้วิธีของคุณเองแต่ต้องการการเข้ารหัสน้อยลง


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

0

สำหรับการโคลนนิ่งลึกใช้ Serializable ในทุกคลาสที่คุณต้องการโคลนเช่นนี้

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

จากนั้นใช้ฟังก์ชั่นนี้:

public static Object deepClone(Object object) {
    try {
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

แบบนี้: Obj newObject = (Obj)deepClone(oldObject);

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