Serializable
کار خود را انجام داد، و چه چیزی در مورد اجرای خودکار کل فرآیند را دوست ندارید؟ و نمونه هایی که ما به آنها نگاه کردیم نیز بدون عارضه بودند. پس مشکل چیست؟ چرا اساساً برای کارهای مشابه به رابط دیگری نیاز داریم؟ واقعیت این است که Serializable
چند کاستی دارد. ما تعدادی از آنها را فهرست می کنیم:
-
کارایی. رابط
Serializable
کاربری مزایای زیادی دارد، اما عملکرد بالا به وضوح یکی از آنها نیست.اول،
Serializable
پیاده سازی داخلی 's مقدار زیادی از اطلاعات سرویس و انواع داده های موقت تولید می کند.دوم،
Serializable
به Reflection API متکی است (در حال حاضر لازم نیست در این مورد غوطه ور شوید؛ اگر علاقه مند هستید، می توانید در اوقات فراغت خود بیشتر بخوانید). این چیز به شما امکان می دهد کارهای به ظاهر غیرممکن را در جاوا انجام دهید: به عنوان مثال، مقادیر فیلدهای خصوصی را تغییر دهید. CodeGym یک مقاله عالی در مورد Reflection API دارد . شما می توانید در مورد آن در آنجا بخوانید. -
انعطاف پذیری. هنگامی که از رابط استفاده می کنیم، فرآیند سریال سازی-جداسازی را کنترل نمی کنیم
Serializable
.از یک طرف، بسیار راحت است، زیرا اگر ما به طور خاص نگران عملکرد نباشیم، به نظر خوب می رسد که مجبور نباشیم کد بنویسیم. اما اگر واقعاً نیاز داشته باشیم برخی از ویژگی های خودمان را (در زیر مثالی ارائه می دهیم) به منطق سریال سازی اضافه کنیم، چه؟
اساساً، تنها چیزی که ما باید فرآیند را کنترل کنیم،
transient
کلمه کلیدی برای حذف برخی از داده ها است. خودشه. این تمام جعبه ابزار ماست :/ -
امنیت. این مورد تا حدی از مورد قبلی مشتق شده است.
ما قبلاً زمان زیادی را صرف فکر کردن به این موضوع نکردهایم، اما اگر برخی از اطلاعات کلاس شما برای چشمان و گوشهای کنجکاو دیگران در نظر گرفته نشده باشد، چه؟ یک مثال ساده یک رمز عبور یا سایر داده های شخصی کاربر است که در دنیای امروزی توسط مجموعه ای از قوانین اداره می شود.
اگر استفاده کنیم
Serializable
، واقعاً نمی توانیم کاری در مورد آن انجام دهیم. ما همه چیز را همانطور که هست سریال می کنیم.اما اگر این کار را به روش درست انجام دهیم، باید این نوع داده ها را قبل از نوشتن روی یک فایل یا ارسال آن از طریق شبکه رمزگذاری کنیم. اما
Serializable
این امکان را نمی دهد

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 {
}
}
همانطور که می بینید، ما تغییرات قابل توجهی داریم! یکی از موارد اصلی واضح است: هنگام پیاده سازی Externalizable
رابط، باید دو روش مورد نیاز را پیاده سازی کنید: writeExternal()
و readExternal()
. همانطور که قبلاً گفتیم، مسئولیت سریال سازی و سریال زدایی بر عهده برنامه نویس است. اما اکنون می توانید مشکل عدم کنترل فرآیند را حل کنید! کل فرآیند مستقیماً توسط شما برنامه ریزی شده است. به طور طبیعی، این امکان مکانیزم بسیار انعطاف پذیرتری را فراهم می کند. علاوه بر این، مشکل امنیت حل شده است. همانطور که می بینید، کلاس ما دارای یک فیلد اطلاعات شخصی است که نمی توان آن را بدون رمز ذخیره کرد. اکنون می توانیم به راحتی کدی بنویسیم که این محدودیت را برآورده کند. برای مثال، میتوانیم دو روش خصوصی ساده برای رمزگذاری و رمزگشایی دادههای حساس به کلاس خود اضافه کنیم. ما داده ها را در فایل می نویسیم و آن را به صورت رمزگذاری شده از فایل می خوانیم. بقیه داده ها همانطور که هستند نوشته و خوانده می شوند :) در نتیجه کلاس ما چیزی شبیه به این است:
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;
}
}
ما دو روش را پیاده سازی کردیم که از همان پارامترها ObjectOutput
و 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()
و decryptString()
متدها، ما بهطور خاص خروجی کنسول را اضافه کردیم تا فرمی را که دادههای مخفی در آن نوشته و خوانده میشوند، تأیید کنیم. کد بالا خط زیر را نشان می دهد: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh رمزگذاری با موفقیت انجام شد! محتویات کامل فایل به این صورت است: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx حال بیایید از منطق deserialization خود استفاده کنیم.
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();
}
}
خوب، هیچ چیز در اینجا پیچیده به نظر نمی رسد. باید کار کند! ما آن را اجرا می کنیم و می گیریم ... Exception در thread "main" java.io.InvalidClassException: UserInfo; سازنده معتبری وجود ندارد 
Serializable
, ما بدون یک ... :/ اینجا با یک تفاوت مهم دیگر روبرو شدیم. تفاوت بین Serializable
و Externalizable
نه تنها در دسترسی "گسترش" برنامه نویس و توانایی کنترل انعطاف پذیرتر فرآیند، بلکه در خود فرآیند نهفته است. مهمتر از همه، در مکانیسم deserialization . هنگام استفاده Serializable
، حافظه به سادگی برای شی تخصیص داده می شود، و سپس مقادیر از جریان خوانده می شوند و برای تنظیم فیلدهای شی مورد استفاده قرار می گیرند. اگر استفاده کنیم، Serializable
سازنده شی فراخوانی نمی شود Externalizable
! مکانیسم deserialization متفاوت است.ابتدا سازنده پیش فرض فراخوانی می شود.فقط بعد از آن متد UserInfo
شی ایجاد شده readExternal()
فراخوانی می شود.مسئول تنظیم فیلدهای آبجکت است.به همین دلیل است که هر کلاسی که Externalizable
اینترفیس را پیاده سازی می کند باید یک سازنده پیش فرض داشته باشد . بیایید یکی را به UserInfo
کلاس خود اضافه کنیم و کد را دوباره اجرا کنیم:
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();
}
}
خروجی کنسول: اطلاعات پاسپورت پل پایپر UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'اطلاعات پاسپورت Paul Piper' } اکنون این چیزی کاملاً متفاوت است! ابتدا رشته رمزگشایی شده با اطلاعات مخفی روی کنسول نمایش داده شد. سپس شیئی که از فایل بازیابی کردیم به صورت رشته ای نمایش داده شد! بنابراین ما با موفقیت تمام مشکلات را حل کردیم :) موضوع سریال سازی و سریال سازی ساده به نظر می رسد، اما همانطور که می بینید، درس ها طولانی شده است. و خیلی چیزهای دیگر وجود دارد که ما پوشش نداده ایم! هنگام استفاده از هر یک از این رابط ها هنوز ظرافت های زیادی وجود دارد. اما برای جلوگیری از انفجار مغز شما از اطلاعات جدید بیش از حد، من به طور خلاصه چند نکته مهم دیگر را لیست می کنم و به شما پیوندهایی را برای مطالعه بیشتر ارائه می دهم. بنابراین، چه چیز دیگری باید بدانید؟ اول ، در طول سریال سازی (صرف نظر از اینکه استفاده می کنید Serializable
یا )، به متغیرها Externalizable
توجه کنید . static
وقتی از . استفاده می کنید Serializable
، این فیلدها اصلا سریالی نمی شوند (و بر این اساس، مقادیر آنها تغییر نمی کند، زیرا static
فیلدها متعلق به کلاس هستند، نه شی). اما زمانی که از را استفاده میکنید Externalizable
، فرآیند را خودتان کنترل میکنید، بنابراین از نظر فنی میتوانید آنها را سریال کنید. اما، ما آن را توصیه نمی کنیم، زیرا انجام این کار احتمالاً باعث ایجاد بسیاری از اشکالات ظریف می شود. دوم ، شما باید به متغیرهای دارای اصلاح کننده نیز توجه کنید final
. هنگام استفاده از Serializable
آنها طبق معمول سریالی و غیر سریالی می شوند، اما وقتی از آن استفاده می کنید Externalizable
، غیرممکن است که یک final
متغیر را سریالی کنید ! دلیل ساده است: همه final
فیلدها با فراخوانی سازنده پیش فرض مقداردهی اولیه می شوند - پس از آن، مقدار آنها قابل تغییر نیست. بنابراین، برای سریال سازی اشیایی که دارای final
فیلد هستند، از سریال سازی استاندارد ارائه شده توسط Serializable
. سوم ، وقتی از وراثت استفاده میکنید، تمام کلاسهای نسلی که برخی از Externalizable
کلاسها را به ارث میبرند باید سازندههای پیشفرض نیز داشته باشند. در اینجا پیوند به مقاله خوب در مورد مکانیسم های سریال سازی وجود دارد:
تا دفعه بعد! :)
GO TO FULL VERSION