Serializable
işini yaptı ve tüm sürecin otomatik olarak uygulanmasında sevilmeyecek ne var? Ve baktığımız örnekler de karmaşık değildi. Öyleyse sorun nedir? Temelde aynı görevler için neden başka bir arayüze ihtiyacımız var? Gerçek şu ki, Serializable
birkaç eksikliği var. Bazılarını listeliyoruz:
-
Verim. Arayüzün
Serializable
birçok avantajı vardır, ancak yüksek performans açıkça bunlardan biri değildir.İlk olarak,
Serializable
'ın dahili uygulaması büyük miktarda hizmet bilgisi ve her türden geçici veri üretir.İkincisi,
Serializable
Reflection API'ye dayanır (şu anda bu konuya derinlemesine dalmak zorunda değilsiniz; ilgileniyorsanız, boş zamanlarınızda daha fazlasını okuyabilirsiniz). Bu şey, Java'da imkansız gibi görünen şeyleri yapmanızı sağlar: örneğin, özel alanların değerlerini değiştirmek. CodeGym'de Reflection API hakkında mükemmel bir makale var . Orada okuyabilirsiniz. -
Esneklik. Arayüzü kullandığımızda seri hale getirme-seri hale getirme sürecini kontrol etmiyoruz
Serializable
.Bir yandan, çok uygun, çünkü özellikle performansla ilgilenmiyorsak, o zaman kod yazmak zorunda kalmamak güzel görünüyor. Peki ya serileştirme mantığına gerçekten kendi bazı özelliklerimizi (aşağıda bir örnek vereceğiz) eklememiz gerekirse?
Temel olarak, süreci kontrol etmemiz gereken tek şey,
transient
bazı verileri hariç tutmak için anahtar kelimedir. Bu kadar. Tüm araç kutumuz bu :/ -
Güvenlik. Bu öğe kısmen önceki öğeden türemiştir.
Bunu daha önce düşünmek için fazla zaman harcamadık, ama ya sınıfınızdaki bazı bilgiler başkalarının meraklı gözlerine ve kulaklarına yönelik değilse? Basit bir örnek, günümüz dünyasında bir dizi yasa tarafından yönetilen bir şifre veya diğer kişisel kullanıcı verileridir.
Eğer kullanırsak
Serializable
, bu konuda gerçekten hiçbir şey yapamayız. Her şeyi olduğu gibi seri hale getiriyoruz.Ancak doğru şekilde yaparsak, bu tür verileri bir dosyaya yazmadan veya bir ağ üzerinden göndermeden önce şifrelememiz gerekir. Ama
Serializable
bunu mümkün kılmaz.

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;
// ...constructor, getters, setters, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
Gördüğünüz gibi, önemli değişikliklerimiz var! Ana olan açıktır: Externalizable
arayüzü uygularken, gerekli iki yöntemi uygulamanız gerekir: writeExternal()
vereadExternal()
. Daha önce de söylediğimiz gibi, seri hale getirme ve seriden kaldırma sorumluluğu programcıya aittir. Ancak artık süreç üzerinde kontrol olmaması sorununu çözebilirsiniz! Tüm süreç doğrudan sizin tarafınızdan programlanır. Doğal olarak, bu çok daha esnek bir mekanizmaya izin verir. Ek olarak, güvenlik sorunu çözüldü. Görüldüğü üzere sınıfımızda şifresiz saklanamayan bir kişisel veri alanı bulunmaktadır. Artık bu kısıtlamayı karşılayan kodu kolayca yazabiliriz. Örneğin, hassas verileri şifrelemek ve şifresini çözmek için sınıfımıza iki basit özel yöntem ekleyebiliriz. Verileri dosyaya yazacağız ve dosyadan şifreli biçimde okuyacağız. Geri kalan veriler olduğu gibi yazılacak ve okunacaktır :) Sonuç olarak sınıfımız şöyle bir şeye benziyor:
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;
}
}
Hakkında derste zaten tanıştığımız aynı parametreleri ObjectOutput
ve parametreleri kullanan iki yöntem uyguladık . Doğru anda, gerekli verileri şifreler veya şifresini çözeriz ve şifrelenmiş verileri nesnemizi seri hale getirmek için kullanırız. Bunun pratikte nasıl göründüğüne bakalım: ObjectInput
Serializable
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("Paul", "Piper", "Paul Piper's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
encryptString()
and yöntemlerinde decryptString()
, gizli verilerin yazılacağı ve okunacağı formu doğrulamak için özellikle konsol çıktısı ekledik. Yukarıdaki kod aşağıdaki satırı görüntüledi: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Şifreleme başarılı oldu! Dosyanın tam içeriği şuna benzer: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Şimdi seri durumdan çıkarma mantığımızı kullanmayı deneyelim.
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();
}
}
Burada hiçbir şey karmaşık görünmüyor. İşe yaramalı! Çalıştırıyoruz ve şunu alıyoruz... "main" dizisindeki istisna java.io.InvalidClassException: UserInfo; geçerli kurucu yok 
Serializable
tane olmadan idare ettik... :/ Burada önemli bir nüansla daha karşılaştık. Serializable
ve arasındaki fark Externalizable
yalnızca programcının 'genişletilmiş' erişiminde ve süreci daha esnek bir şekilde kontrol etme yeteneğinde değil, aynı zamanda sürecin kendisinde de yatmaktadır .Serializable
, bellek basitçe nesne için ayrılır ve ardından değerler akıştan okunur ve nesnenin alanlarını ayarlamak için kullanılır. kullanırsak Serializable
, nesnenin yapıcısı çağrılmaz! Tüm iş yansıma (son derste kısaca bahsettiğimiz Reflection API) aracılığıyla gerçekleşir. ile Externalizable
seri durumdan çıkarma mekanizması farklıdır. İlk önce varsayılan kurucu çağrılır. Ancak bundan sonra yaratılan UserInfo
nesnenin readExternal()
yöntemi çağrılır. Nesnenin alanlarını ayarlamaktan sorumludur. Bu nedenle, arayüzü uygulayan herhangi bir sınıfın Externalizable
varsayılan bir yapıcıya sahip olması gerekir . Sınıfımıza bir tane ekleyelim UserInfo
ve kodu tekrar çalıştıralım:
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();
}
}
Konsol çıktısı: Paul Piper'ın pasaport verileri UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper'ın pasaport verileri' } Şimdi bu tamamen farklı bir şey! İlk olarak, gizli bilgiler içeren şifresi çözülmüş dizi konsolda görüntülendi. Ardından dosyadan kurtardığımız nesne bir dizi olarak görüntülendi! Böylece tüm sorunları başarıyla çözdük :) Serileştirme ve seriden çıkarma konusu basit görünüyor, ancak gördüğünüz gibi dersler uzun oldu. Ve ele almadığımız çok daha fazlası var! Bu arayüzlerin her birini kullanırken hala birçok incelik vardır. Ancak aşırı yeni bilgi yüzünden beyninizi patlatmaktan kaçınmak için, kısaca birkaç önemli nokta daha listeleyeceğim ve size ek okumalar için bağlantılar vereceğim. Peki, başka ne bilmeniz gerekiyor? İlk olarakSerializable
, serileştirme sırasında ( veya kullanıp kullanmadığınıza bakılmaksızın Externalizable
), değişkenlere dikkat edin static
. kullandığınızda Serializable
, bu alanlar hiçbir şekilde serileştirilmez (ve buna göre değerleri değişmez, çünkü static
alanlar nesneye değil sınıfa aittir). Ama kullandığınızdaExternalizable
, süreci kendiniz kontrol edersiniz, böylece teknik olarak bunları seri hale getirebilirsiniz. Ancak, yapılması pek çok ince hataya yol açabileceğinden bunu önermiyoruz. İkincisi , değiştiricili değişkenlere de dikkat etmelisiniz final
. kullandığınızda Serializable
, her zamanki gibi serileştirilir ve seriden çıkarılır, ancak kullandığınızda , bir değişkenin Externalizable
serisini kaldırmak imkansızdırfinal
! Nedeni basit: final
varsayılan kurucu çağrıldığında tüm alanlar başlatılır - bundan sonra değerleri değiştirilemez. Bu nedenle, alanları olan nesneleri seri hale getirmek için final
tarafından sağlanan standart serileştirmeyi kullanın Serializable
. Üçüncüsü , kalıtımı kullandığınızda, bazılarını miras alan tüm alt sınıflarExternalizable
sınıfın ayrıca varsayılan kurucuları olmalıdır. İşte serileştirme mekanizmaları hakkında iyi makalenin bağlantısı:
Bir sonrakine kadar! :)
GO TO FULL VERSION