Serialisasi adalah proses yang menerjemahkan suatu objek ke dalam urutan byte, yang kemudian dapat dipulihkan sepenuhnya. Mengapa ini dibutuhkan?
Faktanya adalah, selama eksekusi program normal, umur maksimum objek apa pun diketahui - dari peluncuran program hingga akhirnya. Serialisasi memungkinkan Anda untuk memperluas kerangka kerja ini dan "memberi kehidupan" pada objek dengan cara yang sama antara peluncuran program.
Bonus tambahan untuk semuanya adalah pelestarian lintas platform. Apa pun sistem operasi yang Anda miliki, serialisasi menerjemahkan objek menjadi aliran byte, yang dapat dipulihkan pada OS apa pun. Jika Anda perlu mentransfer objek melalui jaringan, Anda bisa membuat serial objek, menyimpannya ke file dan mentransfernya melalui jaringan ke penerima. Dia akan dapat memulihkan objek yang diterima. Serialisasi juga memungkinkan Anda untuk secara jarak jauh memanggil metode (Java RMI) yang ada di mesin yang berbeda dengan, mungkin, sistem operasi yang berbeda, dan bekerja dengan mereka seolah-olah mereka berada di mesin proses panggilan java.
Menerapkan mekanisme serialisasi cukup sederhana. Kelas Anda perlu mengimplementasikan antarmuka
Serializable . Antarmuka ini adalah pengidentifikasi yang tidak memiliki metode, tetapi memberitahu jvm bahwa objek dari kelas ini dapat diserialisasi. Karena mekanisme serialisasi terhubung ke sistem input / output dasar dan menerjemahkan objek ke dalam aliran byte, untuk menjalankannya Anda perlu membuat aliran output
OutputStream , kemas dalam
ObjectOutputStream dan panggil metode
writeObject (). Untuk mengembalikan objek, Anda perlu mengemas
InputStream ke dalam
ObjectInputStream dan memanggil metode
readObject ().Selama proses serialisasi, bersama dengan objek serializable, grafik objeknya disimpan. Yaitu semua objek yang terkait dengan ini, objek dari kelas lain juga akan bersambung dengan itu.
Pertimbangkan contoh membuat cerita bersambung dari objek Person kelas.
import java.io.*; class Home implements Serializable { private String home; public Home(String home) { this.home = home; } public String getHome() { return home; } } public class Person implements Serializable { private String name; private int countOfNiva; private String fatherName; private Home home; public Person(String name, int countOfNiva, String fatherName, Home home) { this.name = name; this.countOfNiva = countOfNiva; this.fatherName = fatherName; this.home = home; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", countOfNiva=" + countOfNiva + ", fatherName='" + fatherName + '\'' + ", home=" + home + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Home home = new Home("Vishnevaia 1"); Person igor = new Person("Igor", 2, "Raphael", home); Person renat = new Person("Renat", 2, "Raphael", home);
Kesimpulan: Before Serialize: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@355da254} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@355da254} After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae}
Dalam contoh ini, kelas
Home dibuat untuk menunjukkan bahwa ketika membuat serial objek objek, grafik objeknya di-serialisasi dengannya. Kelas
Home juga harus mengimplementasikan antarmuka
Serializable , jika tidak
java.io.NotSerializableException akan terjadi. Contoh ini juga menjelaskan serialisasi menggunakan kelas
ByteArrayOutputStream .
Kesimpulan yang menarik dapat diambil dari hasil eksekusi program:
ketika mengembalikan objek yang memiliki referensi ke objek yang sama sebelum serialisasi, objek ini akan dikembalikan hanya sekali . Ini dapat dilihat pada tautan yang sama pada objek setelah pemulihan:
After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae}
Namun, juga terlihat bahwa ketika merekam dengan dua aliran keluaran (kami memiliki
ObjectInputStream dan
ByteArrayOutputStream ), objek
rumah akan dibuat ulang, meskipun faktanya itu sudah dibuat sebelumnya di salah satu aliran. Kami melihat ini di berbagai alamat objek
rumah yang diterima dalam dua aliran. Ternyata jika Anda membuat serial dengan satu aliran output, lalu mengembalikan objek, maka kami memiliki jaminan memulihkan jaringan objek secara lengkap tanpa duplikat yang tidak perlu. Tentu saja, selama eksekusi program keadaan objek dapat berubah, tetapi ini adalah hati nurani programmer.
MasalahContoh ini juga menunjukkan bahwa ketika memulihkan objek,
ClassNotFoundException dapat terjadi. Apa alasannya? Faktanya adalah bahwa kita dapat dengan mudah membuat serial objek dari kelas
Person ke file, mentransfernya melalui jaringan ke teman kita, yang dapat mengembalikan objek dengan aplikasi lain di mana kelas
Person tidak ada.
Serialisasi. Bagaimana membuat?Bagaimana jika Anda ingin mengelola serialisasi sendiri? Misalnya, objek Anda menyimpan nama pengguna dan kata sandi pengguna. Anda perlu membuat cerita bersambung untuk transmisi lebih lanjut melalui jaringan. Melewati kata sandi dalam hal ini sangat tidak bisa diandalkan. Bagaimana cara mengatasi masalah ini? Ada dua cara. Pertama, gunakan kata kunci
sementara . Kedua, alih-alih mewujudkan minat
Serializable , gunakan ekstensi - antarmuka yang dapat
dieksternalisasi . Pertimbangkan contoh metode pertama dan kedua untuk membandingkannya.
Cara pertama - Serialisasi menggunakan transient import java.io.*; public class Logon implements Serializable { private String login; private transient String password; public Logon(String login, String password) { this.login = login; this.password = password; } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } }
Kesimpulan: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'}
Cara kedua - Serialisasi dengan implementasi antarmuka Externalizable import java.io.*; public class Logon implements Externalizable { private String login; private String password; public Logon() { } public Logon(String login, String password) { this.login = login; this.password = password; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(login); } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { login = (String) in.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } }
Kesimpulan: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'}
Perbedaan pertama antara dua opsi yang menarik perhatian Anda adalah ukuran kode. Saat mengimplementasikan antarmuka
Externalizable , kita perlu mengganti dua metode:
writeExternal () dan
readExternal () . Dalam metode
writeExternal () , kami menunjukkan bidang mana yang akan diserialisasi dan bagaimana, di
readExternal () cara membacanya. Saat menggunakan kata
transient, kami secara eksplisit menunjukkan bidang atau bidang mana yang tidak perlu diserialisasi. Kami juga mencatat bahwa dalam metode kedua, kami secara eksplisit membuat konstruktor default, apalagi yang publik. Mengapa ini dilakukan? Mari kita coba jalankan kode tanpa konstruktor ini. Dan lihat hasilnya:
Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} Exception in thread "main" java.io.InvalidClassException: Logon; no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169) at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at Logon.main(Logon.java:45)
Kami mendapat pengecualian
java.io.InvalidClassException . Apa alasannya? Jika Anda mengikuti jejak stack, Anda bisa menemukan bahwa ada garis-garis di konstruktor dari kelas
ObjectStreamClass :
if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl);
Untuk antarmuka
Externalizable , metode konstruktor
getExternalizableConstructor () akan dipanggil
, di dalamnya melalui
Reflection kita akan mencoba untuk mendapatkan konstruktor default dari kelas yang kita pulihkan objeknya. Jika kami tidak dapat menemukannya, atau tidak tersedia untuk
umum , maka kami mendapatkan pengecualian. Anda dapat mengatasi situasi ini sebagai berikut: jangan membuat konstruktor secara eksplisit di kelas dan mengisi bidang menggunakan setter dan dapatkan nilai dengan getter. Kemudian, ketika mengkompilasi kelas, konstruktor default akan dibuat, yang akan tersedia untuk
getExternalizableConstructor () . Untuk
Serializable, metode
getSerializableConstructor () mendapatkan konstruktor dari kelas
Object dan mencari kelas yang diinginkan darinya, jika tidak menemukannya, kita akan mendapatkan
ClassNotFoundException . Ternyata perbedaan utama antara
Serializable dan
Externalizable adalah bahwa yang pertama tidak memerlukan konstruktor untuk membuat pemulihan objek. Ini hanya akan pulih sepenuhnya dari byte. Untuk yang kedua, selama restorasi, sebuah objek pertama akan dibuat menggunakan konstruktor pada titik deklarasi, dan kemudian nilai bidangnya dari byte yang diterima selama serialisasi akan ditulis ke dalamnya. Secara pribadi, saya lebih suka metode pertama, itu jauh lebih sederhana. Selain itu, bahkan jika kita masih perlu mengatur perilaku serialisasi, kita tidak dapat menggunakan
Externalizable , serta mengimplementasikan
Serializable dengan menambahkan (tanpa mengesampingkan) metode
writeObject () dan
readObject () ke dalamnya. Tetapi agar mereka βbekerjaβ, tanda tangan mereka harus diperhatikan dengan ketat.
import java.io.*; public class Talda implements Serializable { private String name; private String description; public Talda(String name, String description) { this.name = name; this.description = description; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); System.out.println("Our writeObject"); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); System.out.println("Our readObject"); } @Override public String toString() { return "Talda{" + "name='" + name + '\'' + ", description='" + description + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Talda partizanka = new Talda("Partizanka", "Viiiski"); System.out.println("Before: \n" + partizanka); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Talda.out")); out.writeObject(partizanka); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Talda.out")); partizanka = (Talda) in.readObject(); System.out.println("After: \n" + partizanka); } }
Kesimpulan: Before: Talda{name='Partizanka', description='Viiiski'} Our writeObject Our readObject After: Talda{name='Partizanka', description='Viiiski'}
Di dalam metode kami yang ditambahkan,
defaultWriteObject () dan
defaultReadObject () dipanggil. Mereka bertanggung jawab atas serialisasi default, seolah-olah itu berfungsi tanpa metode yang kami tambahkan.
Sebenarnya, ini hanya puncak gunung es, jika Anda terus mempelajari mekanisme serialisasi, maka dengan tingkat probabilitas yang tinggi, Anda dapat menemukan lebih banyak nuansa, menemukan yang kami katakan: "Serialisasi ... tidak begitu sederhana."