Serializable
hat seinen Job gemacht, und was kann man an der automatischen Implementierung des gesamten Prozesses nicht lieben? Und auch die Beispiele, die wir uns angeschaut haben, waren unkompliziert. Also, was ist das Problem? Warum brauchen wir für im Wesentlichen dieselben Aufgaben eine andere Schnittstelle? Tatsache ist, dass es Serializable
mehrere Mängel aufweist. Wir listen einige davon auf:
-
Leistung. Die
Serializable
Schnittstelle hat viele Vorteile, aber hohe Leistung gehört eindeutig nicht dazu.Erstens
Serializable
generiert die interne Implementierung eine große Menge an Serviceinformationen und alle Arten von temporären Daten.Zweitens
Serializable
basiert es auf der Reflection-API (Sie müssen sich jetzt nicht intensiv damit befassen; bei Interesse können Sie in Ruhe mehr darüber lesen). Mit diesem Ding können Sie die scheinbar unmöglichen Dinge in Java tun: zum Beispiel die Werte privater Felder ändern. CodeGym hat einen hervorragenden Artikel über die Reflection API . Dort können Sie darüber nachlesen. -
Flexibilität. Wir kontrollieren den Serialisierungs-Deserialisierungsprozess nicht, wenn wir die
Serializable
Schnittstelle verwenden.Einerseits ist es sehr praktisch, denn wenn uns die Leistung nicht besonders wichtig ist, scheint es schön, keinen Code schreiben zu müssen. Aber was ist, wenn wir der Serialisierungslogik wirklich einige unserer eigenen Funktionen hinzufügen müssen (wir geben unten ein Beispiel)?
Im Grunde müssen wir den Prozess nur mit dem
transient
Schlüsselwort steuern, um einige Daten auszuschließen. Das ist es. Das ist unser gesamter Werkzeugkasten :/ -
Sicherheit. Dieser Artikel leitet sich teilweise vom vorherigen Artikel ab.
Wir haben bisher nicht viel Zeit damit verbracht, darüber nachzudenken, aber was ist, wenn einige Informationen in Ihrem Unterricht nicht für die neugierigen Augen und Ohren anderer bestimmt sind? Ein einfaches Beispiel ist ein Passwort oder andere persönliche Benutzerdaten, die heutzutage einer Reihe von Gesetzen unterliegen.
Wenn wir verwenden
Serializable
, können wir eigentlich nichts dagegen tun. Wir serialisieren alles so, wie es ist.Aber wenn wir es richtig machen, müssen wir diese Art von Daten verschlüsseln, bevor wir sie in eine Datei schreiben oder über ein Netzwerk senden. Macht das aber
Serializable
nicht möglich.
Externalizable
Schnittstelle verwenden würden.
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 {
}
}
Wie Sie sehen, gibt es bei uns erhebliche Veränderungen! Der wichtigste liegt auf der Hand: Bei der Implementierung der Externalizable
Schnittstelle müssen Sie zwei erforderliche Methoden implementieren: writeExternal()
undreadExternal()
. Wie bereits erwähnt, liegt die Verantwortung für die Serialisierung und Deserialisierung beim Programmierer. Aber jetzt können Sie das Problem der fehlenden Kontrolle über den Prozess lösen! Der gesamte Prozess wird direkt von Ihnen programmiert. Dies ermöglicht natürlich einen wesentlich flexibleren Mechanismus. Darüber hinaus ist das Problem mit der Sicherheit gelöst. Wie Sie sehen, verfügt unsere Klasse über ein persönliches Datenfeld, das nicht unverschlüsselt gespeichert werden kann. Jetzt können wir problemlos Code schreiben, der diese Einschränkung erfüllt. Beispielsweise können wir unserer Klasse zwei einfache private Methoden zum Ver- und Entschlüsseln sensibler Daten hinzufügen. Wir schreiben die Daten in die Datei und lesen sie verschlüsselt aus der Datei. Der Rest der Daten wird so wie sie sind geschrieben und gelesen :) Als Ergebnis sieht unsere Klasse in etwa so aus:
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;
}
}
Wir haben zwei Methoden implementiert, die dieselben Parameter ObjectOutput
und ObjectInput
Parameter verwenden, die wir bereits in der Lektion kennengelernt haben Serializable
. Im richtigen Moment verschlüsseln oder entschlüsseln wir die erforderlichen Daten und verwenden die verschlüsselten Daten, um unser Objekt zu serialisieren. Mal sehen, wie das in der Praxis aussieht:
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();
}
}
In den Methoden encryptString()
und decryptString()
haben wir speziell eine Konsolenausgabe hinzugefügt, um die Form zu überprüfen, in der die geheimen Daten geschrieben und gelesen werden. Der obige Code zeigte die folgende Zeile an: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Die Verschlüsselung war erfolgreich! Der vollständige Inhalt der Datei sieht folgendermaßen aus: ¬í sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Versuchen wir nun, unsere Deserialisierungslogik zu verwenden.
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();
}
}
Nun, hier scheint nichts kompliziert zu sein. Es sollte funktionieren! Wir führen es aus und erhalten ... Ausnahme im Thread „main“ java.io.InvalidClassException: UserInfo; Kein gültiger Konstruktor Ups! :( Anscheinend ist es nicht so einfach! Der Deserialisierungsmechanismus hat eine Ausnahme ausgelöst und verlangt, dass wir einen Standardkonstruktor erstellen. Ich frage mich, warum. Mit sind Serializable
wir ohne ausgekommen... :/ Hier sind wir auf eine weitere wichtige Nuance gestoßen. Die Der Unterschied zwischen Serializable
und Externalizable
liegt nicht nur im „erweiterten“ Zugriff des Programmierers und der Möglichkeit, den Prozess flexibler zu steuern, sondern auch im Prozess selbst. Vor allem im Deserialisierungsmechanismus . Bei der VerwendungSerializable
, wird dem Objekt einfach Speicher zugewiesen, und dann werden Werte aus dem Stream gelesen und zum Festlegen der Felder des Objekts verwendet. Wenn wir verwenden Serializable
, wird der Konstruktor des Objekts nicht aufgerufen! Die gesamte Arbeit geschieht durch Reflektion (die Reflection API, die wir in der letzten Lektion kurz erwähnt haben). Bei Externalizable
ist der Deserialisierungsmechanismus anders. Zuerst wird der Standardkonstruktor aufgerufen. Erst danach wird die Methode des erstellten UserInfo
Objekts readExternal()
aufgerufen. Es ist für das Festlegen der Felder des Objekts verantwortlich. Aus diesem Grund muss jede Klasse, die die Externalizable
Schnittstelle implementiert, über einen Standardkonstruktor verfügen . Fügen wir einen zu unserer UserInfo
Klasse hinzu und führen den Code erneut aus:
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();
}
}
Konsolenausgabe: Passdaten von Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Passdaten von Paul Piper' } Das ist etwas ganz anderes! Zunächst wurde die entschlüsselte Zeichenfolge mit geheimen Informationen auf der Konsole angezeigt. Dann wurde das aus der Datei wiederhergestellte Objekt als Zeichenfolge angezeigt! Wir haben also alle Probleme erfolgreich gelöst :) Das Thema Serialisierung und Deserialisierung scheint einfach, aber wie Sie sehen, waren die Lektionen langwierig. Und es gibt noch so viel mehr, was wir noch nicht abgedeckt haben! Bei der Verwendung jeder dieser Schnittstellen gibt es noch viele Feinheiten. Damit Ihr Gehirn jedoch nicht durch die Fülle an neuen Informationen explodiert, liste ich kurz noch ein paar weitere wichtige Punkte auf und gebe Ihnen Links zu weiterführender Lektüre. Was müssen Sie sonst noch wissen? Achten Sie zunächst während der Serialisierung (unabhängig davon, ob Sie Serializable
oder verwenden) auf Variablen. Wenn Sie verwenden , werden diese Felder überhaupt nicht serialisiert (und dementsprechend ändern sich ihre Werte nicht, da Felder zur Klasse und nicht zum Objekt gehören). Aber wenn Sie verwendenExternalizable
static
Serializable
static
Externalizable
, steuern Sie den Prozess selbst, sodass Sie sie technisch gesehen serialisieren könnten. Wir empfehlen dies jedoch nicht, da dies wahrscheinlich zu vielen subtilen Fehlern führt. Zweitens sollten Sie auch auf Variablen mit dem Modifikator achten final
. Wenn Sie verwenden Serializable
, werden sie wie üblich serialisiert und deserialisiert, aber wenn Sie verwenden Externalizable
, ist es unmöglich, eine final
Variable zu deserialisieren ! Der Grund ist einfach: Alle final
Felder werden initialisiert, wenn der Standardkonstruktor aufgerufen wird – danach kann ihr Wert nicht mehr geändert werden. Um Objekte mit final
Feldern zu serialisieren, verwenden Sie daher die von bereitgestellte Standardserialisierung Serializable
. Drittens : Wenn Sie die Vererbung verwenden, erben alle Nachkommenklassen einige davonExternalizable
Die Klasse muss auch über Standardkonstruktoren verfügen. Hier ist ein Link zu einem guten Artikel über Serialisierungsmechanismen:
Bis zum nächsten Mal! :) :)
GO TO FULL VERSION