คุณจะทำสำเนาวัตถุลึก ๆ ได้อย่างไร


301

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


4
Kryo ได้สร้างในการสนับสนุนการคัดลอก / โคลน นี่เป็นการคัดลอกโดยตรงจากวัตถุไปยังวัตถุไม่ใช่วัตถุ -> ไบต์ -> วัตถุ
NateS

1
นี่เป็นคำถามที่เกี่ยวข้องที่ถูกถามในภายหลัง: recomendation ยูทิลิตี้โคลนลึก
แบรด Cupit

การใช้ห้องสมุดโคลนช่วยฉันด้วย! github.com/kostaskougios/cloning
gaurav

คำตอบ:


168

วิธีที่ปลอดภัยคือการทำให้เป็นอันดับวัตถุแล้วยกเลิกการเรียงลำดับ สิ่งนี้ทำให้มั่นใจได้ว่าทุกอย่างเป็นการอ้างอิงใหม่

นี่คือบทความเกี่ยวกับวิธีการทำสิ่งนี้อย่างมีประสิทธิภาพ

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


6
โปรดทราบว่าการใช้งาน FastByteArrayOutputStream ที่ให้ไว้ในบทความอาจมีประสิทธิภาพมากกว่า มันใช้การขยายสไตล์ ArrayList เมื่อบัฟเฟอร์เต็ม แต่จะดีกว่าถ้าใช้วิธีการขยายสไตล์ LinkedList แทนที่จะสร้างบัฟเฟอร์ 2x ใหม่และ memcpy-ing บัฟเฟอร์ปัจจุบันทับรักษารายการที่เชื่อมโยงของบัฟเฟอร์การเพิ่มบัฟเฟอร์ใหม่เมื่อกระแสเติมเต็ม หากคุณได้รับคำขอให้เขียนข้อมูลมากกว่าที่จะพอดีกับขนาดบัฟเฟอร์เริ่มต้นของคุณให้สร้างโหนดบัฟเฟอร์ที่มีขนาดใหญ่เท่ากับการร้องขอ โหนดไม่จำเป็นต้องมีขนาดเท่ากัน
ไบรอันแฮร์ริส


บทความที่ดีซึ่งอธิบายถึงการทำสำเนาแบบลึกผ่านการทำให้เป็นอนุกรม: javaworld.com/article/2077578/learn-java/ ......
โฆษณา Infinitum

@BrianHarris รายการที่เชื่อมโยงนั้นไม่ได้มีประสิทธิภาพมากกว่าอาเรย์แบบไดนามิก การแทรกองค์ประกอบลงในอาเรย์แบบไดนามิกจะถูกตัดจำหน่ายความซับซ้อนคงที่ในขณะที่การแทรกเข้าไปในรายการที่เชื่อมโยงนั้นเป็นความซับซ้อนเชิงเส้น
Norill Tempest

ซีเรียลไลซ์ & ดีซีเรียลไลซ์เท่าไหร่ช้ากว่าวิธีการสร้างตัวคัดลอก
Woland

75

Object.clone()ไม่กี่คนที่ได้กล่าวถึงการใช้หรือการเอาชนะ อย่าทำมัน Object.clone()มีปัญหาที่สำคัญบางอย่างและการใช้งานไม่ได้รับการสนับสนุนในกรณีส่วนใหญ่ โปรดดูรายการที่ 11 จาก " มีผลบังคับใช้ Java " โดยโจชัวโบลชหาคำตอบที่สมบูรณ์ ฉันเชื่อว่าคุณสามารถใช้Object.clone()กับอาเรย์แบบดั้งเดิมได้อย่างปลอดภัยแต่นอกเหนือจากนั้นคุณต้องมีความรอบคอบในการใช้และการแทนที่อย่างถูกต้อง

โครงร่างที่ขึ้นอยู่กับการทำให้เป็นอันดับ (XML หรืออื่น ๆ ) เป็น kludgy

ไม่มีคำตอบง่าย ๆ ที่นี่ หากคุณต้องการคัดลอกวัตถุที่ลึกคุณจะต้องสำรวจกราฟวัตถุและคัดลอกวัตถุลูกแต่ละรายการอย่างชัดเจนผ่านตัวสร้างสำเนาของวัตถุหรือวิธีการโรงงานคงที่ที่จะคัดลอกวัตถุลูกลึก ไม่StringสามารถคัดลอกImmutables (เช่น) ได้ คุณควรสนับสนุนความไม่เปลี่ยนรูปด้วยเหตุผลนี้


58

คุณสามารถทำสำเนาลึกด้วยซีเรียลไลซ์เซชั่นโดยไม่ต้องสร้างไฟล์

implement serializableวัตถุของคุณคุณต้องการคัดลอกลึกจะต้อง หากชั้นเรียนไม่เป็นที่สิ้นสุดหรือไม่สามารถแก้ไขได้ให้ขยายชั้นเรียนและใช้งานได้

แปลงคลาสของคุณเป็นจำนวนไบต์:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

กู้คืนคลาสของคุณจากกระแสไบต์:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();

4
ถ้าชั้นเรียนเป็นอย่างไรคุณจะขยายเวลาออกไป?
Kumar Manish

1
คลาส @KumarManish MyContainer ใช้อินสแตนซ์ {อินสแตนซ์ MyFinalClass ต่อเนื่องได้; ... }
Matteo T.

ฉันพบว่านี่เป็นคำตอบที่ยอดเยี่ยม Clone is ระเบียบ
blackbird014

@MatteoT คุณสมบัติของคลาสที่ไม่สามารถทำให้เป็นอนุกรมได้จะถูกทำให้เป็นอนุกรมอย่างไรinstanceในกรณีนี้?
Farid

40

คุณสามารถทำการโคลนแบบลึกที่ใช้การทำซีเรียลorg.apache.commons.lang3.SerializationUtils.clone(T)ไลเซชันโดยใช้ใน Apache Commons Lang แต่ต้องระวัง - ประสิทธิภาพเป็นสุดยอด

โดยทั่วไปแล้ววิธีที่ดีที่สุดคือการเขียนวิธีการโคลนของคุณเองสำหรับแต่ละคลาสของวัตถุในกราฟวัตถุที่ต้องการการโคลน


นอกจากนี้ยังมีในorg.apache.commons.lang.SerializationUtils
Pino

25

วิธีหนึ่งที่จะใช้การทำสำเนาลึกคือการเพิ่มตัวสร้างสำเนาให้กับแต่ละคลาสที่เกี่ยวข้อง ตัวสร้างการคัดลอกใช้อินสแตนซ์ของ 'this' เป็นอาร์กิวเมนต์เดียวและคัดลอกค่าทั้งหมดจากมัน ค่อนข้างทำงาน แต่ค่อนข้างตรงไปตรงมาและปลอดภัย

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

ตัวอย่าง:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

แก้ไข: โปรดทราบว่าเมื่อใช้ตัวสร้างสำเนาคุณต้องทราบประเภทรันไทม์ของวัตถุที่คุณกำลังคัดลอก ด้วยวิธีการข้างต้นคุณไม่สามารถคัดลอกรายการแบบผสมได้อย่างง่ายดาย


1
เพียงแค่สนใจในกรณีที่สิ่งที่คุณกำลังคัดลอกเป็นคลาสย่อย แต่ถูกอ้างอิงโดยผู้ปกครอง เป็นไปได้หรือไม่ที่จะแทนที่ตัวสร้างสำเนา
Pork 'n' Bunny

ทำไมคลาสแม่ของคุณถึงคลาสย่อย คุณยกตัวอย่างได้ไหม
Adriaan Koster

1
รถยนต์ระดับสาธารณะขยายยานพาหนะและจากนั้นอ้างถึงรถยนต์เป็นยานพาหนะ originaList = new ArrayList <Vehicle>; copyList = new ArrayList <Vehicle>; originalList.add (รถยนต์ใหม่ ()); สำหรับ (ยานพาหนะยานพาหนะ: vehicleList) {copyList.add (ยานพาหนะใหม่ (ยานพาหนะ)); }
หมู 'n' Bunny

@AdriaanKoster: หากรายการเดิมมีToyotaรหัสของคุณจะใส่Carในรายการปลายทาง การโคลนนิ่งที่เหมาะสมนั้นโดยทั่วไปต้องการคลาสที่จัดเตรียมวิธีโรงงานเสมือนซึ่งสัญญาระบุว่าจะส่งคืนออบเจ็กต์ใหม่ของคลาสของตัวเอง ตัวสร้างสำเนาตัวเองควรจะprotectedตรวจสอบให้แน่ใจว่ามันจะถูกใช้เพื่อสร้างวัตถุที่มีชนิดที่ตรงกับวัตถุที่ถูกคัดลอกเท่านั้น)
supercat

ดังนั้นถ้าฉันเข้าใจข้อเสนอแนะของคุณอย่างถูกต้องวิธีโรงงานจะเรียกตัวสร้างสำเนาส่วนตัวหรือไม่ ตัวสร้างการคัดลอกของคลาสย่อยจะตรวจสอบได้อย่างไรว่าฟิลด์ซูเปอร์คลาสนั้นเริ่มต้นได้อย่างไร คุณยกตัวอย่างได้ไหม
Adriaan Koster

20

คุณสามารถใช้ไลบรารีที่มี API อย่างง่ายและทำการโคลนอย่างรวดเร็วด้วยการสะท้อน (ควรเร็วกว่าวิธีการทำให้เป็นอนุกรม)

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

19

Apache Commons เสนอวิธีที่รวดเร็วในการโคลนวัตถุ

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);

1
สิ่งนี้ใช้ได้กับวัตถุที่ใช้ Serializable เท่านั้นและสำหรับเขตข้อมูลทั้งหมดที่ใช้ Serializable
wonhee

11

XStream มีประโยชน์จริง ๆ ในกรณีดังกล่าว นี่คือรหัสง่าย ๆ ที่จะทำการโคลน

private static final XStream XSTREAM = new XStream();
...

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));

1
ไม่คุณไม่จำเป็นต้องมีค่าใช้จ่ายในการ xml-ing วัตถุ
egelev

@egeleve คุณรู้หรือไม่ว่าคุณกำลังตอบความคิดเห็นจาก '08 ใช่ไหม? ฉันไม่ได้ใช้ Java อีกแล้วและตอนนี้อาจมีเครื่องมือที่ดีกว่า อย่างไรก็ตามในเวลานั้นการจัดลำดับเป็นรูปแบบที่แตกต่างกันและจากนั้นการแบ็กอัพซีเรียลไลซ์ให้ดูเหมือนแฮ็คที่ดี - มันไม่มีประสิทธิภาพอย่างแน่นอน
sankara


10

สำหรับผู้ใช้Spring Framework ใช้คลาสorg.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}

9

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

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

public static <T> T copy(T anObject, Class<T> classInfo) {
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(anObject);
    T newObject = gson.fromJson(text, classInfo);
    return newObject;
}
public static void main(String[] args) {
    String originalObject = "hello";
    String copiedObject = copy(originalObject, String.class);
}

3
โปรดปฏิบัติตามข้อตกลงการตั้งชื่อ Java เพื่อประโยชน์ของคุณเองและของเรา
Patrick Bergner

8

ใช้ XStream ( http://x-stream.github.io/ ) คุณสามารถควบคุมคุณสมบัติที่คุณสามารถละเว้นผ่านคำอธิบายประกอบหรือระบุชื่อคุณสมบัติให้กับคลาส XStream อย่างชัดเจน ยิ่งกว่านั้นคุณไม่จำเป็นต้องใช้อินเทอร์เฟซแบบ clonable


7

การทำสำเนาลึกสามารถทำได้เฉพาะเมื่อได้รับความยินยอมจากแต่ละชั้นเรียน หากคุณมีการควบคุมลำดับชั้นของชั้นเรียนแล้วคุณสามารถใช้ส่วนต่อประสาน clonable และใช้วิธีการโคลน มิฉะนั้นการทำสำเนาที่ลึกนั้นเป็นไปไม่ได้ที่จะทำอย่างปลอดภัยเพราะวัตถุอาจแชร์ทรัพยากรที่ไม่ใช่ข้อมูล (เช่นการเชื่อมต่อฐานข้อมูล) โดยทั่วไปแล้วการทำสำเนาแบบลึกถือว่าเป็นการใช้งานที่ไม่เหมาะสมในสภาพแวดล้อม Java และควรหลีกเลี่ยงผ่านวิธีการออกแบบที่เหมาะสม


2
คุณสามารถอธิบาย "แนวทางการออกแบบที่เหมาะสม" ได้หรือไม่?
fklappan

6
import com.thoughtworks.xstream.XStream;

public class deepCopy {
    private static  XStream xstream = new XStream();

    //serialize with Xstream them deserialize ...
    public static Object deepCopy(Object obj){
        return xstream.fromXML(xstream.toXML(obj));
    }
}



2

1)

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;
   }
 }

2)

    // (1) create a MyPerson object named Al
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
    MyPerson al = new MyPerson("Al", "Arun", address);

    // (2) make a deep clone of Al
    MyPerson neighbor = (MyPerson)deepClone(al);

นี่คือ MyPerson และ MyAddress class ของคุณจะต้องติดตั้งส่วนต่อประสานที่ปลอดภัย


2

การใช้แจ็คสันเพื่อทำให้เป็นอันดับและทำให้เป็นวัตถุเป็นอนุกรม การใช้งานนี้ไม่ต้องการวัตถุเพื่อใช้คลาส Serializable

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

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