Serializable
deed zijn werk, en wat is er niet zo geweldig aan de automatische implementatie van het hele proces? En de voorbeelden die we bekeken, waren ook ongecompliceerd. Wat is het probleem? Waarom hebben we een andere interface nodig voor in wezen dezelfde taken? Het feit is dat het Serializable
verschillende tekortkomingen heeft. We sommen er enkele op:
-
Prestatie. De
Serializable
interface heeft veel voordelen, maar hoge prestaties horen daar duidelijk niet bij.Ten eerste
Serializable
genereert 's interne implementatie een grote hoeveelheid service-informatie en allerlei tijdelijke gegevens.Ten tweede,
Serializable
vertrouwt op de Reflection API (u hoeft hier nu niet diep op in te gaan; u kunt op uw gemak meer lezen, als u geïnteresseerd bent). Met dit ding kun je de schijnbaar onmogelijke dingen in Java doen: verander bijvoorbeeld de waarden van privévelden. CodeGym heeft een uitstekend artikel over de Reflection API . Daar kun je erover lezen. -
Flexibiliteit. We hebben geen controle over het serialisatie-deserialisatieproces wanneer we de
Serializable
interface gebruiken.Aan de ene kant is het erg handig, want als we ons niet echt zorgen maken over de prestaties, lijkt het prettig om geen code te hoeven schrijven. Maar wat als we echt enkele van onze eigen functies moeten toevoegen (we zullen hieronder een voorbeeld geven) aan de serialisatielogica?
Kortom, alles wat we hebben om het proces te beheersen, is het
transient
sleutelwoord om bepaalde gegevens uit te sluiten. Dat is het. Dat is onze hele gereedschapskist :/ -
Beveiliging. Dit item vloeit deels voort uit het vorige item.
We hebben hier nog niet veel tijd over nagedacht, maar wat als sommige informatie in je klas niet bedoeld is voor nieuwsgierige ogen en oren van anderen? Een eenvoudig voorbeeld is een wachtwoord of andere persoonlijke gebruikersgegevens, die in de wereld van vandaag door een heleboel wetten worden beheerst.
Als we gebruiken
Serializable
, kunnen we er eigenlijk niets aan doen. We serialiseren alles zoals het is.Maar als we het op de juiste manier doen, moeten we dit soort gegevens versleutelen voordat we ze naar een bestand schrijven of via een netwerk verzenden. Maar
Serializable
maakt dit niet mogelijk.
Externalizable
interface zouden gebruiken.
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 {
}
}
Zoals je kunt zien, hebben we belangrijke veranderingen! De belangrijkste ligt voor de hand: bij het implementeren van de Externalizable
interface moet u twee vereiste methoden implementeren: writeExternal()
enreadExternal()
. Zoals we eerder zeiden, ligt de verantwoordelijkheid voor serialisatie en deserialisatie bij de programmeur. Maar nu kunt u het probleem van geen controle over het proces oplossen! Het hele proces wordt rechtstreeks door u geprogrammeerd. Dit maakt natuurlijk een veel flexibeler mechanisme mogelijk. Bovendien is het probleem met de beveiliging opgelost. Zoals je kunt zien, heeft onze klas een veld met persoonlijke gegevens dat niet onversleuteld kan worden opgeslagen. Nu kunnen we eenvoudig code schrijven die aan deze beperking voldoet. We kunnen bijvoorbeeld twee eenvoudige privémethoden aan onze klasse toevoegen om gevoelige gegevens te versleutelen en te ontsleutelen. We schrijven de gegevens naar het bestand en lezen deze in versleutelde vorm uit het bestand. De rest van de gegevens wordt geschreven en gelezen zoals het is :) Als resultaat ziet onze klas er ongeveer zo uit:
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;
}
}
We hebben twee methoden geïmplementeerd die dezelfde ObjectOutput
en ObjectInput
dezelfde parameters gebruiken die we al hebben ontmoet in de les over Serializable
. Op het juiste moment versleutelen of ontsleutelen we de vereiste gegevens en gebruiken we de versleutelde gegevens om ons object te serialiseren. Laten we eens kijken hoe dit er in de praktijk uitziet:
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 de methoden encryptString()
en decryptString()
hebben we specifiek console-uitvoer toegevoegd om de vorm te verifiëren waarin de geheime gegevens worden geschreven en gelezen. De bovenstaande code gaf de volgende regel weer: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh De codering is gelukt! De volledige inhoud van het bestand ziet er als volgt uit: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Laten we nu proberen onze deserialisatielogica te gebruiken.
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();
}
}
Nou, niets lijkt hier ingewikkeld. Het zou moeten werken! We voeren het uit en krijgen... Uitzondering in thread "main" java.io.InvalidClassException: UserInfo; geen geldige constructor Oeps! :( Blijkbaar is het niet zo eenvoudig! Het deserialisatiemechanisme veroorzaakte een uitzondering en eiste dat we een standaardconstructor zouden maken. Ik vraag me af waarom. Met Serializable
, konden we het zonder doen... :/ Hier zijn we nog een belangrijke nuance tegengekomen. verschil tussen Serializable
en Externalizable
ligt niet alleen in de 'uitgebreide' toegang van de programmeur en de mogelijkheid om het proces flexibeler te besturen, maar ook in het proces zelf. Vooral in het deserialisatiemechanisme .Serializable
, wordt eenvoudigweg geheugen toegewezen aan het object en vervolgens worden waarden uit de stream gelezen en gebruikt om de velden van het object in te stellen. Als we gebruiken Serializable
, wordt de constructor van het object niet aangeroepen! Al het werk gebeurt door middel van reflectie (de Reflection API, die we in de vorige les kort hebben genoemd). Met Externalizable
is het deserialisatiemechanisme anders. De standaardconstructor wordt eerst aangeroepen. Pas daarna wordt de methode UserInfo
van het gecreëerde object readExternal()
aangeroepen. Het is verantwoordelijk voor het instellen van de velden van het object. Daarom moet elke klasse die de Externalizable
interface implementeert een standaardconstructor hebben . Laten we er een aan onze UserInfo
klas toevoegen en de code opnieuw uitvoeren:
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();
}
}
Console-uitvoer: Paul Piper's paspoortgegevens UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper's paspoortgegevens' } Dat is iets heel anders! Eerst werd de gedecodeerde string met geheime informatie op de console weergegeven. Vervolgens werd het object dat we uit het bestand hadden hersteld, weergegeven als een string! We hebben dus alle problemen met succes opgelost :) Het onderwerp serialisatie en deserialisatie lijkt eenvoudig, maar zoals u kunt zien, zijn de lessen lang geweest. En er is zoveel meer dat we niet hebben behandeld! Er zijn nog steeds veel subtiliteiten bij het gebruik van elk van deze interfaces. Maar om te voorkomen dat je brein explodeert door overmatige nieuwe informatie, zal ik kort een paar belangrijke punten opsommen en je links geven naar aanvullende lectuur. Dus, wat moet je nog meer weten? Ten eerste , let tijdens serialisatie (ongeacht of u Serializable
of gebruikt Externalizable
) op static
variabelen. Wanneer u gebruikt Serializable
, zijn deze velden helemaal niet geserialiseerd (en dienovereenkomstig veranderen hun waarden niet, omdat static
velden tot de klasse behoren, niet tot het object). Maar wanneer je gebruiktExternalizable
, je beheert het proces zelf, dus technisch gezien zou je ze kunnen serialiseren. Maar we raden het niet aan, omdat het waarschijnlijk veel subtiele bugs zal veroorzaken. Ten tweede moet u ook aandacht besteden aan variabelen met de final
modifier. Wanneer u gebruikt Serializable
, zijn ze zoals gewoonlijk geserialiseerd en gedeserialiseerd, maar wanneer u gebruikt Externalizable
, is het onmogelijk om een final
variabele te deserialiseren ! De reden is eenvoudig: alle final
velden worden geïnitialiseerd wanneer de standaardconstructor wordt aangeroepen — daarna kan hun waarde niet meer worden gewijzigd. Om objecten met final
velden te serialiseren, gebruikt u daarom de standaardserialisatie van Serializable
. Ten derde , wanneer u overerving gebruikt, alle afstammingsklassen die er een aantal ervenExternalizable
klasse moet ook standaard constructors hebben. Hier is een link naar een goed artikel over serialisatiemechanismen:
Tot de volgende keer! :)
GO TO FULL VERSION