Serializable
अपना काम किया, और पूरी प्रक्रिया के स्वत: कार्यान्वयन के बारे में क्या अच्छा नहीं है? और जिन उदाहरणों को हमने देखा वे भी सरल थे। तो समस्या क्या है? अनिवार्य रूप से समान कार्यों के लिए हमें दूसरे इंटरफ़ेस की आवश्यकता क्यों है? सच तो यह है कि Serializable
इसमें कई कमियां हैं। हम उनमें से कुछ को सूचीबद्ध करते हैं:
-
प्रदर्शन। इंटरफ़ेस
Serializable
के कई फायदे हैं, लेकिन स्पष्ट रूप से उच्च प्रदर्शन उनमें से एक नहीं है।सबसे पहले,
Serializable
का आंतरिक कार्यान्वयन बड़ी मात्रा में सेवा जानकारी और सभी प्रकार के अस्थायी डेटा उत्पन्न करता है।दूसरा,
Serializable
प्रतिबिंब एपीआई पर निर्भर करता है (आपको अभी इस पर गहराई से गोता लगाने की ज़रूरत नहीं है; यदि आप रुचि रखते हैं, तो आप अपने अवकाश पर अधिक पढ़ सकते हैं)। यह चीज़ आपको जावा में असंभव प्रतीत होने वाली चीजें करने देती है: उदाहरण के लिए, निजी क्षेत्रों के मूल्यों को बदलें। CodeGym के पास रिफ्लेक्शन 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 अब आइए अपने अक्रमांकन तर्क का उपयोग करके देखें।
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; कोई मान्य निर्माता नहीं उफ़! :( जाहिर तौर पर, यह इतना आसान नहीं है! डिसेरिएलाइजेशन मैकेनिज्म ने एक अपवाद फेंक दिया और मांग की कि हम एक डिफ़ॉल्ट कंस्ट्रक्टर बनाएं Serializable
। Serializable
और के बीच का अंतर Externalizable
न केवल प्रोग्रामर की 'विस्तारित' पहुंच और प्रक्रिया को अधिक लचीले ढंग से नियंत्रित करने की क्षमता में है, बल्कि स्वयं प्रक्रिया में भी है। इन सबसे ऊपर, अक्रमांकन तंत्र में । उपयोग करते समयSerializable
, मेमोरी को केवल ऑब्जेक्ट के लिए आवंटित किया जाता है, और फिर मान स्ट्रीम से पढ़े जाते हैं और ऑब्जेक्ट के फ़ील्ड को सेट करने के लिए उपयोग किए जाते हैं। यदि हम उपयोग करते हैं Serializable
, तो वस्तु के निर्माता को नहीं कहा जाता है! सभी कार्य प्रतिबिंब के माध्यम से होते हैं (प्रतिबिंब एपीआई, जिसे हमने पिछले पाठ में संक्षेप में बताया था)। के साथ 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 = 'Paul', lastName = 'Piper', SuperSecretInformation = 'पॉल पाइपर का पासपोर्ट डेटा' } अब यह पूरी तरह से कुछ अलग है! सबसे पहले, गुप्त सूचना के साथ डिक्रिप्टेड स्ट्रिंग को कंसोल पर प्रदर्शित किया गया था। तब हमने फ़ाइल से जो वस्तु बरामद की थी वह एक स्ट्रिंग के रूप में प्रदर्शित हुई थी! इसलिए हमने सभी समस्याओं को सफलतापूर्वक हल कर लिया है :) क्रमांकन और अक्रमांकन का विषय सरल लगता है, लेकिन, जैसा कि आप देख सकते हैं, पाठ लंबे रहे हैं। और भी बहुत कुछ है जिसे हमने कवर नहीं किया है! इनमें से प्रत्येक इंटरफेस का उपयोग करते समय अभी भी कई सूक्ष्मताएं शामिल हैं। लेकिन अत्यधिक नई जानकारी से आपके मस्तिष्क को विस्फोट से बचाने के लिए, मैं संक्षेप में कुछ और महत्वपूर्ण बिंदुओं की सूची दूंगा और अतिरिक्त पढ़ने के लिए आपको लिंक दूंगा। तो, आपको और क्या जानने की ज़रूरत है? सबसे पहले , क्रमांकन के दौरान (भले ही आप Serializable
या का उपयोग कर रहे हों Externalizable
), static
चरों पर ध्यान दें। जब आप उपयोग करते हैं Serializable
, तो इन क्षेत्रों को क्रमबद्ध नहीं किया जाता है (और, तदनुसार, उनके मान नहीं बदलते हैं, क्योंकि static
फ़ील्ड वर्ग से संबंधित हैं, वस्तु से नहीं)। लेकिन जब आप इस्तेमाल करते हैंExternalizable
, आप प्रक्रिया को स्वयं नियंत्रित करते हैं, इसलिए तकनीकी रूप से आप उन्हें क्रमबद्ध कर सकते हैं। लेकिन, हम इसकी अनुशंसा नहीं करते हैं, क्योंकि ऐसा करने से बहुत सारे सूक्ष्म बग बनने की संभावना है। दूसराfinal
, आपको संशोधक के साथ चर पर भी ध्यान देना चाहिए । जब आप उपयोग करते हैं Serializable
, तो वे सामान्य रूप से क्रमबद्ध और deserialized होते हैं, लेकिन जब आप उपयोग करते हैं , तो एक चर Externalizable
को deserialize करना असंभव हैfinal
! कारण सरल है: final
जब डिफॉल्ट कंस्ट्रक्टर को कॉल किया जाता है तो सभी फील्ड इनिशियलाइज़ हो जाते हैं - उसके बाद, उनका मान नहीं बदला जा सकता है। इसलिए, उन वस्तुओं को क्रमबद्ध करने के लिए जिनमें final
फ़ील्ड हैं, द्वारा प्रदान किए गए मानक क्रमांकन का उपयोग करें Serializable
। तीसरा , जब आप वंशानुक्रम का उपयोग करते हैं, तो सभी वंशज वर्ग जो कुछ को प्राप्त करते हैंExternalizable
क्लास में डिफॉल्ट कंस्ट्रक्टर भी होना चाहिए। यहाँ क्रमांकन तंत्र के बारे में अच्छे लेख का लिंक दिया गया है:
अगली बार तक! :)
GO TO FULL VERSION