JavaRush /Blog Java /Random-PL /Co ukrywa modyfikator przejściowy w Javie?
Анзор Кармов
Poziom 31
Санкт-Петербург

Co ukrywa modyfikator przejściowy w Javie?

Opublikowano w grupie Random-PL
Cześć! W dzisiejszym artykule przyjrzymy się modyfikatorowi przejściowemu w Javie. Porozmawiajmy o tym, dlaczego ten modyfikator jest potrzebny i jak poprawnie go używać. Iść! Co ukrywa modyfikator przejściowy w Javie - 1

Pamiętajmy o serializacji

Modyfikator transientjest używany w procesie serializacji i deserializacji obiektów. Porozmawiajmy więc najpierw krótko o tym. Co ukrywa modyfikator przejściowy w Javie - 2Załóżmy, że mamy jakiś obiekt i ma on pola, z których każde ma jakąś wartość. Wszystko to nazywa się stanem obiektu. Serializacja to konwersja stanu obiektu na sekwencję bajtów. Te bajty są zwykle przechowywane w jakimś pliku. Deserializacja jest procesem odwrotnym. Wyobraźmy sobie, że serializujemy obiekt w bajty i przechowujemy ten zestaw bajtów w jakimś pliku. Podczas deserializacji program potrzebuje:
  1. Odczytaj zestaw bajtów z pliku.
  2. Z tego zestawu bajtów skonstruuj obiekt początkowy i ustaw każde pole na wartość, jaką obiekt miał w momencie serializacji.
Kiedy może się to przydać? Na przykład, gdy chcemy, aby program zapisał swój stan podczas zamykania i przywrócił go przy następnym włączeniu. Po zamknięciu IntelliJ IDEA najprawdopodobniej przy następnym włączeniu będziesz mieć otwarte te same karty i klasy

Przypomnijmy sobie serializację w praktyce

Cóż, teraz spójrzmy na serializację w praktyce. Jeżeli chcesz lepiej zrozumieć temat polecamy zapoznać się z materiałem Serializacja i deserializacja w Javie . Cóż, w tym artykule przejdziemy od góry i przejdziemy od razu do przykładów. Załóżmy, że mamy klasę Userz zestawem pól, metodami pobierającymi i ustawiającymi oraz metodą toString:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private String password;

    public User() {}

    public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.birthDate = birthDate;
        this.login = login;
        this.password = password;
    }

    /*
        Геттеры, Сеттеры
     */

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", birthDate=" + birthDate +
                ", login='" + login + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
W przyszłości chcemy serializować obiekty tej klasy. Napiszmy metodę pobierającą obiekt Useri string path- ścieżkę do pliku, w którym będziemy zapisywać bajty:
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //utwórz 2 wątki, aby serializować obiekt i zapisać go w pliku
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем obiekt в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
Napiszemy także metodę deserializacji. Metoda pobiera ciąg znaków path(ścieżkę do pliku, z którego obiekt zostanie „załadowany”) i zwraca obiekt typu User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

        //создаем 2 потока для десериализации obiektа из plik
        fileInputStream = new FileInputStream(path);
        objectInputStream = new ObjectInputStream(fileInputStream);

        //загружаем obiekt из plik
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
Wszystkie narzędzia są gotowe do użycia. Czas podzielić bajty na atomy . Napiszmy metodę main, w której utworzymy obiekt klasy Useri serializujemy go. Następnie załadujemy go i porównamy z tym, co było pierwotnie:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до plik
    final String path = "/home/zor/user.ser";

    // tworzymy nasz obiekt
    User user = new User();
    user.setFirstName("Stefan");
    user.setLastName("Smith");
    user.setEmail("ssmith@email.com");
    user.setBirthDate(LocalDate.of(1991, 7, 16));
    user.setLogin("ssmith");
    user.setPassword("gemma_arterton_4ever_in_my_heart91");

    System.out.println("Initial user: " + user + "\r\n");


    serialize(user, path);
    User loadedUser = deserialize(path);
    System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
Jeśli uruchomimy tę metodę, zobaczymy następujące dane wyjściowe:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Jak widać na wynikach, obiekty są identyczne. Jest jednak małe ale... I właśnie w tym miejscu w grę wchodzi hiszpański wstyd transient .

Modyfikator (wreszcie)transient

