안녕하세요! 오늘 글에서는 Java의 Transient Modifier에 대해 살펴보겠습니다. 이 수정자가 왜 필요한지, 올바르게 사용하는 방법에 대해 이야기해 보겠습니다. 가다!
이제 바이트를 원자로 분할할 시간입니다 . 스페인의 수치심이 수정자(마지막)
우리가 사용자의 비밀번호를 저장했다는 사실에 헷갈린 사람이 있나요? 특히 그런 비밀번호는... 예, 예, 우리가 직접 생각해 냈지만 여전히... 때로는 일부 필드를 직렬화할 수 없거나 직렬화하지 않는 것이 더 좋은 상황이 있습니다. 위의 예에서는 비밀번호를 제외한 모든 필드를 저장하고 싶습니다. 이것을 달성하는 방법은 무엇입니까? 답변: 수정자를 사용하세요
일부 클래스에는 다른 필드나 기타 정보를 기반으로 계산되는 필드가 있는 경우가 있습니다. 말하자면, 즉석에서 계산됩니다. 그러한 분야의 예를 들기 위해 온라인 상점이나 음식 배달 서비스에서의 주문을 상상해 봅시다. 각 주문은 기타 정보 중에서 상품 목록과 총 비용으로 구성됩니다. 이는 차례로 각 제품의 총 비용으로 구성됩니다. 최종 비용은 "직접" 설정해서는 안 됩니다. 모든 상품의 비용을 합산하여 프로그래밍 방식으로 계산해야 합니다. 프로그래밍 방식으로 계산해야 하는 이와 같은 필드는 직렬화할 필요가 없습니다. 따라서 수정자로 표시합니다
개인정보를 저장하는 클래스도 있습니다. 기사 시작 부분에서 그러한 클래스의 예를 살펴보았습니다. 이러한 정보가 JVM 외부로 유출되도록 허용해서는 안 됩니다.
때때로 클래스에는 인터페이스를 구현하지 않는 다른 클래스의 객체인 필드가 포함되어 있습니다
마지막으로 한 가지만 말씀드리겠습니다. 객체 상태 정보의 일부가 아닌 필드를 직렬화할 필요가 없습니다. 위의 예는 이 규칙에 속합니다. 그러나 디버깅을 위해 추가되거나 개체 상태에 대한 정보를 전달하지 않는 일종의 서비스 기능을 수행하기 위해 추가된 다른 모든 필드를 여기에 포함할 수도 있습니다.
직렬화를 기억하자
수정자는transient
개체를 직렬화 및 역직렬화하는 과정에서 사용됩니다. 그럼 먼저 이것에 대해 간단히 이야기해 보겠습니다. 객체가 있고 여기에 각각 값이 있는 필드가 있다고 가정해 보겠습니다. 이 모든 것을 객체의 상태라고 합니다. 직렬화는 객체의 상태를 바이트 시퀀스로 변환하는 것입니다. 이 바이트는 일반적으로 일부 파일에 저장됩니다. 역직렬화는 반대 프로세스입니다. 객체를 바이트로 직렬화하고 이 바이트 집합을 일부 파일에 저장했다고 가정해 보겠습니다. 역직렬화할 때 프로그램에는 다음이 필요합니다.
- 파일에서 바이트 세트를 읽습니다.
- 이 바이트 집합에서 초기 개체를 구성하고 각 필드를 직렬화 당시 개체가 가졌던 값으로 설정합니다.
실제로 직렬화를 기억하자
이제 실제로 직렬화를 살펴보겠습니다. 주제를 더 잘 이해하려면 Java의 직렬화 및 역직렬화 자료를 읽는 것이 좋습니다 . 글쎄, 이 기사에서는 맨 위로 이동하여 바로 예제를 살펴보겠습니다.User
일부 필드, getter 및 setter, 메서드 집합이 있는 클래스가 있다고 가정해 보겠습니다 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 + '\'' +
'}';
}
}
우리는 앞으로 이 클래스의 객체를 직렬화하려고 합니다. 객체 User
와 문자열 path
(바이트를 저장할 파일의 경로)을 취하는 메서드를 작성해 보겠습니다.
static void serialize(User user, String path) throws IOException {
FileOutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//create 2 threads to serialize the object and save it to a file
outputStream = new FileOutputStream(path);
objectOutputStream = new ObjectOutputStream(outputStream);
// сохраняем an object в файл
objectOutputStream.writeObject(user);
} finally {
// Закроем потоки в блоке finally
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
또한 역직렬화 방법도 작성하겠습니다. 이 메서드는 문자열 path
(객체를 "로드"할 파일의 경로)을 사용하고 다음 유형의 객체를 반환합니다 User
.
static User deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = null;
ObjectInputStream objectInputStream = null;
try {
//создаем 2 потока для десериализации an object из file
fileInputStream = new FileInputStream(path);
objectInputStream = new ObjectInputStream(fileInputStream);
//загружаем an object из file
return (User) objectInputStream.readObject();
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
}
}
모든 도구를 사용할 준비가 되었습니다. main
클래스 객체를 생성 User
하고 직렬화하는 메서드를 작성해 보겠습니다 . 그런 다음 이를 로드하고 원래의 것과 비교해 보겠습니다.
public static void main(String[] args) throws IOException, ClassNotFoundException {
// вставьте свой путь до file
final String path = "/home/zor/user.ser";
// create our object
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");
}
메소드를 실행하면 다음과 같은 출력이 표시됩니다.
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'}
출력에서 볼 수 있듯이 개체는 동일합니다. 하지만 작은 문제가 있습니다... 그리고 이것이 바로 transient
작용하는 지점입니다 .
수정자(마지막)transient
우리가 사용자의 비밀번호를 저장했다는 사실에 헷갈린 사람이 있나요? 특히 그런 비밀번호는... 예, 예, 우리가 직접 생각해 냈지만 여전히... 때로는 일부 필드를 직렬화할 수 없거나 직렬화하지 않는 것이 더 좋은 상황이 있습니다. 위의 예에서는 비밀번호를 제외한 모든 필드를 저장하고 싶습니다. 이것을 달성하는 방법은 무엇입니까? 답변: 수정자를 사용하세요 transient
. 필드가 직렬화되어서는 안 됨을 나타내기 위해 transient
클래스 필드 앞에 지정된 수정자입니다( 등의 다른 수정자와 public
유사 ). final
키워드로 표시된 필드는 transient
직렬화되지 않습니다. 이제 사용자의 비밀번호를 저장하지 않고 약간의 혼란을 해결하기 위해 사용자와 함께 예제를 편집해 보겠습니다. 이렇게 하려면 클래스의 해당 필드를 키워드로 표시하세요 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...
*/
}
위 예제의 메소드를 다시 실행하면 main
비밀번호가 저장되지 않은 것을 볼 수 있습니다.
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'}
좋습니다. 우리는 목표를 달성했으며 기밀 정보를 저장하지 않습니다. 특히 이런 정보는... (미안해요)
일시적인 사용 시기는 언제입니까?
직렬화의 맥락을 자세히 살펴보려면 사용자에 대한 예제가 필요했습니다. 이제 수식어를 언제 사용해야 하는지 좀 더 구체적으로 이야기해 보겠습니다transient
.
- 프로그래밍 방식으로 계산되는 필드
일부 클래스에는 다른 필드나 기타 정보를 기반으로 계산되는 필드가 있는 경우가 있습니다. 말하자면, 즉석에서 계산됩니다. 그러한 분야의 예를 들기 위해 온라인 상점이나 음식 배달 서비스에서의 주문을 상상해 봅시다. 각 주문은 기타 정보 중에서 상품 목록과 총 비용으로 구성됩니다. 이는 차례로 각 제품의 총 비용으로 구성됩니다. 최종 비용은 "직접" 설정해서는 안 됩니다. 모든 상품의 비용을 합산하여 프로그래밍 방식으로 계산해야 합니다. 프로그래밍 방식으로 계산해야 하는 이와 같은 필드는 직렬화할 필요가 없습니다. 따라서 수정자로 표시합니다 transient
.
class Order implements Serializable {
private List- items;
private transient BigDecimal totalAmount; //вычисляется на ходу
}
- 개인정보가 포함된 필드
개인정보를 저장하는 클래스도 있습니다. 기사 시작 부분에서 그러한 클래스의 예를 살펴보았습니다. 이러한 정보가 JVM 외부로 유출되도록 허용해서는 안 됩니다. transient
따라서 해당 클래스를 직렬화하려면 해당 데이터가 있는 필드를 수정자로 표시해야 합니다 .
- 인터페이스를 구현하지 않는 필드
Serializable
때때로 클래스에는 인터페이스를 구현하지 않는 다른 클래스의 객체인 필드가 포함되어 있습니다 Serializable
Serializable
. 이러한 필드의 예로는 로거, I/O 스트림, 데이터베이스 연결을 저장하는 개체 및 기타 유틸리티 클래스가 있습니다. 직렬화할 수 없는 필드가 포함된 개체를 직렬화하려고 하면 오류가 발생합니다 java.io.NotSerializableException
. 이를 방지하려면 인터페이스를 구현하지 않는 모든 필드를 Serializable
수정자로 표시해야 합니다 transient
.
public class FileReader implements Serializable {
// Первые 2 поля не реализуют Serializable
// Помечаем их How 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();
}
}
}
}
- 객체 상태에 대한 정보가 포함된 필드
마지막으로 한 가지만 말씀드리겠습니다. 객체 상태 정보의 일부가 아닌 필드를 직렬화할 필요가 없습니다. 위의 예는 이 규칙에 속합니다. 그러나 디버깅을 위해 추가되거나 개체 상태에 대한 정보를 전달하지 않는 일종의 서비스 기능을 수행하기 위해 추가된 다른 모든 필드를 여기에 포함할 수도 있습니다.
transient
그리고final
결과
그게 다야. 오늘 우리는 수정자에 대해 이야기했습니다transient
.
- 우리는 이론과 실제에서 연재를 기억했습니다.
- 우리는 클래스의 일부 필드를 직렬화하지 않으려면 수정자로 표시해야 한다는 것을 깨달았습니다
transient
. - 우리는 이 수정자를 어떤 상황에 사용해야 하는지 논의했습니다. 그러한 상황은 네 가지였습니다.
- 프로그래밍 방식으로 계산되는 필드
- 비밀 정보가 포함된 필드
- 인터페이스를 구현하지 않는 필드
Serializable
; - 객체 상태의 일부가 아닌 필드.
GO TO FULL VERSION