JavaRush /Java Blog /Random-ID /Antarmuka yang Dapat Dieksternalisasi di Java

Antarmuka yang Dapat Dieksternalisasi di Java

Dipublikasikan di grup Random-ID
Halo! Hari ini kita akan melanjutkan pengenalan kita tentang serialisasi dan deserialisasi objek Java. Pada kuliah terakhir, kita diperkenalkan dengan antarmuka penanda Serializable , melihat contoh penggunaannya, dan juga mempelajari cara mengontrol proses serialisasi menggunakan kata kunci transient . Ya, “kelola prosesnya”, tentu saja, adalah kata yang kuat. Kami memiliki satu kata kunci, satu ID versi, dan pada dasarnya hanya itu. Proses selanjutnya “terprogram” di dalam Java, dan tidak ada akses ke sana. Dari segi kenyamanan, tentu saja bagus. Namun seorang programmer dalam pekerjaannya seharusnya tidak hanya fokus pada kenyamanan dirinya sendiri, bukan? :) Ada faktor lain yang perlu dipertimbangkan. Oleh karena itu, Serializable bukan satu-satunya alat untuk serialisasi-deserialisasi di Java. Hari ini kita akan berkenalan dengan antarmuka yang Dapat Dieksternalisasi . Namun bahkan sebelum kita melanjutkan mempelajarinya, Anda mungkin memiliki pertanyaan yang masuk akal: mengapa kita memerlukan alat lain? SerializableSaya 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 Serializableia memiliki sejumlah kelemahan. Mari kita daftar beberapa di antaranya:
  1. Pertunjukan. Antarmukanya memiliki Serializablebanyak keunggulan, tetapi performa tinggi jelas bukan salah satunya.

Memperkenalkan Antarmuka yang Dapat Dieksternalisasi - 2

Pertama , mekanisme internal Serializablemenghasilkan 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 Serializabledidasarkan 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.

  1. 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 transientuntuk mengecualikan beberapa data, dan hanya itu. Semacam "perangkat" :/

  2. 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 Serializableitu tidak memberikan kesempatan ini.

Memperkenalkan Antarmuka yang Dapat Dieksternalisasi - 3Baiklah, akhirnya mari kita lihat seperti apa tampilan kelas menggunakan 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, ExternalizableAnda 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 outdan ObjectInputtelah 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 Memperkenalkan Antarmuka yang Dapat Dieksternalisasi - 4 Ups :( Ternyata tidak sesederhana itu! Mekanisme deserialisasi menimbulkan pengecualian dan mengharuskan kami membuat konstruktor default. Saya bertanya-tanya mengapa? SerializableKami berhasil tanpanya... :/ Di sini kita sampai pada nuansa penting lainnya . Perbedaan antara Serializabledan Externalizabletidak 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, Serializablememori 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 , Externalizablemekanisme deserialisasi akan berbeda. Pada awalnya, konstruktor default dipanggil. Dan baru kemudian UserInfodipanggil pada metode objek yang dibuat readExternal(), yang bertanggung jawab untuk mengisi bidang objek. Yaitu mengapa setiap kelas yang mengimplementasikan antarmuka Externalizableharus memiliki konstruktor default . Mari tambahkan ke kelas kita UserInfodan 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 Serializableatau Externalizable), perhatikan variabelnya static. Saat digunakan, Serializablebidang-bidang ini tidak diserialisasikan sama sekali (dan, karenanya, nilainya tidak berubah, karena staticbidang-bidang tersebut milik kelas, bukan objek). Namun saat menggunakannya, ExternalizableAnda 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, Serializablemereka diserialisasi dan dideserialisasi seperti biasa, tetapi saat digunakan, tidak mungkin Externalizableuntuk melakukan deserialisasi finalsuatu 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 Externalizablekelas juga harus memiliki konstruktor default. Berikut beberapa tautan ke artikel bagus tentang mekanisme serialisasi: Sampai jumpa! :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION