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ść!
atomy . Napiszmy metodę hiszpański wstyd Modyfikator (wreszcie)
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
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
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,
Czasami klasa zawiera pola-obiekty innych klas, które nie implementują interfejsu
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.
Pamiętajmy o serializacji
Modyfikatortransient
jest używany w procesie serializacji i deserializacji obiektów. Porozmawiajmy więc najpierw krótko o tym. Załóż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:
- Odczytaj zestaw bajtów z pliku.
- Z tego zestawu bajtów skonstruuj obiekt początkowy i ustaw każde pole na wartość, jaką obiekt miał w momencie serializacji.
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ęUser
z 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 User
i 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 main
, w której utworzymy obiekt klasy User
i 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 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
. transient
jest modyfikatorem określonym przed polem klasy (podobnie jak inne modyfikatory, takie jak public
itp final
.), aby wskazać, że pole nie powinno być serializowane. Pola oznaczone słowem kluczowym transient
nie 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ć modyfikatoratransient
.
- 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, transient
jeśli zamierzasz serializować taką klasę.
- Pola, które nie implementują interfejsu
Serializable
Czasami klasa zawiera pola-obiekty innych klas, które nie implementują interfejsu Serializable
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 Serializable
muszą 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.
transient
Ifinal
Wyniki
To wszystko. Dzisiaj rozmawialiśmy o modyfikatorzetransient
:
- Przypomnieliśmy sobie serializację w teorii i praktyce.
- Zdaliśmy sobie sprawę, że aby nie serializować niektórych pól klasy, należy je oznaczyć modyfikatorem
transient
. - Omówiliśmy, w jakich sytuacjach należy zastosować ten modyfikator. Były cztery takie sytuacje:
- pola obliczane programowo;
- pola zawierające tajne informacje;
- pola, które nie implementują interfejsu
Serializable
; - pola, które nie są częścią stanu obiektu.
GO TO FULL VERSION