Czy ktoś był zdezorientowany faktem, że zapisaliśmy hasło użytkownika? Szczególnie takie hasło... Tak, tak, sami je wymyśliliśmy, ale mimo wszystko... Czasem zdarzają się sytuacje, że niektórych pól nie da się serializować, albo lepiej tego nie robić. W powyższym przykładzie chciałbym zapisać wszystkie pola oprócz hasła. Jak to osiągnąć? Odpowiedź: użyj modyfikatora transient. transientjest modyfikatorem określonym przed polem klasy (podobnie jak inne modyfikatory, takie jak publicitp final.), aby wskazać, że pole nie powinno być serializowane. Pola oznaczone słowem kluczowym transientnie są serializowane. Teraz zmodyfikujmy przykład z naszym użytkownikiem, aby poprawić małe zamieszanie i nie zapisywać hasła użytkownika. W tym celu zaznacz odpowiednie pole w klasie słowem kluczowym transient:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private transient String password;

    /*
        Конструкторы, геттеры, сеттеры, toString...
     */
}
Jeśli ponownie uruchomimy metodę z powyższego przykładu main, zobaczymy, że hasło nie zostało zapisane:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
Świetnie, osiągnęliśmy swój cel i nie przechowujemy informacji poufnych. Zwłaszcza tego rodzaju informacje... (przepraszam)

Kiedy stosować przejściowe?

Aby zagłębić się w kontekst serializacji, potrzebny był przykład z użytkownikiem. Porozmawiajmy teraz bardziej szczegółowo o tym, kiedy używać modyfikatora transient.

  • Pola obliczane programowo

Niektóre klasy mają czasami pola obliczane na podstawie innych pól lub innych informacji. Są one obliczane, że tak powiem, na bieżąco. Aby dać przykład takiego pola, wyobraźmy sobie zamówienie w sklepie internetowym lub w jakiejś firmie zajmującej się dostawą jedzenia. Każde zamówienie składa się m.in. z wykazu towarów oraz całkowitego kosztu. Na niego z kolei składa się całkowity koszt każdego produktu. Okazuje się, że ostatecznego kosztu nie należy ustalać „od ręki”: należy go obliczyć programowo, sumując koszt wszystkich towarów. Pola takie, które powinny być obliczane programowo, nie muszą być serializowane. Dlatego zaznaczamy je modyfikatorem transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • Pola z informacjami prywatnymi

Istnieją również klasy przechowujące prywatne informacje. Przykład takiej klasy przyjrzeliśmy się na początku artykułu. Nie należy pozwalać, aby takie informacje wyciekały poza maszynę JVM. Dlatego pola z takimi danymi muszą być oznaczone modyfikatorem, transientjeśli zamierzasz serializować taką klasę.

  • Pola, które nie implementują interfejsuSerializable

Czasami klasa zawiera pola-obiekty innych klas, które nie implementują interfejsu Serializable. Przykładami takich pól są rejestratory, strumienie we/wy, obiekty przechowujące połączenia z bazami danych i inne klasy narzędzi. Jeśli spróbujesz serializować obiekt zawierający pola, których nie można serializować, pojawi się błąd java.io.NotSerializableException. Aby tego uniknąć, wszystkie pola nie implementujące interfejsu Serializablemuszą być oznaczone modyfikatorem transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их Jak transient поля
    private transient InputStream is;
    private transient BufferedReader buf;
    private String fileName;

    // Constructors, Getters, Setters

    public String readFile() throws IOException {
        try {
            is = new FileInputStream(fileName);
            buf = new BufferedReader(new InputStreamReader(is));
            String line = buf.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line).append("\n");
                line = buf.readLine();
            }
            return sb.toString();
        } finally {
            if (buf != null) {
                buf.close();
            }
            if (is != null) {
                is.close();
            }
        }
    }
}

  • Pola z informacją o stanie obiektu

Cóż, ostatnia rzecz. Nie ma potrzeby serializacji pól, które nie są częścią informacji o stanie obiektu. Powyższe przykłady podlegają tej regule. Ale można tu także uwzględnić wszystkie inne pola dodane w celu debugowania lub wykonania jakiejś funkcji serwisowej, która nie niesie informacji o stanie obiektu.

transientIfinal

Wyniki

To wszystko. Dzisiaj rozmawialiśmy o modyfikatorze transient:
  1. Przypomnieliśmy sobie serializację w teorii i praktyce.
  2. Zdaliśmy sobie sprawę, że aby nie serializować niektórych pól klasy, należy je oznaczyć modyfikatorem transient.
  3. Omówiliśmy, w jakich sytuacjach należy zastosować ten modyfikator. Były cztery takie sytuacje:
    1. pola obliczane programowo;
    2. pola zawierające tajne informacje;
    3. pola, które nie implementują interfejsu Serializable;
    4. pola, które nie są częścią stanu obiektu.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION