Serializable
gjorde sitt jobb, och vad är inte att älska med den automatiska implementeringen av hela processen? Och exemplen vi tittade på var också okomplicerade. Så vad är problemet? Varför behöver vi ett annat gränssnitt för i huvudsak samma uppgifter? Faktum är att det Serializable
har flera brister. Vi listar några av dem:
-
Prestanda. Gränssnittet
Serializable
har många fördelar, men hög prestanda är uppenbarligen inte en av dem.För det första
Serializable
genererar den interna implementeringen en stor mängd serviceinformation och alla typer av temporär data.För det andra,
Serializable
förlitar sig på Reflection API (du behöver inte dyka djupt på detta just nu, du kan läsa mer när du vill, om du är intresserad). Denna sak låter dig göra de till synes omöjliga sakerna i Java: till exempel ändra värden för privata fält. CodeGym har en utmärkt artikel om Reflection API . Du kan läsa om det där. -
Flexibilitet. Vi kontrollerar inte serialisering-deserialiseringsprocessen när vi använder gränssnittet
Serializable
.Å ena sidan är det väldigt bekvämt, för om vi inte är särskilt bekymrade över prestanda, så verkar det skönt att slippa skriva kod. Men vad händer om vi verkligen behöver lägga till några av våra egna funktioner (vi ger ett exempel nedan) till serialiseringslogiken?
I grund och botten är allt vi behöver för att kontrollera processen nyckelordet
transient
för att utesluta vissa data. Det är allt. Det är hela vår verktygslåda :/ -
Säkerhet. Denna post härrör delvis från föregående post.
Vi har inte ägnat så mycket tid åt att tänka på detta tidigare, men tänk om viss information i din klass inte är avsedd för andras nyfikna ögon och öron? Ett enkelt exempel är ett lösenord eller annan personlig användardata, som i dagens värld styrs av en massa lagar.
Om vi använder
Serializable
, kan vi egentligen inte göra något åt det. Vi serialiserar allt som det är.Men om vi gör det på rätt sätt måste vi kryptera den här typen av data innan vi skriver den till en fil eller skickar den över ett nätverk. Men
Serializable
gör inte detta möjligt.
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 {
}
}
Som ni ser har vi betydande förändringar! Den viktigaste är uppenbar: när du implementerar gränssnittet Externalizable
måste du implementera två nödvändiga metoder: writeExternal()
ochreadExternal()
. Som vi sa tidigare kommer ansvaret för serialisering och deserialisering att ligga hos programmeraren. Men nu kan du lösa problemet med ingen kontroll över processen! Hela processen programmeras direkt av dig. Naturligtvis tillåter detta en mycket mer flexibel mekanism. Dessutom är problemet med säkerhet löst. Som du kan se har vår klass ett persondatafält som inte kan lagras okrypterat. Nu kan vi enkelt skriva kod som uppfyller denna begränsning. Till exempel kan vi lägga till två enkla privata metoder till vår klass för att kryptera och dekryptera känslig data. Vi kommer att skriva data till filen och läsa den från filen i krypterad form. Resten av informationen kommer att skrivas och läsas som den är :) Som ett resultat ser vår klass ut ungefär så här:
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;
}
}
Vi implementerade två metoder som använder samma ObjectOutput
och ObjectInput
parametrar som vi redan träffade på lektionen om Serializable
. I rätt ögonblick krypterar eller dekrypterar vi den nödvändiga informationen, och vi använder den krypterade informationen för att serialisera vårt objekt. Låt oss se hur det här ser ut i praktiken:
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();
}
}
I metoderna encryptString()
och decryptString()
har vi specifikt lagt till konsolutdata för att verifiera i vilken form de hemliga data kommer att skrivas och läsas. Koden ovan visade följande rad: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Krypteringen lyckades! Det fullständiga innehållet i filen ser ut så här: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Låt oss nu försöka använda vår deserialiseringslogik.
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();
}
}
Nåväl, inget verkar komplicerat här. Det borde fungera! Vi kör det och får... Undantag i tråden "huvud" java.io.InvalidClassException: UserInfo; ingen giltig konstruktor Hoppsan! :( Tydligen är det inte så lätt! Deserialiseringsmekanismen gjorde ett undantag och krävde att vi skulle skapa en standardkonstruktör. Jag undrar varför. Med , Serializable
klarade vi oss utan en... :/ Här har vi stött på en annan viktig nyans. skillnaden mellan Serializable
och Externalizable
ligger inte bara i programmerarens "utökade" åtkomst och förmågan att mer flexibelt styra processen, utan också i själva processen. Framför allt i deserialiseringsmekanismen . När du använderSerializable
, minne tilldelas helt enkelt för objektet, och sedan läses värden från strömmen och används för att ställa in objektets fält. Om vi använder Serializable
, anropas inte objektets konstruktor! Allt arbete sker genom reflektion (Reflection API, som vi kort nämnde i förra lektionen). Med Externalizable
är deserialiseringsmekanismen annorlunda. Standardkonstruktorn anropas först. Först efter det anropas det skapade UserInfo
objektets readExternal()
metod. Den ansvarar för att ställa in objektets fält. Det är därför varje klass som implementerar Externalizable
gränssnittet måste ha en standardkonstruktor . Låt oss lägga till en till vår UserInfo
klass och köra koden igen:
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();
}
}
Konsolutgång: Paul Pipers passdata UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Pipers passdata' } Nu är det något helt annat! Först visades den dekrypterade strängen med hemlig information på konsolen. Sedan visades objektet vi hämtade från filen som en sträng! Så vi har framgångsrikt löst alla problem :) Ämnet serialisering och deserialisering verkar enkelt, men som du kan se har lektionerna varit långa. Och det finns så mycket mer vi inte har täckt! Det finns fortfarande många subtiliteter involverade när du använder vart och ett av dessa gränssnitt. Men för att undvika att din hjärna exploderar från överdriven ny information, ska jag kort lista några fler viktiga punkter och ge dig länkar till ytterligare läsning. Så, vad mer behöver du veta? Först , under serialisering (oavsett om du använder Serializable
eller Externalizable
), var uppmärksam på static
variabler. När du använder Serializable
serialiseras dessa fält inte alls (och följaktligen ändras inte deras värden, eftersom static
fält tillhör klassen, inte objektet). Men när du använderExternalizable
, kontrollerar du processen själv, så tekniskt sett kan du serialisera dem. Men vi rekommenderar det inte, eftersom det sannolikt kommer att skapa massor av subtila buggar. För det andra bör du också vara uppmärksam på variabler med final
modifieraren. När du använder Serializable
, serialiseras de och deserialiseras som vanligt, men när du använder Externalizable
, är det omöjligt att deserialisera en final
variabel ! Anledningen är enkel: alla final
fält initieras när standardkonstruktorn anropas - efter det kan deras värde inte ändras. För att serialisera objekt som har final
fält, använd därför standardserialiseringen som tillhandahålls av Serializable
. För det tredje , när du använder nedärvning, alla avkomliga klasser som ärver någraExternalizable
klass måste också ha standardkonstruktorer. Här är länk till bra artikel om serialiseringsmekanismer:
Tills nästa gång! :)