Serializable
من با کارم کنار آمدم و اجرای خودکار کل فرآیند نمی تواند شادی کند. نمونه هایی که به آنها نگاه کردیم نیز پیچیده نبودند. پس قضیه چیه؟ چرا یک رابط دیگر برای اساساً همان کار؟ واقعیت این است که Serializable
یک سری معایب دارد. بیایید برخی از آنها را فهرست کنیم:
-
کارایی. رابط کاربری
Serializable
مزایای زیادی دارد، اما عملکرد بالا به وضوح یکی از آنها نیست.
در مرحله اول ، مکانیزم داخلی Serializable
حجم زیادی از اطلاعات سرویس و انواع مختلفی از داده های موقت را در طول عملیات تولید می کند.
ثانیا (اگر علاقه دارید لازم نیست اکنون وارد این موضوع شوید و در اوقات فراغت خود مطالعه کنید)، کار Serializable
بر اساس استفاده از Reflection API است. این ابزار به شما امکان می دهد کارهایی را انجام دهید که در جاوا غیرممکن به نظر می رسند: به عنوان مثال، مقادیر فیلدهای خصوصی را تغییر دهید. JavaRush یک مقاله عالی در مورد 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;
//...конструктор, геттеры, сеттеры, 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 out
و به عنوان پارامترهایی استفاده می کنند 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("Ivan", "Ivanov", "Ivan Ivanov'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();
}
}
خوب، به نظر می رسد هیچ چیز پیچیده ای در اینجا وجود ندارد، باید کار کند! اجازه دهید اجرا کنیم... استثنا در موضوع "اصلی" java.io.InvalidClassException: UserInfo; سازنده معتبری وجود ندارد اوه :( معلوم شد که خیلی ساده نیست! مکانیسم deserialization یک استثنا انداخت و از ما خواست که یک سازنده پیش فرض ایجاد کنیم. تعجب می کنم که چرا؟ Serializable
ما بدون آن موفق شدیم... :/ اینجا به یک نکته مهم دیگر می رسیم تفاوت بین Serializable
و Externalizable
نه تنها در دسترسی "گسترده" برای برنامه نویس و توانایی مدیریت انعطاف پذیرتر فرآیند است، بلکه در خود فرآیند نیز نهفتهSerializable
است . برای یک شی تخصیص داده می شود و پس از آن مقادیری از جریان خوانده می شود که تمام فیلدهای آن را پر می کند. اگر از آن استفاده کنیم Serializable
سازنده شی فراخوانی نمی شود! همه کارها از طریق انعکاس انجام می شود (API Reflection که در آخر به طور خلاصه به آن اشاره کردیم. سخنرانی).در مورد , 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='Ivan', lastName='Ivanov', superSecretInformation='اطلاعات پاسپورت ایوان ایوانف'} موضوعی کاملاً متفاوت! ابتدا رشته رمزگشایی شده با داده های مخفی به کنسول خروجی داده شد و سپس شیء ما از فایل با فرمت رشته ای بازیابی شد! اینطوری همه مشکلات را با موفقیت حل کردیم :) موضوع سریال سازی و سریال سازی ساده به نظر می رسد، اما همانطور که می بینید، سخنرانی های ما طولانی شد. و این تمام نیست! هنگام استفاده از هر یک از این رابط ها ظرافت های بسیار بیشتری وجود دارد، اما برای اینکه اکنون مغز شما از حجم اطلاعات جدید منفجر نشود، به طور خلاصه چند نکته مهم دیگر را لیست می کنم و پیوندهایی برای خواندن اضافی ارائه می دهم. پس چه چیز دیگری باید بدانید؟ در مرحله اول ، هنگام سریال سازی (مهم نیست که استفاده می کنید Serializable
یا Externalizable
) به متغیرها توجه کنید static
. هنگام استفاده، Serializable
این فیلدها به هیچ وجه سریال سازی نمی شوند (و بر این اساس، مقدار آنها تغییر نمی کند، زیرا static
فیلدها متعلق به کلاس هستند، نه شی). اما هنگام استفاده از آن، Externalizable
شما خودتان فرآیند را کنترل می کنید، بنابراین از نظر فنی می توان این کار را انجام داد. اما توصیه نمی شود، زیرا مملو از خطاهای ظریف است. ثانیاً باید به متغیرهای دارای اصلاح کننده نیز توجه شود final
. هنگام استفاده، Serializable
آنها طبق معمول سریال و غیر سریال می شوند، اما در صورت استفاده، غیرممکن است Externalizable
که final
یک متغیر را سریالی کنیم ! دلیل ساده است: همه final
فیلدها با فراخوانی سازنده پیش فرض مقداردهی اولیه می شوند و پس از آن نمی توان مقدار آنها را تغییر داد. بنابراین، برای سریال سازی اشیاء حاوی final
فیلدها، از سریال سازی استاندارد از طریق Serializable
. ثالثاً ، هنگام استفاده از وراثت، تمام کلاسهای ارث بری که از یک Externalizable
کلاس نزول میکنند نیز باید سازندههای پیشفرض داشته باشند. در اینجا چند پیوند به مقالات خوب در مورد مکانیسم های سریال سازی وجود دارد:
به امید دیدار! :)
GO TO FULL VERSION