JavaRush /مدونة جافا /Random-AR /واجهة خارجية في جافا

واجهة خارجية في جافا

نشرت في المجموعة
مرحبًا! سنواصل اليوم تقديمنا لتسلسل وإلغاء تسلسل كائنات Java. في المحاضرة الأخيرة، تعرفنا على واجهة العلامة القابلة للتسلسل ، ونظرنا في أمثلة لاستخدامها، وتعلمنا أيضًا كيفية التحكم في عملية التسلسل باستخدام الكلمة الأساسية العابرة . حسنًا، إن عبارة "إدارة العملية" هي بالطبع كلمة قوية. لدينا كلمة رئيسية واحدة، ومعرف إصدار واحد، وهذا كل ما في الأمر. بقية العملية "مثبتة" داخل جافا، ولا يمكن الوصول إليها. من وجهة نظر الراحة، هذا أمر جيد بالطبع. لكن المبرمج في عمله يجب أن يركز ليس فقط على راحته، أليس كذلك؟ :) وهناك عوامل أخرى للنظر فيها. ولذلك، فإن Serializable ليس الأداة الوحيدة للتسلسل وإلغاء التسلسل في Java. اليوم سوف نتعرف على الواجهة القابلة للتحويل . ولكن حتى قبل أن ننتقل إلى دراستها، قد يكون لديك سؤال معقول: لماذا نحتاج إلى أداة أخرى؟ Serializableلقد تعاملت مع وظيفتي، والتنفيذ التلقائي للعملية برمتها لا يمكن إلا أن نفرح. الأمثلة التي نظرنا إليها لم تكن معقدة أيضًا. إذن ما هو الاتفاق؟ لماذا واجهة أخرى لنفس المهمة في الأساس؟ والحقيقة هي أن Serializableلديها عددا من العيوب. دعونا قائمة بعض منهم:
  1. أداء. تتمتع الواجهة Serializableبالعديد من المزايا، ولكن من الواضح أن الأداء العالي ليس واحدًا منها.

تقديم الواجهة القابلة للتحويل إلى الخارج - 2

أولاً ، تقوم الآلية الداخلية Serializableبتوليد كمية كبيرة من معلومات الخدمة وأنواع مختلفة من البيانات المؤقتة أثناء التشغيل.
ثانيًا (ليس عليك الخوض في هذا الآن والقراءة في وقت فراغك إذا كنت مهتمًا)، Serializableيعتمد العمل على استخدام Reflection API. تتيح لك هذه الأداة الغريبة القيام بأشياء قد تبدو مستحيلة في Java: على سبيل المثال، تغيير قيم الحقول الخاصة. لدى 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 الآن دعونا نحاول استخدام منطق إلغاء التسلسل الذي كتبناه.
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 عفوًا :( اتضح أن الأمر ليس بهذه البساطة! ألقت آلية إلغاء التسلسل استثناءً وتطلبت منا إنشاء مُنشئ افتراضي. أتساءل لماذا؟ لقد Serializableنجحنا في ذلك بدونه... :/ هنا نأتي إلى فارق بسيط مهم آخر "الفرق بين Serializableو Externalizableلا يكمن فقط في الوصول "الممتد" للمبرمج والقدرة على إدارة العملية بشكل أكثر مرونة، ولكن أيضًا في العملية نفسها. أولاً وقبل كل شيء، في آلية إلغاء التسلسل ... عند استخدامها، Serializableتكون الذاكرة ببساطة المخصص للكائن وبعد ذلك تتم قراءة القيم من المجرى الذي يملأ جميع حقوله وإذا استخدمنا Serializableلا يتم استدعاء منشئ الكائن!كل العمل يتم من خلال الانعكاس (Reflection API الذي ذكرناه باختصار في الأخير محاضرة). في حالة ، Externalizableستكون آلية إلغاء التسلسل مختلفة. في البداية، يتم استدعاء المنشئ الافتراضي. وعندها فقط 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