Serializable 과 File 활용하기
1. 직렬화(Serializable)
직렬화란 객체를 데이터 스트림으로 만드는 것을 의미한다.
객체는 클래스에 정의된 인스턴스 변수의 집합으로 객체는 오직 인스턴스 변수로만 구성되어 있다.
(클래스 변수와 메서드는 method area 에 저장되고 인스턴스 변수만 heap 영역에 저장된다.)
2. ObjectInputStream 과 ObjectOutPutStream
직렬화를 위해서는 ObjectOutPutStream 를 사용하고,
역직렬화를 위해서는 ObjectInputStream 를 사용한다.
직렬화 (객체 👉 스트림) : ObjectOutPutStream
역직렬화 (스트림 👉 객체) : ObjectInputStream
해당 스트림은 이름에서도 알 수 있듯이 바이트 기반 스트림 InputStream / OutputStream 을 상속받았으며,
주 스트림이 필요한 보조 스트림이다.
// 직렬화
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objectFile.ser");
// 학생 객체 저장
out.writeObject(new Student());
// 역직렬화
ObjectInputStream in = new ObjectInoutStream(new FileInputStream("objectFile.ser");
// 저장된 학생 객체 읽기
Student student = (Student)in.readObject();
readObject() 때에는 주의해야 할 점이 있다.
바로 읽어오는 객체가 어떤 타입인지 명시적 형변환을 해줘야 한다는 것이다.
3. 직렬화가 가능한 클래스 만들기
직렬화는 모든 클래스가 가능한 것이 아니라 Serializable 인터페이스가 구현되어 있는 클래스만 가능하다.
public class Student implements Serializable{
// 학번
private int no;
// 이름
private String name;
// 국어
private int korean;
// 수학
private int math;
// 영어
private int english;
}
이런 식으로 클래스에 Serializable 인터페이스를 작성해주면 직렬화가 가능해진다.
여기에서 직렬화 대상이 되는 인스턴스 변수는 no, name, korean, math, english 가 된다.
만약 여기서 특정 변수를 직렬화 대상에서 제외해야 한다면 어떻게 해야 할까?
그럴 때 사용할 수 있는 것이 transient 키워드이다.
public class Student implements Serializable{
// 학번
private transient int no;
// 이름
private String name;
// 국어
private int korean;
// 수학
private int math;
// 영어
private int english;
}
이렇게 구현할 수 있다.
이 클래스의 인스턴스를 직렬화 한다면 no 부분에는 해당 데이터 타입의 기본값이 들어간다.
학생 예제를 만들었던 프로젝트 코드를 활용하여 예시를 만들어 보고자 한다.
해당 예제에서는 테스트를 위한 5명의 학생을 만들어 작성하는 코드가 있었다.
// StudentService
// 초기화 블럭
{
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
}
위의 코드 내용에 대해서 직렬화와 파일을 적용해 보자.
기존의 List로 구현한 학생의 데이터를 File 로 만들어 관리하려고 한다.
이 File 에 학생이 없을 때만 테스트 학생을 만들어 내고,
File 에 학생이 있을 때에는 기존의 학생 데이터를 불러 올 수 있도록 한다.
이러한 내용의 요구 조건을 만들어 구현하면 다음과 같다.
{
// try-catch 를 통해 분기와 예외 처리
try{
// 파일 읽어오기
ObjectInputStream studentFile = new ObjectInputStream(new FileInputStream("studentFile.ser"));
// 읽어 온 데이터를 학생 리스트에 담기
students = (List<Student>)studentFile.readObject();
// 읽어 온 데이터를 정렬한 학생 리스트에 담기
sortedStudents = (List<Student>)studentFile.readObject();
// 동명의 파일이 없을 때 발생하는 예외
} catch(FileNotFoundException e) {
// 테스트 학생이 생성된다.
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
students.add(new Student(students.size() + 1));
// 테스트 학생의 정렬한다.
sortingStudents();
} catch(ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
직렬화 대상이 되는 클래스는 앞서 말했듯이 Serializable 인터페이스를 구현해야 한다.
때문에 Student 클래스에는 구현을 해놓았다.
하지만 ArrayList 에는 구현한 적이 없는데 어떻게 된 것일까?
ArrayList 에 들어가보면 상단에 아래와 같은 코드를 확인할 수 있다.
즉 이미 구현이 되어 있었기 때문에 문제 없이 직렬화가 가능했던 것이다.
직렬화를 할 때는 대상이 되는 모든 것에 Serializable 이 구현되어야 함을 잊지 말자.