Serializable
Saya mengatasi pekerjaan saya, dan implementasi otomatis dari seluruh proses merupakan kabar baik. Contoh yang kami lihat juga tidak rumit. Jadi apa masalahnya? Mengapa antarmuka lain untuk tugas yang pada dasarnya sama? Faktanya adalah Serializable
ia memiliki sejumlah kelemahan. Mari kita daftar beberapa di antaranya:
-
Pertunjukan. Antarmukanya memiliki
Serializable
banyak keunggulan, tetapi performa tinggi jelas bukan salah satunya.
Pertama , mekanisme internal Serializable
menghasilkan sejumlah besar informasi layanan dan berbagai jenis data sementara selama operasi.
Kedua (Anda tidak perlu membahasnya sekarang dan membaca di waktu senggang jika Anda tertarik), pekerjaan ini Serializable
didasarkan pada penggunaan Reflection API. Alat ini memungkinkan Anda melakukan hal-hal yang tampaknya mustahil di Java: misalnya, mengubah nilai bidang privat. JavaRush memiliki artikel bagus tentang Reflection API , Anda dapat membacanya di sini.
-
Fleksibilitas. Kami tidak mengontrol proses serialisasi-deserialisasi sama sekali saat menggunakan file
Serializable
.Di satu sisi, ini sangat nyaman, karena jika kita tidak terlalu peduli dengan kinerja, kemampuan untuk tidak menulis kode sepertinya nyaman. Namun bagaimana jika kita benar-benar perlu menambahkan beberapa fitur kita sendiri (contoh salah satunya ada di bawah) ke logika serialisasi?
Intinya, yang harus kita kendalikan prosesnya hanyalah kata kunci
transient
untuk mengecualikan beberapa data, dan hanya itu. Semacam "perangkat" :/ -
Keamanan. Poin ini sebagian mengikuti poin sebelumnya.
Kami belum banyak memikirkan hal ini sebelumnya, tetapi bagaimana jika beberapa informasi di kelas Anda tidak ditujukan untuk “telinga orang lain” (lebih tepatnya, mata)? Contoh sederhananya adalah kata sandi atau data pribadi pengguna lainnya, yang di dunia modern diatur oleh banyak undang-undang.
Dengan menggunakan
Serializable
, kami sebenarnya tidak bisa berbuat apa-apa. Kami membuat serial semuanya apa adanya.Namun, dalam cara yang baik, kita harus mengenkripsi data semacam ini sebelum menulisnya ke file atau mengirimkannya melalui jaringan. Tapi
Serializable
itu tidak memberikan kesempatan ini.
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
//...конструктор, геттеры, сеттеры, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
Seperti yang Anda lihat, kami telah membuat perubahan signifikan! Yang utama jelas: saat mengimplementasikan antarmuka, Externalizable
Anda harus mengimplementasikan dua metode wajib - writeExternal()
dan readExternal()
. Seperti yang kami katakan sebelumnya, semua tanggung jawab untuk serialisasi dan deserialisasi berada di tangan programmer. Namun, sekarang Anda dapat mengatasi masalah kurangnya kendali atas proses ini! Seluruh proses diprogram langsung oleh Anda, yang tentu saja menciptakan mekanisme yang jauh lebih fleksibel. Selain itu, masalah keamanan juga teratasi. Seperti yang Anda lihat, kami memiliki bidang di kelas kami: data pribadi yang tidak dapat disimpan tanpa terenkripsi. Sekarang kita dapat dengan mudah menulis kode yang memenuhi batasan ini. Misalnya, tambahkan dua metode privat sederhana ke kelas kita untuk mengenkripsi dan mendekripsi data rahasia. Kami akan menulisnya ke file dan membacanya dari file dalam bentuk terenkripsi. Dan kita akan menulis dan membaca sisa datanya apa adanya :) Hasilnya, kelas kita akan terlihat seperti ini:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
Kami telah menerapkan dua metode yang menggunakan parameter yang sama ObjectOutput out
dan ObjectInput
telah kami temui dalam kuliah tentang Serializable
. Pada saat yang tepat, kami mengenkripsi atau mendekripsi data yang diperlukan, dan dalam bentuk ini kami menggunakannya untuk membuat serialisasi objek kami. Mari kita lihat bagaimana hal ini akan terlihat dalam praktiknya:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
Dalam metode encryptString()
dan decryptString()
, kami secara khusus menambahkan keluaran ke konsol untuk memeriksa dalam bentuk apa data rahasia akan ditulis dan dibaca. Kode di atas menampilkan baris berikut ke konsol: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Enkripsi berhasil! Isi lengkap file terlihat seperti ini: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Sekarang mari kita coba menggunakan logika deserialisasi yang kami tulis.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Sepertinya tidak ada yang rumit di sini, ini akan berhasil! Ayo jalankan... Pengecualian di thread "utama" java.io.InvalidClassException: UserInfo; tidak ada konstruktor yang valid Ups :( Ternyata tidak sesederhana itu! Mekanisme deserialisasi menimbulkan pengecualian dan mengharuskan kami membuat konstruktor default. Saya bertanya-tanya mengapa? Serializable
Kami berhasil tanpanya... :/ Di sini kita sampai pada nuansa penting lainnya . Perbedaan antara Serializable
dan Externalizable
tidak hanya terletak pada akses "diperluas" untuk pemrogram dan kemampuan untuk mengelola proses dengan lebih fleksibel, tetapi juga pada proses itu sendiri. Pertama-tama, pada mekanisme deserialisasi ... Saat digunakan, Serializable
memori hanyalah dialokasikan untuk suatu objek, setelah itu nilai dibaca dari aliran, yang mengisi semua bidangnya. Jika kita menggunakan Serializable
, konstruktor objek tidak dipanggil! Semua pekerjaan dilakukan melalui refleksi (API Refleksi, yang kami sebutkan secara singkat di bagian terakhir kuliah). Dalam kasus , Externalizable
mekanisme deserialisasi akan berbeda. Pada awalnya, konstruktor default dipanggil. Dan baru kemudian UserInfo
dipanggil pada metode objek yang dibuat readExternal()
, yang bertanggung jawab untuk mengisi bidang objek. Yaitu mengapa setiap kelas yang mengimplementasikan antarmuka Externalizable
harus memiliki konstruktor default . Mari tambahkan ke kelas kita UserInfo
dan jalankan kembali kodenya:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Keluaran konsol: data paspor Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='data paspor Ivan Ivanov'} Masalah yang sama sekali berbeda! Pertama, string yang didekripsi dengan data rahasia dikeluarkan ke konsol, dan kemudian objek kita dipulihkan dari file dalam format string! Beginilah cara kami berhasil menyelesaikan semua masalah :) Topik serialisasi dan deserialisasi tampaknya sederhana, tetapi seperti yang Anda lihat, kuliah kami ternyata panjang. Dan bukan itu saja! Ada lebih banyak kehalusan saat menggunakan masing-masing antarmuka ini, tetapi agar otak Anda tidak meledak karena banyaknya informasi baru, saya akan membuat daftar singkat beberapa poin penting dan memberikan tautan ke bacaan tambahan. Jadi, apa lagi yang perlu Anda ketahui? Pertama , saat membuat serial (tidak masalah apakah Anda menggunakan Serializable
atau Externalizable
), perhatikan variabelnya static
. Saat digunakan, Serializable
bidang-bidang ini tidak diserialisasikan sama sekali (dan, karenanya, nilainya tidak berubah, karena static
bidang-bidang tersebut milik kelas, bukan objek). Namun saat menggunakannya, Externalizable
Anda mengontrol sendiri prosesnya, jadi secara teknis hal ini bisa dilakukan. Namun hal ini tidak disarankan karena dapat menimbulkan kesalahan halus. Kedua , perhatian juga harus diberikan pada variabel dengan pengubahnya final
. Saat digunakan, Serializable
mereka diserialisasi dan dideserialisasi seperti biasa, tetapi saat digunakan, tidak mungkin Externalizable
untuk melakukan deserialisasi final
suatu variabel ! Alasannya sederhana: semua final
-field diinisialisasi ketika konstruktor default dipanggil, dan setelah itu nilainya tidak dapat diubah. Oleh karena itu, untuk membuat serialisasi objek yang berisi final
-field, gunakan serialisasi standar melalui Serializable
. Ketiga , saat menggunakan warisan, semua kelas turunan yang diturunkan dari suatu Externalizable
kelas juga harus memiliki konstruktor default. Berikut beberapa tautan ke artikel bagus tentang mekanisme serialisasi:
Sampai jumpa! :)
GO TO FULL VERSION