CodeGym/Blog Java/Random-PL/Zewnętrzny interfejs w Javie
Autor
Volodymyr Portianko
Java Engineer at Playtika

Zewnętrzny interfejs w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy dalej poznawać serializację i deserializację obiektów Javy. Na ostatniej lekcji poznaliśmy interfejs znacznika Serializable , przejrzeliśmy przykłady jego użycia, a także dowiedzieliśmy się, jak można wykorzystać słowo kluczowe transient do sterowania procesem serializacji. Cóż, stwierdzenie, że „kontrolujemy proces” może być przesadą. Mamy jedno słowo kluczowe, jeden identyfikator wersji i to wszystko. Reszta procesu jest ukryta w Javie i nie mamy do niej dostępu. Oczywiście pod względem wygody jest to dobre. Ale programista nie powinien kierować się tylko własnym komfortem, prawda? :) Istnieją inne czynniki, które należy wziąć pod uwagę. Dlatego serializowalnynie jest jedynym mechanizmem serializacji-deserializacji w Javie. Dzisiaj zapoznamy się z interfejsem Externalizable . Ale zanim zaczniemy to badać, możesz zadać sobie rozsądne pytanie: po co nam inny mechanizm? Serializablespełniło swoje zadanie, a czego nie można pokochać w automatycznej realizacji całego procesu? A przykłady, które oglądaliśmy, były również nieskomplikowane. Więc w czym problem? Dlaczego potrzebujemy innego interfejsu do zasadniczo tych samych zadań? Faktem jest, że Serializablema kilka wad. Wymieniamy niektóre z nich:
  1. Wydajność. Interfejs Serializablema wiele zalet, ale wysoka wydajność wyraźnie nie jest jedną z nich.

    Przedstawiamy interfejs z możliwością udostępniania — 2

    Po pierwsze, Serializable wewnętrzna implementacja generuje dużą ilość informacji o usługach i wszelkiego rodzaju danych tymczasowych.

    Po drugie, Serializable opiera się na Reflection API (nie musisz teraz zagłębiać się w ten temat; możesz przeczytać więcej w wolnym czasie, jeśli jesteś zainteresowany). Ta rzecz pozwala robić pozornie niemożliwe rzeczy w Javie: na przykład zmieniać wartości pól prywatnych. CodeGym ma doskonały artykuł o Reflection API . Można tam o tym przeczytać.

  2. Elastyczność. Nie kontrolujemy procesu serializacji-deserializacji, gdy używamy interfejsu Serializable.

    Z jednej strony jest to bardzo wygodne, ponieważ jeśli nie zależy nam szczególnie na wydajności, to miło jest nie musieć pisać kodu. Ale co, jeśli naprawdę musimy dodać niektóre z naszych własnych funkcji (podamy przykład poniżej) do logiki serializacji?

    Zasadniczo wszystko, co musimy kontrolować proces, to transientsłowo kluczowe, aby wykluczyć niektóre dane. Otóż ​​to. To cały nasz zestaw narzędzi :/

  3. Bezpieczeństwo. Ten element wynika częściowo z poprzedniego elementu.

    Nie spędzaliśmy dużo czasu na myśleniu o tym wcześniej, ale co, jeśli niektóre informacje w twojej klasie nie są przeznaczone dla wścibskich oczu i uszu innych? Prostym przykładem jest hasło lub inne dane osobowe użytkownika, które w dzisiejszym świecie rządzą się wieloma prawami.

    Jeśli używamy Serializable, tak naprawdę nie możemy nic z tym zrobić. Serializujemy wszystko tak, jak jest.

    Ale jeśli robimy to we właściwy sposób, musimy zaszyfrować tego rodzaju dane przed zapisaniem ich do pliku lub wysłaniem przez sieć. Ale Serializablenie umożliwia tego.

Przedstawiamy interfejs z możliwością udostępniania — 3Cóż, zobaczmy wreszcie, jak wyglądałaby klasa, gdybyśmy używali interfejsu 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 {

   }
}
Jak widać u nas spore zmiany! Główna jest oczywista: implementując interfejs Externalizable, musisz zaimplementować dwie wymagane metody: writeExternal()ireadExternal(). Jak powiedzieliśmy wcześniej, odpowiedzialność za serializację i deserializację będzie spoczywać na programiście. Ale teraz możesz rozwiązać problem braku kontroli nad procesem! Cały proces jest programowany bezpośrednio przez Ciebie. Oczywiście pozwala to na znacznie bardziej elastyczny mechanizm. Dodatkowo rozwiązany został problem z bezpieczeństwem. Jak widać, nasza klasa ma pole danych osobowych, które nie mogą być przechowywane w postaci niezaszyfrowanej. Teraz możemy łatwo napisać kod, który spełnia to ograniczenie. Na przykład możemy dodać do naszej klasy dwie proste prywatne metody szyfrowania i deszyfrowania wrażliwych danych. Dane zapiszemy do pliku i odczytamy z pliku w postaci zaszyfrowanej. Reszta danych zostanie zapisana i odczytana bez zmian :) W rezultacie nasza klasa wygląda mniej więcej tak:
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;
   }
}
Zaimplementowaliśmy dwie metody wykorzystujące te same parametry ObjectOutputi ObjectInputparametry, które poznaliśmy już na lekcji o Serializable. W odpowiednim momencie szyfrujemy lub deszyfrujemy wymagane dane, a zaszyfrowane dane wykorzystujemy do serializacji naszego obiektu. Zobaczmy, jak to wygląda w praktyce:
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();

   }
}
W metodach encryptString()i decryptString()specjalnie dodaliśmy dane wyjściowe konsoli, aby zweryfikować formę, w jakiej tajne dane będą zapisywane i odczytywane. Powyższy kod wyświetlał następujący wiersz: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Szyfrowanie powiodło się! Pełna zawartość pliku wygląda następująco: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Spróbujmy teraz użyć naszej logiki deserializacji.
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();

   }
}
Cóż, nic nie wydaje się tu skomplikowane. Powinno działać! Uruchamiamy go i otrzymujemy... Wyjątek w wątku „main” java.io.InvalidClassException: UserInfo; brak poprawnego konstruktora Przedstawiamy interfejs z możliwością udostępniania — 4 Ups! :( Najwyraźniej nie jest to takie proste! Mechanizm deserializacji rzucił wyjątek i zażądał stworzenia domyślnego konstruktora. Ciekawe dlaczego. Z Serializable, obeszliśmy się bez niego... :/ Tutaj napotkaliśmy kolejny ważny niuans. różnica między Serializablei Externalizabletkwi nie tylko w „rozszerzonym” dostępie programisty i możliwości bardziej elastycznego sterowania procesem, ale także w samym procesie. Przede wszystkim w mechanizmie deserializacji .Serializable, pamięć jest po prostu przydzielana dla obiektu, a następnie wartości są odczytywane ze strumienia i używane do ustawiania pól obiektu. Jeśli użyjemy Serializable, konstruktor obiektu nie jest wywoływany! Cała praca odbywa się poprzez refleksję (interfejs API Reflection, o którym pokrótce wspomnieliśmy w ostatniej lekcji). Z Externalizable, mechanizm deserializacji jest inny. Konstruktor domyślny jest wywoływany jako pierwszy. Dopiero potem wywoływana jest metoda utworzonego UserInfoobiektu readExternal(). Odpowiada za ustawienie pól obiektu. Dlatego każda klasa implementująca Externalizableinterfejs musi mieć konstruktora domyślnego . Dodajmy jeden do naszej UserInfoklasy i ponownie uruchommy kod:
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();
   }
}
Dane wyjściowe konsoli: dane paszportowe Paula Pipera UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Dane paszportowe Paula Pipera' } Teraz to coś zupełnie innego! Najpierw na konsoli wyświetlany był odszyfrowany ciąg znaków z tajnymi informacjami. Następnie obiekt, który odzyskaliśmy z pliku, został wyświetlony jako ciąg znaków! Tak więc pomyślnie rozwiązaliśmy wszystkie problemy :) Temat serializacji i deserializacji wydaje się prosty, ale jak widać, lekcje były długie. I jest o wiele więcej, których nie omówiliśmy! Korzystanie z każdego z tych interfejsów wciąż wiąże się z wieloma subtelnościami. Ale aby uniknąć eksplodowania twojego mózgu z powodu nadmiaru nowych informacji, pokrótce wymienię kilka ważniejszych punktów i podam linki do dodatkowych lektur. Co jeszcze musisz wiedzieć? Po pierwsze , podczas serializacji (niezależnie od tego, czy używasz Serializablelub Externalizable), zwróć uwagę na staticzmienne. Kiedy używasz Serializable, te pola w ogóle nie są serializowane (a zatem ich wartości się nie zmieniają, ponieważ staticpola należą do klasy, a nie do obiektu). Ale kiedy używaszExternalizable, sam kontrolujesz proces, więc technicznie możesz je serializować. Ale nie zalecamy tego, ponieważ może to spowodować wiele subtelnych błędów. Po drugie , należy również zwrócić uwagę na zmienne z finalmodyfikatorem. Kiedy używasz Serializable, są one serializowane i deserializowane jak zwykle, ale kiedy używasz Externalizable, nie można deserializować finalzmiennej ! Powód jest prosty: wszystkie finalpola są inicjalizowane w momencie wywołania konstruktora domyślnego — po tym nie można już zmienić ich wartości. W związku z tym, aby serializować obiekty, które mają finalpola, użyj standardowej serializacji zapewnianej przez Serializable. Po trzecie , kiedy używasz dziedziczenia, wszystkie klasy potomne dziedziczą niektóreExternalizableklasa musi mieć również domyślne konstruktory. Oto link do dobrego artykułu o mechanizmach serializacji: Do następnego razu! :)
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy