การทำให้เป็นอนุกรมของ Java: readObject () เทียบกับ readResolve ()


127

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

คำถามที่ยังไม่มีคำตอบคือ:

  • อะไรคือความแตกต่างระหว่างสองวิธี?
  • วิธีใดควรดำเนินการเมื่อใด
  • readResolve () ควรใช้อย่างไรโดยเฉพาะในแง่ของการคืนค่าอะไร

ฉันหวังว่าคุณจะกระจ่างในเรื่องนี้


ตัวอย่างจาก JDK ของ Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe

คำตอบ:


138

readResolveใช้สำหรับแทนที่วัตถุที่อ่านจากสตรีม การใช้งานเพียงอย่างเดียวที่ฉันเคยเห็นคือการบังคับใช้ singletons; เมื่ออ่านวัตถุให้แทนที่ด้วยอินสแตนซ์ซิงเกิลตัน สิ่งนี้ช่วยให้มั่นใจได้ว่าไม่มีใครสามารถสร้างอินสแตนซ์อื่นได้โดยการทำให้เป็นอนุกรมและแยกส่วนของซิงเกิลตัน


3
มีหลายวิธีสำหรับรหัสที่เป็นอันตราย (หรือแม้แต่ข้อมูล) ในการแก้ไขปัญหานั้น
Tom Hawtin - แท็

6
Josh Bloch พูดถึงเงื่อนไขที่จะหยุดทำงานใน Java 2nd ed ที่มีประสิทธิภาพ รายการที่ 77 เขาพูดถึงเรื่องนี้ในคำพูดนี้ที่เขาให้ไว้ใน Google IO เมื่อสองสามปีก่อน (บางครั้งในช่วงท้ายของการพูดคุย): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
ฉันพบว่าคำตอบนี้ไม่เพียงพอเล็กน้อยเนื่องจากไม่ได้กล่าวถึงtransientฟิลด์ readResolveใช้สำหรับแก้ไขวัตถุหลังจากอ่านแล้ว ตัวอย่างการใช้งานบางทีอ็อบเจ็กต์มีแคชบางตัวที่สามารถสร้างขึ้นใหม่จากข้อมูลที่มีอยู่และไม่จำเป็นต้องทำให้เป็นอนุกรม ข้อมูลแคชสามารถประกาศได้transientและreadResolve()สามารถสร้างใหม่ได้หลังจาก deserialization สิ่งต่างๆเช่นนั้นคือสิ่งที่วิธีนี้มีไว้สำหรับ
Jason C

2
@JasonC ความคิดเห็นของคุณที่ว่า "สิ่งต่างๆเช่นนั้น [การจัดการชั่วคราว] คือสิ่งที่วิธีนี้มีไว้สำหรับ " ทำให้เข้าใจผิด ดูเอกสาร Java สำหรับSerializable: "คลาสที่ต้องกำหนดการแทนที่เมื่ออ่านอินสแตนซ์จากสตรีมควรใช้ [ readResolve] เมธอดพิเศษนี้ ... "
Opher

2
วิธีการ readResolve ยังสามารถใช้ในกรณีมุมที่สมมติว่าคุณได้ซีเรียลออบเจ็กต์จำนวนมากและเก็บไว้ในฐานข้อมูล หากในเวลาต่อมาคุณต้องการย้ายข้อมูลนั้นไปยังรูปแบบใหม่คุณสามารถทำได้อย่างง่ายดายในเมธอด readResolve
Nilesh Rajani

29

รายการ 90, Java ที่ใช้งานจริง, ฉบับที่ 3 ครอบคลุมreadResolveและwriteReplaceสำหรับพร็อกซีแบบอนุกรม - การใช้งานหลัก ตัวอย่างไม่ได้เขียนreadObjectและwriteObjectวิธีการเนื่องจากใช้การทำให้เป็นอนุกรมเริ่มต้นเพื่ออ่านและเขียนฟิลด์

readResolveเรียกว่า after readObjecthas return (ในทางกลับกันwriteReplaceเรียกว่าก่อนหน้านี้writeObjectและอาจเป็นวัตถุอื่น) อ็อบเจ็กต์ที่เมธอดส่งคืนแทนที่thisอ็อบเจ็กต์ที่ส่งกลับไปยังผู้ใช้ObjectInputStream.readObjectและการอ้างอิงย้อนกลับเพิ่มเติมไปยังอ็อบเจ็กต์ในสตรีม ทั้งสองreadResolveและwriteReplaceอาจส่งคืนวัตถุประเภทเดียวกันหรือต่างกัน การส่งคืนประเภทเดียวกันมีประโยชน์ในบางกรณีที่ต้องเป็นฟิลด์finalและจำเป็นต้องมีความเข้ากันได้แบบย้อนหลังหรือต้องคัดลอกและ / หรือตรวจสอบความถูกต้อง

การใช้readResolveไม่ได้บังคับใช้คุณสมบัติซิงเกิลตัน


9

readResolve สามารถใช้เพื่อเปลี่ยนข้อมูลที่ต่อเนื่องกันโดยใช้วิธี readObject ตัวอย่างเช่น xstream API ใช้คุณลักษณะนี้เพื่อเริ่มต้นแอตทริบิวต์บางอย่างที่ไม่ได้อยู่ใน XML เพื่อทำการ deserialized

http://x-stream.github.io/faq.html#Serialization


1
XML และ Xstream ไม่เกี่ยวข้องกับคำถามเกี่ยวกับ Java Serialization และคำถามนี้ได้รับคำตอบอย่างถูกต้องเมื่อหลายปีก่อน -1
Marquis of Lorne

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

5

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


readResolve () ชัดเจนสำหรับฉัน แต่ฉันก็ยังมีคำถามที่อธิบายไม่ได้อยู่ในใจ แต่คำตอบของคุณแค่อ่านใจฉันขอบคุณ
Rajni Gangwar

5

readObject () เป็นเมธอดที่มีอยู่ในคลาสObjectInputStreamในขณะที่การอ่านอ็อบเจ็กต์ในช่วงเวลาของการ deserialization เมธอด readObject ตรวจสอบภายในว่าคลาสอ็อบเจ็กต์ที่ถูก deserialized มีเมธอด readResolve หรือไม่หากมีเมธอด readResolve มันจะเรียกใช้เมธอด readResolve และส่งคืนค่าเดียวกัน ตัวอย่าง.

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



2

เมื่อใช้การทำให้เป็นอนุกรมเพื่อแปลงวัตถุเพื่อให้สามารถบันทึกเป็นไฟล์ได้เราสามารถทริกเกอร์เมธอด readResolve () เมธอดนี้เป็นแบบส่วนตัวและถูกเก็บไว้ในคลาสเดียวกันซึ่งอ็อบเจ็กต์ถูกดึงออกมาในขณะที่ deserialization ตรวจสอบให้แน่ใจว่าหลังจากการดีซีเรียลไลเซชันสิ่งที่ส่งคืนจะเหมือนกับการทำให้เป็นอนุกรม นั่นคือ,instanceSer.hashCode() == instanceDeSer.hashCode()

readResolve () วิธีการไม่ใช่วิธีคงที่ After in.readObject()ถูกเรียกในขณะที่ deserialisation เพียงแค่ทำให้แน่ใจว่าวัตถุที่ส่งคืนนั้นเหมือนกับวัตถุที่ถูกทำให้เป็นอนุกรมดังต่อไปนี้ในขณะที่out.writeObject(instanceSer)

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

ด้วยวิธีนี้มันยังช่วยในการใช้งานรูปแบบการออกแบบซิงเกิลตันเพราะทุกครั้งที่ส่งคืนอินสแตนซ์เดียวกัน

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}

1

ฉันรู้ว่าคำถามนี้เก่าจริงๆและมีคำตอบที่เป็นที่ยอมรับ แต่เมื่อมันปรากฏขึ้นสูงมากในการค้นหาของ Google ฉันคิดว่าฉันจะชั่งใจเพราะไม่มีคำตอบที่ครอบคลุมสามกรณีที่ฉันคิดว่าสำคัญ - ในใจของฉันการใช้งานหลักสำหรับสิ่งเหล่านี้ วิธีการ แน่นอนทุกคนคิดว่าจำเป็นต้องมีรูปแบบการจัดลำดับแบบกำหนดเอง

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

  1. หากวัตถุเนื่องมีเขตข้อมูลไม่เปลี่ยนรูปซึ่งเป็นอันดับความต้องการที่กำหนดเอง, การแก้ปัญหาเดิมwriteObject/readObjectไม่เพียงพอเป็นวัตถุ deserialized ถูกสร้างขึ้นก่อนที่จะwriteObjectอ่านส่วนหนึ่งของกระแสที่เขียนใน นำรายการที่เชื่อมโยงไปใช้เพียงเล็กน้อยนี้:

    public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }

โครงสร้างนี้สามารถทำให้เป็นอนุกรมได้โดยการเขียนheadฟิลด์ของทุกลิงก์ซ้ำๆ ตามด้วยnullค่า อย่างไรก็ตามreadObjectการกำหนดค่าเริ่มต้นของรูปแบบดังกล่าวจะกลายเป็นไปไม่ได้: ไม่สามารถเปลี่ยนค่าของช่องสมาชิกได้ (แก้ไขเป็นnull) มาที่นี่writeReplace/ readResolveคู่:

private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}

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

  1. เราอาจต้องการ deserialize ออบเจ็กต์ของคลาสที่แตกต่างจากที่เราเขียนลงในไฟล์ObjectOutputStream. กรณีนี้จะเกิดขึ้นกับมุมมองเช่นการjava.util.Listใช้งานรายการซึ่งแสดงส่วนที่ยาวArrayListขึ้น เห็นได้ชัดว่าการจัดลำดับรายการสำรองทั้งหมดเป็นความคิดที่ไม่ดีและเราควรเขียนองค์ประกอบจากชิ้นส่วนที่ดูเท่านั้น ทำไมถึงหยุดอยู่แค่นั้นและมีระดับความไม่เป็นประโยชน์หลังจาก deserialization? เราสามารถอ่านองค์ประกอบจากสตรีมเป็นArrayListและส่งคืนได้โดยตรงแทนที่จะรวมไว้ในคลาสมุมมองของเรา

  2. หรืออีกวิธีหนึ่งการมีคลาส delegate ที่คล้ายกันสำหรับการทำให้เป็นอนุกรมอาจเป็นทางเลือกในการออกแบบ ตัวอย่างที่ดีคือการนำรหัสอนุกรมของเรากลับมาใช้ใหม่ ตัวอย่างเช่นถ้าเรามีคลาส builder (คล้ายกับ StringBuilder สำหรับ String) เราสามารถเขียนผู้แทนการทำให้เป็นอนุกรมซึ่งทำให้คอลเลกชันเป็นลำดับโดยการเขียนตัวสร้างว่างลงในสตรีมตามด้วยขนาดคอลเลกชันและองค์ประกอบที่ส่งคืนโดยตัววนซ้ำของการรวบรวม ซีเรียลไลซ์จะเกี่ยวข้องกับการอ่านสร้าง, ผนวกองค์ประกอบอ่านต่อมาทั้งหมดและกลับมาผลสุดท้ายจากผู้ได้รับมอบหมายbuild() readResolveในกรณีนี้เราจำเป็นต้องใช้การทำให้เป็นอนุกรมในคลาสรูทของลำดับชั้นการรวบรวมเท่านั้นและไม่จำเป็นต้องใช้รหัสเพิ่มเติมจากการใช้งานในปัจจุบันหรือในอนาคตหากพวกเขาใช้นามธรรมiterator()และbuilder()วิธีการ (หลังสำหรับการสร้างคอลเลกชันประเภทเดียวกัน - ซึ่งจะเป็นคุณสมบัติที่มีประโยชน์มากในตัวเอง) อีกตัวอย่างหนึ่งคือการมีลำดับชั้นของคลาสซึ่งโค้ดที่เราควบคุมไม่ได้ทั้งหมด - คลาสพื้นฐานของเราจากไลบรารีบุคคลที่สามอาจมีฟิลด์ส่วนตัวจำนวนเท่าใดก็ได้ที่เราไม่รู้อะไรเลยและอาจเปลี่ยนจากเวอร์ชันหนึ่งไปเป็นอีกเวอร์ชันหนึ่ง วัตถุต่อเนื่องของเรา ในกรณีนี้การเขียนข้อมูลและสร้างออบเจ็กต์ใหม่ด้วยตนเองจะปลอดภัยกว่าในการ deserialization


0

วิธี readResolve

สำหรับคลาส Serializable และ Externalizable เมธอด readResolve อนุญาตให้คลาสแทนที่ / แก้ไขอ็อบเจ็กต์ที่อ่านจากสตรีมก่อนที่จะส่งคืนให้กับผู้เรียก ด้วยการใช้เมธอด readResolve คลาสสามารถควบคุมประเภทและอินสแตนซ์ของอินสแตนซ์ของตัวเองที่ถูก deserialized ได้โดยตรง กำหนดวิธีการดังนี้:

Any-ACCESS-MODIFIER Object readResolve () พ่น ObjectStreamException;

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

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


0

ตามที่ตอบไปแล้วreadResolveเป็นเมธอดส่วนตัวที่ใช้ใน ObjectInputStream ในขณะที่ deserializing วัตถุ สิ่งนี้ถูกเรียกก่อนที่จะส่งคืนอินสแตนซ์จริง ในกรณีของ Singleton ที่นี่เราสามารถบังคับให้ส่งคืนการอ้างอิงอินสแตนซ์แบบซิงเกิลตันที่มีอยู่แล้วแทนการอ้างอิงอินสแตนซ์ที่ไม่กำหนดค่าเริ่มต้น สิ่งที่writeReplaceคล้ายกันที่เรามีสำหรับ ObjectOutputStream

ตัวอย่างสำหรับreadResolve:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

เอาท์พุท:

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