CodeGym /Java Blog /Willekeurig /Externaliseerbare interface in Java
John Squirrels
Niveau 41
San Francisco

Externaliseerbare interface in Java

Gepubliceerd in de groep Willekeurig
Hoi! Vandaag gaan we verder met het leren kennen van serialisatie en deserialisatie van Java- objecten. In de laatste les hebben we de interface voor serialiseerbare markeringen leren kennen , voorbeelden van het gebruik ervan bekeken en ook geleerd hoe u het transient- trefwoord kunt gebruiken om het serialisatieproces te besturen. Welnu, zeggen dat we 'het proces beheersen' is misschien overdreven. We hebben één trefwoord, één versie-ID, en dat is het zo'n beetje. De rest van het proces is verborgen in Java en we hebben er geen toegang toe. Qua gemak is dit natuurlijk goed. Maar een programmeur moet zich toch niet alleen laten leiden door zijn of haar eigen comfort? :) Er zijn nog andere factoren waarmee u rekening moet houden. Daarom Serializableis niet het enige mechanisme voor serialisatie-deserialisatie in Java. Vandaag maken we kennis met de Externalizable -interface. Maar voordat we het gaan bestuderen, heb je misschien een redelijke vraag: waarom hebben we een ander mechanisme nodig? Serializabledeed 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 Serializableverschillende tekortkomingen heeft. We sommen er enkele op:
  1. Prestatie. De Serializableinterface heeft veel voordelen, maar hoge prestaties horen daar duidelijk niet bij.

    Introductie van de Externaliseerbare interface - 2

    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.

  2. Flexibiliteit. We hebben geen controle over het serialisatie-deserialisatieproces wanneer we de Serializableinterface 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 transientsleutelwoord om bepaalde gegevens uit te sluiten. Dat is het. Dat is onze hele gereedschapskist :/

  3. 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 Serializablemaakt dit niet mogelijk.

Introductie van de Externaliseerbare interface - 3Laten we eindelijk eens kijken hoe de klasse eruit zou zien als we de Externalizableinterface 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 Externalizableinterface 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 ObjectOutputen ObjectInputdezelfde 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 Introductie van de Externaliseerbare interface - 4 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 Serializableen Externalizableligt 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 Externalizableis het deserialisatiemechanisme anders. De standaardconstructor wordt eerst aangeroepen. Pas daarna wordt de methode UserInfovan het gecreëerde object readExternal()aangeroepen. Het is verantwoordelijk voor het instellen van de velden van het object. Daarom moet elke klasse die de Externalizableinterface implementeert een standaardconstructor hebben . Laten we er een aan onze UserInfoklas 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 Serializableof gebruikt Externalizable) op staticvariabelen. Wanneer u gebruikt Serializable, zijn deze velden helemaal niet geserialiseerd (en dienovereenkomstig veranderen hun waarden niet, omdat staticvelden 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 finalmodifier. 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 finalvelden worden geïnitialiseerd wanneer de standaardconstructor wordt aangeroepen — daarna kan hun waarde niet meer worden gewijzigd. Om objecten met finalvelden te serialiseren, gebruikt u daarom de standaardserialisatie van Serializable. Ten derde , wanneer u overerving gebruikt, alle afstammingsklassen die er een aantal ervenExternalizableklasse moet ook standaard constructors hebben. Hier is een link naar een goed artikel over serialisatiemechanismen: Tot de volgende keer! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION