JavaRush /Blog Java /Random-PL /Zewnętrzny interfejs w Javie

Zewnętrzny interfejs w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy kontynuować nasze wprowadzenie do serializacji i deserializacji obiektów Java. Na ostatnim wykładzie zapoznaliśmy się z interfejsem znacznika Serializable , przyjrzeliśmy się przykładom jego użycia, a także dowiedzieliśmy się, jak kontrolować proces serializacji za pomocą słowa kluczowego transient . Cóż, „zarządzanie procesem” to oczywiście mocne słowo. Mamy jedno słowo kluczowe, jeden identyfikator wersji i to w zasadzie wszystko. Pozostała część procesu jest „wbudowana” w Javę i nie ma do niej dostępu. Z punktu widzenia wygody jest to oczywiście dobre. Ale programista w swojej pracy powinien skupiać się nie tylko na własnym komforcie, prawda? :) Należy wziąć pod uwagę inne czynniki. Dlatego Serializable nie jest jedynym narzędziem do serializacji-deserializacji w Javie. Dziś zapoznamy się z interfejsem Externalizable . Ale jeszcze zanim przystąpiliśmy do jego studiowania, możesz zadać rozsądne pytanie: dlaczego potrzebujemy innego narzędzia? SerializableDałem sobie radę ze swoją pracą, a automatyczna realizacja całego procesu nie może się nie cieszyć. Przykłady, które sprawdziliśmy, również nie były skomplikowane. Więc o co chodzi? Po co inny interfejs do zasadniczo tego samego zadania? Faktem jest, że Serializablema wiele wad. Wymieńmy niektóre z nich:
  1. Wydajność. Interfejs ma Serializablewiele zalet, ale wysoka wydajność z pewnością nie jest jedną z nich.

Przedstawiamy interfejs możliwy do uzewnętrznienia — 2

Po pierwsze , wewnętrzny mechanizm Serializablegeneruje w trakcie pracy dużą ilość informacji serwisowych oraz różnego rodzaju dane tymczasowe.
Po drugie (nie musisz w to teraz wchodzić i czytać w wolnej chwili, jeśli jesteś zainteresowany), praca Serializableopiera się na wykorzystaniu API Reflection. To urządzenie pozwala robić rzeczy, które w Javie wydawałyby się niemożliwe: na przykład zmieniać wartości pól prywatnych. JavaRush ma doskonały artykuł na temat Reflection API , możesz o nim przeczytać tutaj.

  1. Elastyczność. W ogóle nie kontrolujemy procesu serializacji-deserializacji podczas korzystania z platformy Serializable.

    Z jednej strony jest to bardzo wygodne, bo jeśli nie zależy nam specjalnie na wydajności, to możliwość niepisania kodu wydaje się wygodna. Ale co, jeśli naprawdę potrzebujemy dodać kilka własnych funkcji (przykład jednej z nich znajdziesz poniżej) do logiki serializacji?

    Zasadniczo wszystko, co musimy kontrolować, to słowo kluczowe transientwykluczające niektóre dane i to wszystko. Coś w rodzaju „zestawu narzędzi” :/

  2. Bezpieczeństwo. Punkt ten częściowo wynika z poprzedniego.

    Nie zastanawialiśmy się wcześniej nad tym zbyt wiele, ale co, jeśli niektóre informacje na twoich zajęciach nie są przeznaczone dla „uszu innych ludzi” (a dokładniej oczu)? Prostym przykładem jest hasło lub inne dane osobowe użytkownika, które we współczesnym świecie regulowane są przez szereg przepisów.

    Używając Serializable, właściwie nic nie możemy z tym zrobić. Serializujemy wszystko tak, jak jest.

    Ale w dobrym tego słowa znaczeniu musimy szyfrować tego rodzaju dane przed zapisaniem ich do pliku lub przesłaniem przez sieć. Ale Serializablenie daje takiej możliwości.

Przedstawiamy interfejs możliwy do uzewnętrznienia — 3Cóż, przyjrzyjmy się w końcu, jak wyglądałaby klasa przy użyciu metody 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 {

   }
}
Jak widać, dokonaliśmy znaczących zmian! Główna jest oczywista: implementując interfejs, Externalizablemusisz wdrożyć dwie obowiązkowe metody - writeExternal()i readExternal(). Jak powiedzieliśmy wcześniej, cała odpowiedzialność za serializację i deserializację będzie spoczywać na programiście. Jednak teraz możesz rozwiązać problem braku kontroli nad tym procesem! Cały proces programujesz bezpośrednio przez Ciebie, co oczywiście tworzy znacznie bardziej elastyczny mechanizm. Ponadto rozwiązano również problem bezpieczeństwa. Jak widać, w naszej klasie mamy pole: dane osobowe, których nie można przechowywać w postaci niezaszyfrowanej. Teraz możemy łatwo napisać kod spełniający to ograniczenie. Na przykład dodaj do naszej klasy dwie proste metody prywatne służące do szyfrowania i deszyfrowania tajnych danych. Zapiszemy je do pliku i odczytamy z pliku w postaci zaszyfrowanej. A resztę danych będziemy pisać i czytać tak jak jest :) W rezultacie nasza klasa będzie 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, które wykorzystują te same parametry ObjectOutput outi jako ObjectInput, z którymi spotkaliśmy się już w wykładzie na temat Serializable. W odpowiednim momencie szyfrujemy lub deszyfrujemy niezbędne dane i w tej formie wykorzystujemy je do serializacji naszego obiektu. Zobaczmy jak to będzie 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("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
W metodach encryptString()i decryptString()specjalnie dodaliśmy dane wyjściowe do konsoli, aby sprawdzić, w jakiej formie tajne dane zostaną zapisane i odczytane. Powyższy kod wyświetla na konsoli następujący wiersz: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Szyfrowanie powiodło się! Pełna zawartość pliku wygląda następująco: ¬н sr UserInfoГ!}thoughџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Teraz spróbujmy użyć napisanej przez nas 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óż, nie wydaje się, żeby było tu coś skomplikowanego, powinno działać! Uruchommy... Wyjątek w wątku „main” java.io.InvalidClassException: UserInfo; brak prawidłowego konstruktora Przedstawiamy interfejs, który można uzewnętrznić — 4 Ups :( Okazało się, że to nie jest takie proste! Mechanizm deserializacji zgłosił wyjątek i wymagał od nas stworzenia domyślnego konstruktora. Ciekawe dlaczego? SerializableDaliśmy radę bez niego... :/ Tu dochodzimy do kolejnego ważnego niuansu Różnica między Serializablea Externalizablepolega nie tylko na „rozszerzonym” dostępie programisty i możliwości bardziej elastycznego zarządzania procesem, ale także na samym procesie. Przede wszystkim na mechanizmie deserializacji … Pamięć wykorzystywana Serializablejest po prostu przydzielane dla obiektu, po czym ze strumienia odczytywane są wartości, które wypełniają wszystkie jego pola. Jeżeli skorzystamy z tego Serializablekonstruktor obiektu nie zostanie wywołany! Cała praca odbywa się poprzez odbicie (Reflection API, o którym pokrótce wspominaliśmy w ostatnim wykład).W przypadku , Externalizablemechanizm deserializacji będzie inny.Na początku wywoływany jest domyślny konstruktor.I dopiero wtedy UserInfona utworzonym obiekcie wywoływana jest metoda readExternal(), która odpowiedzialna jest za wypełnienie pól obiektu,czyli dlaczego każda klasa implementująca interfejs Externalizablemusi mieć konstruktora domyślnego . Dodajmy go do naszej klasy UserInfoi 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 z konsoli: Dane paszportowe Iwana Iwanowa UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Dane paszportowe Iwana Iwanowa'} Zupełnie inna sprawa! Najpierw odszyfrowany ciąg znaków z tajnymi danymi został przesłany do konsoli, a następnie nasz obiekt został odzyskany z pliku w formacie ciągu znaków! W ten sposób pomyślnie rozwiązaliśmy wszystkie problemy :) Temat serializacji i deserializacji wydaje się prosty, jednak jak widać nasze wykłady okazały się długie. I to nie wszystko! Subtelności w korzystaniu z każdego z tych interfejsów jest znacznie więcej, ale aby teraz Wasz mózg nie eksplodował od natłoku nowych informacji, pokrótce wymienię kilka ważniejszych punktów i podam linki do dodatkowej lektury. Co jeszcze musisz wiedzieć? Po pierwsze , serializując (nie ma znaczenia, czy użyjesz Serializablelub Externalizable), zwróć uwagę na zmienne static. Kiedy są używane, Serializablepola te w ogóle nie są serializowane (i odpowiednio ich wartość się nie zmienia, ponieważ staticpola należą do klasy, a nie obiektu). Ale używając go, Externalizablesam kontrolujesz proces, więc technicznie jest to możliwe. Ale nie jest to zalecane, ponieważ jest to obarczone subtelnymi błędami. Po drugie należy zwrócić uwagę również na zmienne z modyfikatorem final. Kiedy są używane, Serializablesą one serializowane i deserializowane jak zwykle, ale kiedy są używane, nie da się Externalizabledeserializować finalzmiennej ! Powód jest prosty: wszystkie finalpola są inicjowane w momencie wywołania konstruktora domyślnego i po tym czasie nie można zmienić ich wartości. Dlatego, aby serializować obiekty zawierające final-pola, użyj standardowej serializacji poprzez Serializable. Po trzecie , podczas korzystania z dziedziczenia wszystkie klasy dziedziczące pochodzące z jakiejś Externalizableklasy muszą także mieć konstruktory domyślne. Oto kilka linków do dobrych artykułów na temat mechanizmów serializacji: Do zobaczenia! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION