ก่อนที่จะเข้าสู่หัวข้ออย่างเป็นทางการก่อนอื่นให้เข้าใจแนวคิดของการคัดลอกลึกและสำเนาล่วงหน้า:
สำเนาเบา:
วัตถุใหม่จะถูกสร้างขึ้นซึ่งมีสำเนาที่แน่นอนของค่าคุณสมบัติของวัตถุต้นฉบับ หากแอตทริบิวต์เป็นประเภทพื้นฐานค่าของประเภทพื้นฐานจะถูกคัดลอก หากแอตทริบิวต์เป็นที่อยู่หน่วยความจำที่อยู่หน่วยความจำจะถูกคัดลอกดังนั้นหากวัตถุเปลี่ยนที่อยู่นี้จะส่งผลกระทบต่อวัตถุอื่น
สำเนาลึก:
ไม่เพียง แต่คุณต้องการคัดลอกค่าตัวแปรสมาชิกที่ไม่ได้อ้างอิงทั้งหมดของวัตถุ แต่คุณต้องสร้างอินสแตนซ์ใหม่สำหรับตัวแปรสมาชิกของประเภทการอ้างอิงและเริ่มต้นเป็นค่าอินสแตนซ์พารามิเตอร์ที่เป็นทางการ
หลังจากทำความเข้าใจกับแนวคิดให้ทดสอบว่าการดำเนินการกำหนดวัตถุธรรมดาเป็นสำเนาลึกหรือสำเนาตื้น:
รหัสทดสอบ:
ระดับความลึกระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {คัดลอก first = new Copy ("HZW", 24); คัดลอกวินาที = ก่อน; second.name = "Shanxi"; System.out.println (first.name); // output shanxi}} class copy {ชื่อสตริงสาธารณะ; อายุ int สาธารณะ; สำเนาสาธารณะ (ชื่อสตริงอายุ int) {this.name = name; this.age = อายุ; - จะพบได้ว่าหลังจากแก้ไขค่าแอตทริบิวต์ชื่อเป็นครั้งที่สองเป็น Shanxi ค่าแอตทริบิวต์ชื่อแรกก็จะกลายเป็น Shanxi นี่แสดงให้เห็นว่าการมอบหมายวัตถุธรรมดาเป็นของสำเนาตื้น
หลังจากทำความเข้าใจว่าการมอบหมายระหว่างวัตถุเป็นสำเนาตื้นลองมาดูกันว่าการโคลนนิ่งเป็นสำเนาลึกหรือสำเนาตื้นหรือไม่ รหัสทดสอบคือการเปิดใช้งานวัตถุคัดลอกด้านบนเพื่อใช้วิธีการโคลนในอินเตอร์เฟส cloneable:
ระดับความลึกระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {คัดลอก first = new Copy ("HZW", 24); คัดลอกวินาที = null; ลอง {second = (copy) first.clone (); } catch (clonenotsupportedException e) {e.printStackTrace (); } second.name = "Shanxi"; System.out.println (first.name); // output: hzw system.out.println (ครั้งแรก); // output: com.hzw.day33.copy@7f39ebdb system.out.println (ที่สอง); // output: com.hzw.day33.copy@33 อายุ int สาธารณะ; สำเนาสาธารณะ (ชื่อสตริงอายุ int) {this.name = name; this.age = อายุ; } @Override วัตถุที่ได้รับการป้องกันโคลน () พ่น clonenotsupportedException {return super.clone (); -จะเห็นได้ว่าวัตถุที่สร้างขึ้นครั้งแรกและวัตถุที่โคลนที่สองเป็นสองอินสแตนซ์ดังนั้นการดัดแปลงแอตทริบิวต์ชื่อในวินาทีจะไม่ส่งผลกระทบต่อแอตทริบิวต์ชื่อในครั้งแรก อย่างไรก็ตามเราไม่สามารถคิดได้ว่าการโคลนนิ่งเป็นสำเนาที่ลึกเช่นตัวอย่างต่อไปนี้:
ระดับความลึกระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {นักเรียนนักเรียน = นักเรียนใหม่ (95); คัดลอก first = สำเนาใหม่ ("hzw", 24, นักเรียน); คัดลอกวินาที = null; ลอง {second = (copy) first.clone (); } catch (clonenotsupportedException e) {e.printStackTrace (); } second.name = "Shanxi"; second.student.score = 60; System.out.println (First == Second); // False System.out.println (First.student == Second.student); // True System.out.println (First.student.score); // 60}} การคัดลอกคลาส อายุ int สาธารณะ; นักศึกษาสาธารณะ สำเนาสาธารณะ (ชื่อสตริงอายุ int นักเรียน) {this.name = name; this.age = อายุ; this.student = นักเรียน; } @Override วัตถุที่ได้รับการป้องกันโคลน () พ่น clonenotsupportedException {return super.clone (); }} นักเรียนชั้นเรียน {คะแนน INT สาธารณะ; นักเรียนสาธารณะ (คะแนน int) {this.score = คะแนน; -คุณเคยเห็นไหม เราสร้างครั้งที่สองผ่านการโคลนนิ่งและเป็นที่ชัดเจนว่าครั้งแรกและครั้งที่สองเป็นสองกรณีเนื่องจากผลลัพธ์ของครั้งแรก == วินาทีเป็นเท็จ แต่วัตถุนักเรียนในที่หนึ่งและสองจะเหมือนกัน หลังจากปรับเปลี่ยนค่าคะแนนของนักเรียนผ่านวินาทีคะแนนของนักเรียนในครั้งแรกก็เปลี่ยนไปเช่นกัน ซึ่งหมายความว่านักเรียนในครั้งแรกและครั้งที่สองเหมือนกัน ซึ่งหมายความว่าการโคลนนิ่งเป็นสำเนาตื้น หากเราต้องการใช้สำเนาการโคลนอย่างลึกซึ้งเราต้องปล่อยให้วัตถุนักเรียนในวัตถุคัดลอกยังใช้วิธีการโคลนในอินเทอร์เฟซ cloneable และวิธีการโคลนในการคัดลอกส่งคืนโคลนของนักเรียนเพื่อให้นักเรียนไม่ซ้ำกัน รหัสที่แก้ไขมีดังนี้:
ระดับความลึกระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {นักเรียนนักเรียน = นักเรียนใหม่ (95); คัดลอก first = สำเนาใหม่ ("hzw", 24, นักเรียน); คัดลอกวินาที = null; ลอง {second = (copy) first.clone (); } catch (clonenotsupportedException e) {e.printStackTrace (); } second.name = "Shanxi"; second.student.score = 60; System.out.println (First == Second); // false System.out.println (First.student == Second.student); // talse system.out.println (first.student.score); // 95 System.out.println (Second.student.score); อายุ int สาธารณะ; นักศึกษาสาธารณะ สำเนาสาธารณะ (ชื่อสตริงอายุ int นักเรียน) {this.name = name; this.age = อายุ; this.student = นักเรียน; } @Override วัตถุที่ได้รับการป้องกันโคลน () พ่น clonenotsupportedException {copy = (copy) super.clone (); Copy.student = (นักเรียน) student.clone (); ส่งคืนสำเนา; }} ชั้นเรียนนักเรียนใช้ cloneable {คะแนนสาธารณะสาธารณะ; นักเรียนสาธารณะ (คะแนน int) {this.score = คะแนน; } @Override วัตถุที่ได้รับการป้องกันโคลน () พ่น clonenotsupportedException {return super.clone (); }} คุณจะเห็นว่าครั้งแรกและครั้งที่สองก่อนนักเรียนและที่สองนักเรียนไม่เหมือนกันในเวลานี้ ดังนั้นหลังจากที่เราแก้ไขคะแนนของนักเรียนคนที่สองมันไม่ส่งผลกระทบต่อมูลค่าคะแนนของนักเรียนในครั้งแรกบรรลุวัตถุประสงค์ในการคัดลอกลึก;
อย่างไรก็ตามหากคุณคิดอย่างรอบคอบปัญหาจะเกิดขึ้น หากมีแอตทริบิวต์ประเภทอ้างอิงในชั้นเรียนนักเรียนในตัวอย่างของเราข้างต้นเช่นชั้นเรียนวิทยาลัยเราจะต้องให้ชั้นเรียนวิทยาลัยใช้อินเทอร์เฟซ cloneable แล้วโทรหาวิธีการโคลนคลาสวิทยาลัยในวิธีการโคลนในชั้นเรียนนักเรียน ฉันพบว่ามันหายไป กระบวนการนี้ซับซ้อนมาก ประเภทการอ้างอิงที่เกี่ยวข้องทั้งหมดในชั้นเรียนจะต้องใช้อินเตอร์เฟส cloneable ฉันรู้สึกว่ามันลำบากมากโอเคสิ่งต่อไปคือการที่ยอดเยี่ยม
วิธีที่ดีที่สุดในการแก้ปัญหาการคัดลอกลึกคือการใช้การทำให้เป็นอนุกรมเพื่อให้ทุกคลาสไม่จำเป็นต้องใช้อินเทอร์เฟซ cloneable เพียงแค่ทำให้เป็นอนุกรมและ deserialize โดยตรง มาดูกันเถอะ
นำเข้า Java.io.File; นำเข้า Java.io.FileInputStream; นำเข้า Java.io.FileOutputStream; นำเข้า Java.io.ObjectInputStream; นำเข้า Java.io.ObjectOutputStream; นำเข้า java.io.serializable; ระดับความลึกระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {วิทยาลัยวิทยาลัย = วิทยาลัยใหม่ ("Nongda"); นักเรียนนักเรียน = นักเรียนใหม่ (95, โรงเรียน); Copy = สำเนาใหม่ ("HZW", 23, นักเรียน); คัดลอกอื่น = null; // ระบุอินสแตนซ์คลาส deserialized // serialize การดำเนินการอนุกรมลอง {fileOutputStream fos = ใหม่ fileOutputStream (ไฟล์ใหม่ ("d: /copy.txt")); ObjectOutputStream OOS = ใหม่ ObjectOutputStream (FOS); oos.writeObject (คัดลอก); } catch (exception e) {e.printstacktrace (); } // serialize การดำเนินการ deserialization fileinputstream fis; ลอง {fis = new FileInputStream (ไฟล์ใหม่ ("d: /copy.txt")); ObjectInputStream OIS = ใหม่ ObjectInputStream (FIS); อื่น = (คัดลอก) ois.readObject (); } catch (exception e) {e.printstacktrace (); } system.out.println (copy == อื่น); // false system.out.println (copy.student == อีกคนหนึ่ง); // false system.out.println (copy.student.school == อีกคนหนึ่ง System.out.println (copy.student.school.schoolname); // nongda}} การคัดลอกคลาสใช้ serializable {ชื่อสตริงสาธารณะ; อายุ int สาธารณะ; นักศึกษาสาธารณะ สำเนาสาธารณะ (ชื่อสตริงอายุ int นักเรียน) {this.name = name; this.age = อายุ; this.student = นักเรียน; }} ชั้นเรียนนักเรียนใช้ Serializable {คะแนนสาธารณะสาธารณะ; โรงเรียนวิทยาลัยสาธารณะ; นักเรียนสาธารณะ (คะแนน int, โรงเรียนวิทยาลัย) {this.score = คะแนน; this.school = โรงเรียน; }} Class College ใช้ Serializable {Public String Schoolname; วิทยาลัยสาธารณะ (String SchoolName) {this.SCHOOLNAME = SchoolName; - จากผลลัพธ์เราจะเห็นว่าวัตถุที่สร้างขึ้นหลังจาก deserialization เป็นสำเนาของวัตถุต้นฉบับและไม่มีความสัมพันธ์ใด ๆ กับวัตถุดั้งเดิมยกเว้นค่าแอตทริบิวต์เดียวกัน ดังนั้นเมื่อเราแก้ไขชื่อโรงเรียนของ deserialization สร้างวัตถุเป็น "wuda" เราไม่ได้แก้ไขชื่อโรงเรียนของอินสแตนซ์ดั้งเดิมและเรายังคงส่งออก "Nongda" ดังนั้นเราจึงได้รับผลสำเนาลึกจริง อย่างไรก็ตามเพื่อให้บรรลุการทำให้เป็นอนุกรมคลาสที่เกี่ยวข้องทั้งหมดจะต้องใช้อินเทอร์เฟซแบบอนุกรมซึ่งสะดวกกว่าการใช้ทั้งอินเตอร์เฟส cloneable และวิธีการโคลน
ข้างต้นเป็นคำอธิบายโดยละเอียดเกี่ยวกับสำเนา Java Deep และตื้น หากคุณต้องการโปรดดูที่มัน