Serializable
Ich habe meine Arbeit gemeistert und die automatische Umsetzung des gesamten Prozesses kann mich nur freuen. Auch die Beispiele, die wir uns angesehen haben, waren nicht kompliziert. Also, was ist der Deal? Warum eine andere Schnittstelle für im Wesentlichen dieselbe Aufgabe? Tatsache ist, dass Serializable
es eine Reihe von Nachteilen hat. Lassen Sie uns einige davon auflisten:
-
Leistung. Die Schnittstelle hat
Serializable
viele Vorteile, aber hohe Leistung gehört eindeutig nicht dazu.
Erstens generiert der interne Mechanismus Serializable
während des Betriebs eine große Menge an Serviceinformationen und verschiedene Arten temporärer Daten.
Zweitens (Sie müssen jetzt nicht näher darauf eingehen und es in Ruhe lesen, wenn Sie interessiert sind) Serializable
basiert die Arbeit auf der Verwendung der Reflection API. Mit diesem Gerät können Sie Dinge tun, die in Java unmöglich erscheinen würden: zum Beispiel die Werte privater Felder ändern. JavaRush hat einen hervorragenden Artikel über die Reflection API , den Sie hier lesen können.
-
Flexibilität. Wir kontrollieren den Serialisierungs-Deserialisierungsprozess überhaupt nicht, wenn wir die verwenden
Serializable
.Einerseits ist das sehr praktisch, denn wenn uns die Leistung nicht wirklich am Herzen liegt, erscheint die Möglichkeit, keinen Code zu schreiben, praktisch. Aber was ist, wenn wir der Serialisierungslogik wirklich einige unserer eigenen Funktionen hinzufügen müssen (ein Beispiel dafür finden Sie weiter unten)?
Im Wesentlichen müssen wir zur Steuerung des Prozesses nur ein Schlüsselwort eingeben,
transient
um einige Daten auszuschließen, und das ist alles. So etwas wie ein „Toolkit“ :/ -
Sicherheit. Dieser Punkt folgt teilweise aus dem vorherigen.
Wir haben darüber noch nicht viel nachgedacht, aber was ist, wenn einige Informationen in Ihrem Unterricht nicht für „die Ohren“ (genauer: die Augen) anderer Leute gedacht sind? Ein einfaches Beispiel ist ein Passwort oder andere persönliche Benutzerdaten, die in der modernen Welt durch eine Reihe von Gesetzen geregelt sind.
Mit
Serializable
können wir eigentlich nichts dagegen tun. Wir serialisieren alles so wie es ist.Aber im positiven Sinne müssen wir diese Art von Daten verschlüsseln, bevor wir sie in eine Datei schreiben oder über das Netzwerk übertragen. Aber
Serializable
es gibt diese Gelegenheit nicht.
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 {
}
}
Wie Sie sehen, haben wir erhebliche Änderungen vorgenommen! Der wichtigste liegt auf der Hand: Bei der Implementierung einer Schnittstelle Externalizable
müssen Sie zwei obligatorische Methoden implementieren – writeExternal()
und readExternal()
. Wie bereits erwähnt, liegt die gesamte Verantwortung für die Serialisierung und Deserialisierung beim Programmierer. Jetzt können Sie jedoch das Problem der mangelnden Kontrolle über diesen Prozess lösen! Der gesamte Prozess wird direkt von Ihnen programmiert, wodurch natürlich ein viel flexiblerer Mechanismus entsteht. Darüber hinaus ist auch das Sicherheitsproblem gelöst. Wie Sie sehen, haben wir in unserer Klasse ein Feld: personenbezogene Daten, die nicht unverschlüsselt gespeichert werden können. Jetzt können wir problemlos Code schreiben, der diese Einschränkung erfüllt. Fügen Sie unserer Klasse beispielsweise zwei einfache private Methoden zum Verschlüsseln und Entschlüsseln geheimer Daten hinzu. Wir schreiben sie in eine Datei und lesen sie verschlüsselt aus der Datei. Und wir werden den Rest der Daten so schreiben und lesen, wie sie sind :) Als Ergebnis wird unsere Klasse in etwa so aussehen:
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 ObjectOutput out
und als Parameter verwenden ObjectInput
, die wir bereits in der Vorlesung über kennengelernt haben Serializable
. Zum richtigen Zeitpunkt verschlüsseln oder entschlüsseln wir die notwendigen Daten und verwenden sie in dieser Form zur Serialisierung unseres Objekts. Mal sehen, wie das in der Praxis aussehen wird:
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();
}
}
In den Methoden encryptString()
und decryptString()
haben wir speziell eine Ausgabe in die Konsole hinzugefügt, um zu prüfen, in welcher Form die geheimen Daten geschrieben und gelesen werden. Der obige Code gibt die folgende Zeile an die Konsole aus: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Verschlüsselung erfolgreich! Der vollständige Inhalt der Datei sieht folgendermaßen aus: ¬í sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Versuchen wir nun, die von uns geschriebene 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 es nichts Kompliziertes zu geben, es sollte funktionieren! Lassen Sie uns ausführen ... Ausnahme im Thread „main“ java.io.InvalidClassException: UserInfo; Kein gültiger Konstruktor Hoppla :( Es stellte sich heraus, dass es nicht so einfach war! Der Deserialisierungsmechanismus löste eine Ausnahme aus und erforderte, dass wir einen Standardkonstruktor erstellten. Ich frage mich, warum? Serializable
Wir kamen ohne aus... :/ Hier kommen wir zu einer weiteren wichtigen Nuance . Der Unterschied zwischen Serializable
und Externalizable
liegt nicht nur im „erweiterten“ Zugriff für den Programmierer und der Möglichkeit, den Prozess flexibler zu verwalten, sondern auch im Prozess selbst. Erstens im Deserialisierungsmechanismus ... Wenn Serializable
Speicher verwendet wird, ist er einfach für ein Objekt zugewiesen, woraufhin Werte aus dem Stream gelesen werden, die alle seine Felder füllen. Wenn wir verwenden Serializable
, wird der Objektkonstruktor nicht aufgerufen! Die gesamte Arbeit wird durch Reflektion (Reflection API, die wir im letzten Abschnitt kurz erwähnt haben) erledigt Vorlesung). Im Fall von Externalizable
wird der Deserialisierungsmechanismus anders sein. Zu Beginn wird der Standardkonstruktor aufgerufen. Und erst dann UserInfo
wird die Methode des erstellten Objekts aufgerufen readExternal()
, die für das Ausfüllen der Felder des Objekts verantwortlich ist. Das heißt Warum muss jede Klasse, die die Schnittstelle implementiert, Externalizable
einen Standardkonstruktor haben ? Fügen wir es unserer Klasse hinzu UserInfo
und führen Sie 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 Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Passdaten von Ivan Ivanov'} Eine ganz andere Sache! Zuerst wurde der entschlüsselte String mit geheimen Daten an die Konsole ausgegeben und dann wurde unser Objekt aus der Datei im String-Format wiederhergestellt! So haben wir alle Probleme erfolgreich gelöst :) Das Thema Serialisierung und Deserialisierung scheint einfach zu sein, aber wie Sie sehen, waren unsere Vorträge langwierig. Und das ist nicht alles! Bei der Verwendung jeder dieser Schnittstellen gibt es noch viele weitere Feinheiten, aber damit Ihr Gehirn jetzt nicht vor der Menge an neuen Informationen explodiert, werde ich noch kurz einige weitere wichtige Punkte auflisten und Links zu weiterführender Lektüre bereitstellen. Was müssen Sie sonst noch wissen? Achten Sie beim Serialisieren (egal ob Sie oder verwenden) zunächst auf die Variablen . Bei Verwendung werden diese Felder überhaupt nicht serialisiert (und ihr Wert ändert sich dementsprechend nicht, da die Felder zur Klasse und nicht zum Objekt gehören). Aber wenn Sie es verwenden, steuern Sie den Prozess selbst, sodass dies technisch möglich ist. Es wird jedoch nicht empfohlen, da dies mit subtilen Fehlern behaftet ist. Zweitens sollte auch auf Variablen mit dem Modifikator geachtet werden . Wenn sie verwendet werden, werden sie wie üblich serialisiert und deserialisiert, aber wenn sie verwendet werden, ist es unmöglich, eine Variable zu deserialisieren ! Der Grund ist einfach: Alle -Felder werden initialisiert, wenn der Standardkonstruktor aufgerufen wird, und danach kann ihr Wert nicht mehr geändert werden. Verwenden Sie daher zum Serialisieren von Objekten, die -Felder enthalten, die Standard-Serialisierung über . Drittens müssen bei Verwendung der Vererbung alle erbenden Klassen, die von einer Klasse abstammen, auch über Standardkonstruktoren verfügen. Hier sind einige Links zu guten Artikeln über Serialisierungsmechanismen: Serializable
Externalizable
static
Serializable
static
Externalizable
final
Serializable
Externalizable
final
final
final
Serializable
Externalizable
Auf Wiedersehen! :) :)
GO TO FULL VERSION