JavaRush /Java Blog /Random-KO /Java에서 임시 수정자는 무엇을 숨깁니까?
Анзор Кармов
레벨 31
Санкт-Петербург

Java에서 임시 수정자는 무엇을 숨깁니까?

Random-KO 그룹에 게시되었습니다
안녕하세요! 오늘 글에서는 Java의 Transient Modifier에 대해 살펴보겠습니다. 이 수정자가 왜 필요한지, 올바르게 사용하는 방법에 대해 이야기해 보겠습니다. 가다! Java에서 임시 수정자는 무엇을 숨깁니다 - 1

직렬화를 기억하자

수정자는 transient개체를 직렬화 및 역직렬화하는 과정에서 사용됩니다. 그럼 먼저 이것에 대해 간단히 이야기해 보겠습니다. Java - 2에서 임시 수정자는 무엇을 숨깁니다.객체가 있고 여기에 각각 값이 있는 필드가 있다고 가정해 보겠습니다. 이 모든 것을 객체의 상태라고 합니다. 직렬화는 객체의 상태를 바이트 시퀀스로 변환하는 것입니다. 이 바이트는 일반적으로 일부 파일에 저장됩니다. 역직렬화는 반대 프로세스입니다. 객체를 바이트로 직렬화하고 이 바이트 집합을 일부 파일에 저장했다고 가정해 보겠습니다. 역직렬화할 때 프로그램에는 다음이 필요합니다.
  1. 파일에서 바이트 세트를 읽습니다.
  2. 이 바이트 집합에서 초기 개체를 구성하고 각 필드를 직렬화 당시 개체가 가졌던 값으로 설정합니다.
언제 유용할까요? 예를 들어, 프로그램을 종료할 때 상태를 저장하고 다음에 켤 때 복원하도록 하려는 경우입니다. IntelliJ IDEA를 종료하면 다음에 다시 켤 때 동일한 탭과 클래스가 열릴 가능성이 높습니다.

실제로 직렬화를 기억하자

이제 실제로 직렬화를 살펴보겠습니다. 주제를 더 잘 이해하려면 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. 이러한 필드의 예로는 로거, 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.
  1. 우리는 이론과 실제에서 연재를 기억했습니다.
  2. 우리는 클래스의 일부 필드를 직렬화하지 않으려면 수정자로 표시해야 한다는 것을 깨달았습니다 transient.
  3. 우리는 이 수정자를 어떤 상황에 사용해야 하는지 논의했습니다. 그러한 상황은 네 가지였습니다.
    1. 프로그래밍 방식으로 계산되는 필드
    2. 비밀 정보가 포함된 필드
    3. 인터페이스를 구현하지 않는 필드 Serializable;
    4. 객체 상태의 일부가 아닌 필드.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION