Serialisasi Java: readObject () vs. readResolve ()


127

Buku Java yang Efektif dan sumber-sumber lain memberikan penjelasan yang cukup bagus tentang bagaimana dan kapan menggunakan metode readObject () ketika bekerja dengan kelas Java yang dapat diubah serial. Metode readResolve (), di sisi lain, tetap menjadi sedikit misteri. Pada dasarnya semua dokumen yang saya temukan hanya menyebutkan satu atau keduanya atau hanya menyebutkan satu per satu.

Pertanyaan yang tetap tidak terjawab adalah:

  • Apa perbedaan antara kedua metode ini?
  • Kapan metode mana yang harus diterapkan?
  • Bagaimana seharusnya readResolve () digunakan, terutama dalam hal mengembalikan apa?

Saya harap Anda bisa menjelaskan masalah ini.


Contoh dari Oracle's JDK:String.CaseInsensitiveComparator.readResolve()
kevinarpe

Jawaban:


138

readResolvedigunakan untuk mengganti objek yang dibaca dari aliran. Satu-satunya kegunaan yang pernah saya lihat untuk ini adalah menegakkan lajang; ketika sebuah objek dibaca, gantikan dengan instance singleton. Ini memastikan bahwa tidak ada yang bisa membuat contoh lain dengan membuat serial dan deserialisasi singleton.


3
Ada sejumlah cara agar kode jahat (atau bahkan data) dapat menyiasatinya.
Tom Hawtin - tackline

6
Josh Bloch berbicara tentang kondisi di mana ini rusak di Java 2 ed efektif. Butir 77. Dia menyebutkan tentang ini dalam ceramah yang dia berikan di Google IO beberapa tahun yang lalu (beberapa kali menjelang akhir pembicaraan): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
Saya menemukan jawaban ini sedikit tidak memadai, karena tidak menyebutkan transientbidang. readResolvedigunakan untuk menyelesaikan objek setelah dibaca. Contoh penggunaan mungkin adalah objek yang menyimpan beberapa cache yang dapat dibuat kembali dari data yang ada dan tidak perlu diserialisasi; data yang di-cache dapat dideklarasikan transientdan readResolve()dapat dibangun kembali setelah deserialization. Hal-hal seperti itulah gunanya metode ini.
Jason C

2
@JasonC komentar Anda bahwa "Hal-hal seperti itu [penanganan sementara] adalah untuk apa metode ini " menyesatkan. Lihat dokumen Java untuk Serializable: dikatakan "Kelas yang perlu menunjuk pengganti ketika turunannya dibaca dari aliran harus menerapkan readResolvemetode khusus [ ] ini ...".
Opher

2
Metode readResolve juga dapat digunakan dalam kasus sudut di mana misalkan Anda telah membuat serial banyak objek dan menyimpannya dalam database. Jika nanti, Anda ingin memigrasikan data itu ke format baru, Anda dapat dengan mudah mencapainya dalam metode readResolve.
Nilesh Rajani

29

Butir 90, Java Efektif, sampul Ed ke-3 readResolvedan writeReplaceuntuk proxy serial - penggunaan utama mereka. Contoh tidak menulis readObjectdan writeObjectmetode karena mereka menggunakan serialisasi default untuk membaca dan menulis bidang.

readResolvedipanggil setelah readObjecttelah kembali (sebaliknya writeReplacedipanggil sebelumnya writeObjectdan mungkin pada objek yang berbeda). Objek yang dikembalikan metode menggantikan thisobjek yang dikembalikan ke pengguna ObjectInputStream.readObjectdan referensi kembali lebih jauh ke objek dalam aliran. Keduanya readResolvedan writeReplacedapat mengembalikan objek dengan tipe yang sama atau berbeda. Mengembalikan jenis yang sama berguna dalam beberapa kasus di mana bidang harus finaldan kompatibilitas mundur diperlukan atau nilai harus disalin dan / atau divalidasi.

Penggunaan readResolvetidak memberlakukan properti tunggal.


9

readResolve dapat digunakan untuk mengubah data yang diserialisasi melalui metode readObject. Misalnya, xstream API menggunakan fitur ini untuk menginisialisasi beberapa atribut yang tidak ada dalam XML untuk diderialisasi.

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


1
XML dan Xstream tidak relevan dengan pertanyaan tentang Java Serialisasi, dan pertanyaan itu dijawab dengan benar bertahun-tahun yang lalu. -1
Marquis of Lorne

5
Jawaban yang diterima menyatakan bahwa readResolve digunakan untuk mengganti objek. Jawaban ini memberikan informasi tambahan yang berguna yang dapat digunakan untuk memodifikasi objek selama deserialisasi. XStream diberikan sebagai contoh, bukan sebagai satu-satunya perpustakaan yang memungkinkan hal itu terjadi.
Diinginkan

5

readResolve adalah untuk saat Anda mungkin perlu mengembalikan objek yang ada, misalnya karena Anda memeriksa duplikat input yang harus digabungkan, atau (misalnya dalam sistem terdistribusi yang akhirnya konsisten) karena ini merupakan pembaruan yang mungkin tiba sebelum Anda menyadari versi yang lebih lama.


readResolve () jelas bagi saya tetapi saya masih memiliki beberapa pertanyaan yang tidak dapat dijelaskan tetapi jawaban Anda hanya membaca pikiran saya, terima kasih
Rajni Gangwar

5

readObject () adalah metode yang ada di class ObjectInputStream. sementara membaca objek pada saat deserialization, readObject method memeriksa secara internal apakah objek kelas yang sedang deserialisasi memiliki metode readResolve atau tidak jika ada metode readResolve maka akan memanggil metode readResolve dan mengembalikan metode readResolve yang sama. contoh.

Jadi niat menulis metode readResolve adalah praktik yang baik untuk mencapai pola desain singleton murni di mana tidak ada yang bisa mendapatkan contoh lain dengan membuat serial / deserializing.



2

Ketika serialisasi digunakan untuk mengonversi objek sehingga dapat disimpan dalam file, kita dapat memicu metode, readResolve (). Metode ini bersifat pribadi dan disimpan di kelas yang sama yang objeknya diambil saat deserialisasi. Ini memastikan bahwa setelah deserialization, objek apa yang dikembalikan adalah sama dengan serial. Itu adalah,instanceSer.hashCode() == instanceDeSer.hashCode()

Metode readResolve () bukan metode statis. Setelah in.readObject()dipanggil sementara deserialisation itu hanya memastikan bahwa objek yang dikembalikan adalah sama dengan yang serial seperti di bawah ini sementaraout.writeObject(instanceSer)

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

Dengan cara ini, ini juga membantu dalam implementasi pola desain tunggal , karena setiap kali contoh yang sama dikembalikan.

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

1

Saya tahu pertanyaan ini benar-benar tua dan memiliki jawaban yang diterima, tetapi karena muncul sangat tinggi di pencarian google, saya pikir saya akan mempertimbangkan karena tidak ada jawaban yang mencakup tiga kasus yang saya anggap penting - dalam pikiran saya penggunaan utama untuk ini metode. Tentu saja, semua berasumsi bahwa sebenarnya ada kebutuhan untuk format serialisasi khusus.

Ambil, misalnya kelas koleksi. Serialisasi serial dari daftar tertaut atau BST akan menghasilkan hilangnya ruang yang sangat besar dengan perolehan kinerja yang sangat sedikit dibandingkan dengan hanya membuat serial elemen-elemen secara berurutan. Ini bahkan lebih benar jika koleksi adalah proyeksi atau tampilan - menyimpan referensi ke struktur yang lebih besar daripada yang diekspos oleh API publiknya.

  1. Jika objek berseri memiliki bidang yang tidak dapat diubah yang membutuhkan serialisasi khusus, solusi asli writeObject/readObjecttidak mencukupi, karena objek deserialisasi dibuat sebelum membaca bagian aliran yang ditulis writeObject. Ambil penerapan minimal dari daftar tertaut ini:

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

Struktur ini dapat diserialisasi dengan menulis headbidang setiap tautan secara rekursif , diikuti oleh sebuah nullnilai. Namun deserialisasi format seperti itu menjadi tidak mungkin: readObjecttidak dapat mengubah nilai-nilai bidang anggota (sekarang diperbaiki ke null). Berikut datang writeReplace/ readResolvepair:

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

Saya minta maaf jika contoh di atas tidak mengkompilasi (atau bekerja), tetapi mudah-mudahan cukup untuk menggambarkan poin saya. Jika menurut Anda ini adalah contoh yang sangat jauh, harap diingat bahwa banyak bahasa fungsional dijalankan pada JVM dan pendekatan ini menjadi sangat penting dalam kasus mereka.

  1. Kita mungkin ingin melakukan deserialisasi objek dari kelas yang berbeda dari yang kita tulis ke ObjectOutputStream. Ini akan menjadi kasus dengan pandangan seperti java.util.Listimplementasi daftar yang memperlihatkan sepotong lebih lama ArrayList. Jelas, membuat serial seluruh daftar dukungan adalah ide yang buruk dan kita hanya harus menulis elemen dari slice yang dilihat. Tetapi mengapa berhenti pada itu dan memiliki tingkat tipuan yang tidak berguna setelah deserialisasi? Kami hanya bisa membaca elemen dari aliran ke dalam ArrayListdan mengembalikannya secara langsung alih-alih membungkusnya di kelas tampilan kami.

  2. Atau, memiliki kelas delegasi serupa yang didedikasikan untuk serialisasi dapat menjadi pilihan desain. Contoh yang baik akan menggunakan kembali kode serialisasi kami. Sebagai contoh, jika kita memiliki kelas builder (mirip dengan StringBuilder untuk String), kita dapat menulis delegasi serialisasi yang membuat serial setiap koleksi dengan menulis builder kosong ke stream, diikuti dengan ukuran koleksi dan elemen yang dikembalikan oleh iterator colection. Deserialisasi akan melibatkan pembacaan pembangun, menambahkan semua elemen yang selanjutnya dibaca, dan mengembalikan hasil final build()dari delegasireadResolve . Dalam hal ini kita perlu mengimplementasikan serialisasi hanya di kelas akar hirarki koleksi, dan tidak ada kode tambahan yang diperlukan dari implementasi saat ini atau di masa depan, asalkan mereka menerapkan abstrak iterator()danbuilder()metode (yang terakhir untuk membuat ulang koleksi dari jenis yang sama - yang akan menjadi fitur yang sangat berguna dalam dirinya sendiri). Contoh lain adalah memiliki hierarki kelas yang kode kita tidak sepenuhnya kendalikan - kelas dasar kita dari pustaka pihak ketiga dapat memiliki sejumlah bidang pribadi yang tidak kita ketahui dan yang dapat berubah dari satu versi ke versi lain, melanggar objek serial kami. Dalam hal ini akan lebih aman untuk menulis data dan membangun kembali objek secara manual pada deserialisasi.


0

Metode readResolve

Untuk kelas Serializable dan Externalizable, metode readResolve memungkinkan kelas untuk mengganti / menyelesaikan objek yang dibaca dari aliran sebelum dikembalikan ke pemanggil. Dengan mengimplementasikan metode readResolve, sebuah kelas dapat secara langsung mengontrol tipe dan instance dari instance miliknya sendiri yang dideeralisasi. Metode ini didefinisikan sebagai berikut:

ANY-ACCESS-MODIFIER Object readResolve () melempar ObjectStreamException;

The readResolve metode ini disebut ketika ObjectInputStream telah membaca sebuah objek dari sungai dan sedang mempersiapkan untuk kembali ke pemanggil. ObjectInputStream memeriksa apakah kelas objek mendefinisikan metode readResolve. Jika metode ini didefinisikan, metode readResolve dipanggil untuk memungkinkan objek dalam aliran untuk menunjuk objek yang akan dikembalikan. Objek yang dikembalikan harus dari tipe yang kompatibel dengan semua penggunaan. Jika tidak kompatibel, ClassCastException akan dilemparkan ketika tipe ketidakcocokan ditemukan.

Misalnya, kelas Simbol dapat dibuat yang hanya ada satu contoh dari setiap simbol yang mengikat ada dalam mesin virtual. The readResolve Metode akan dilaksanakan untuk menentukan apakah simbol yang sudah ditetapkan dan menggantikan yang sudah ada sebelumnya setara objek Simbol untuk mempertahankan kendala identitas. Dengan cara ini keunikan objek Simbol dapat dipertahankan di seluruh serialisasi.


0

Seperti yang sudah dijawab, readResolveadalah metode pribadi yang digunakan dalam ObjectInputStream saat deserializing objek. Ini disebut tepat sebelum instance aktual dikembalikan. Dalam kasus Singleton, di sini kita dapat memaksa mengembalikan referensi instance singleton yang sudah ada alih-alih referensi instance deserialized. Sama seperti yang kita milikiwriteReplace untuk ObjectOutputStream.

Contoh untuk 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);
    }

}

}

Keluaran:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.