JavaRush /وبلاگ جاوا /Random-FA /رابط خارجی در جاوا

رابط خارجی در جاوا

در گروه منتشر شد
سلام! امروز ما به معرفی خود برای سریال سازی و سریال زدایی اشیاء جاوا ادامه خواهیم داد. در آخرین سخنرانی، ما با رابط نشانگر Serializable آشنا شدیم ، نمونه هایی از کاربرد آن را بررسی کردیم و همچنین یاد گرفتیم که چگونه فرآیند سریال سازی را با استفاده از کلمه کلیدی گذرا کنترل کنیم . خوب، "مدیریت فرآیند"، البته، یک کلمه قوی است. ما یک کلمه کلیدی، یک شناسه نسخه داریم و اساساً همین است. بقیه مراحل داخل جاوا "هاردسیم" است و هیچ دسترسی به آن وجود ندارد. از نقطه نظر راحتی، این، البته، خوب است. اما یک برنامه نویس در کارش باید نه تنها روی راحتی خودش تمرکز کند، درست است؟ :) عوامل دیگری نیز وجود دارد که باید در نظر گرفته شوند. بنابراین، Serializable تنها ابزاری برای سریال‌سازی – deserialization در جاوا نیست. امروز با رابط Externalizable آشنا می شویم . اما حتی قبل از اینکه به مطالعه آن بپردازیم، ممکن است یک سوال معقول داشته باشید: چرا به ابزار دیگری نیاز داریم؟ Serializableمن با کارم کنار آمدم و اجرای خودکار کل فرآیند نمی تواند شادی کند. نمونه هایی که به آنها نگاه کردیم نیز پیچیده نبودند. پس قضیه چیه؟ چرا یک رابط دیگر برای اساساً همان کار؟ واقعیت این است که Serializableیک سری معایب دارد. بیایید برخی از آنها را فهرست کنیم:
  1. کارایی. رابط کاربری Serializableمزایای زیادی دارد، اما عملکرد بالا به وضوح یکی از آنها نیست.

معرفی رابط خارجی - 2

در مرحله اول ، مکانیزم داخلی Serializableحجم زیادی از اطلاعات سرویس و انواع مختلفی از داده های موقت را در طول عملیات تولید می کند.
ثانیا (اگر علاقه دارید لازم نیست اکنون وارد این موضوع شوید و در اوقات فراغت خود مطالعه کنید)، کار Serializableبر اساس استفاده از Reflection API است. این ابزار به شما امکان می دهد کارهایی را انجام دهید که در جاوا غیرممکن به نظر می رسند: به عنوان مثال، مقادیر فیلدهای خصوصی را تغییر دهید. JavaRush یک مقاله عالی در مورد Reflection API دارد که می توانید در اینجا در مورد آن مطالعه کنید.

  1. انعطاف پذیری. ما هنگام استفاده از Serializable.

    از یک طرف، این بسیار راحت است، زیرا اگر ما واقعاً به عملکرد اهمیت نمی دهیم، توانایی ننوشتن کد راحت به نظر می رسد. اما اگر واقعاً نیاز داشته باشیم برخی از ویژگی های خودمان را (نمونه ای از یکی از آنها در زیر آمده است) به منطق سریال سازی اضافه کنیم، چه؟

    اساساً، تنها چیزی که ما برای کنترل فرآیند داریم یک کلمه کلیدی transientبرای حذف برخی داده ها است و بس. یه جورایی مثل یه "ابزار" :/

  2. ایمنی. این نکته تا حدی از مورد قبلی پیروی می کند.

    قبلاً زیاد به این موضوع فکر نکرده بودیم، اما اگر برخی از اطلاعات کلاس شما برای "گوش دیگران" (به طور دقیق تر، چشم) در نظر گرفته نشده باشد، چه؟ یک مثال ساده رمز عبور یا سایر داده های شخصی کاربر است که در دنیای مدرن توسط مجموعه ای از قوانین تنظیم می شود.

    با استفاده از Serializable، ما در واقع نمی توانیم کاری در مورد آن انجام دهیم. ما همه چیز را همانطور که هست سریال می کنیم.

    اما، به روشی خوب، ما باید این نوع داده ها را قبل از نوشتن روی یک فایل یا انتقال آن از طریق شبکه رمزگذاری کنیم. اما Serializableاین فرصت را نمی دهد.

معرفی رابط خارجی - 3خوب، در نهایت ببینیم یک کلاس با استفاده از 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; سازنده معتبری وجود ندارد معرفی رابط خارجی - 4 اوه :( معلوم شد که خیلی ساده نیست! مکانیسم 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کلاس نزول می‌کنند نیز باید سازنده‌های پیش‌فرض داشته باشند. در اینجا چند پیوند به مقالات خوب در مورد مکانیسم های سریال سازی وجود دارد: به امید دیدار! :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION