Chapter 15. 입출력
04 Feb 2022 | 자바의 정석 JAVA1. 자바에서의 입출력
1.1 입출력이란?
- I/O란 Input과 Output의 약자로 입력과 출력, 간단히 줄여 입출력이라고 함
- 입출력: 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 의미
1.2 스트림(stream)
-
자바에서 어느 한쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림(stream)이라고 정의
스트림이란 데이터를 운반하는데 사용되는 연결 통로
- 스트림은 단방향통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없음
- 입력과 출력을 동시에 수행하려면 입력을 위한 입력 스트림(input stream)과 출력을 위한 출력 스트림(output stream), 모두 2개의 스트림이 필요
- 스트림은 먼저 보낸 데이터를 먼제 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고받음
1.3 바이트기반 스트림 - InputStream, OutputStream
-
스트림은 바이트단위로 데이터를 전송
입력스트림 출력스트림 입출력 대상의 종류 FileInputStream FileOutputStream 파일 ByteArrayInputStream ByteArrayOutputStream 메모리(byte 배열) PipedInputStream PipedOutputStream 프로세스(프로세스간의 통신) AudioInputStream AudioOutputStream 오디오 장치 - InputStream 또는 OutputStream의 자손들
- 각각 읽고 쓰는데 필요한 추상메서드를 자신에 맞게 구현해놓음
- 자바에서는 java.io 패키지를 통해 많은 종류의 입출력 관련 클래스들을 제공
- 입출력을 처리할 수 있는 표준화된 방법을 제공함으로써 입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능
-
InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메서드
InputStream OutputStream abstract int read()
abstract void write(int b)
int read(byte[] b)
void write(byte[] b)
int read(byte[] b, int off, int len)
void write(byte[] b, int off, int len)
- read()와 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 맞게 구현하라고 추상메서드로 정의되어 있음
1.4 보조 스트림
-
보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없음
-
보조 스트림은 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있음
-
스트림을 먼저 생성한 후 생성된 스트림을 이용해 보조 스트림을 생성
-
보조 스트림의 종류
입력 출력 설명 FilterInputStream FilterOutputStream 필터를 이용한 입출력 처리 BufferedInputStream BufferedOutputStream 버퍼를 이용한 입출력 성능향상 DataInputStream DataOutputStream 기본형 단위로 데이터를 처리하는 기능 SequenceInputStream 없음 두 개의 스트림을 하나로 연결 LineNumberInputStream 없음 읽어 온 데이터의 라인 번호를 카운트 (JDK1.1부터 LineNumberReader로 대체) ObjectInputStream ObjectOutputStream 데이터를 객체단위로 읽고 쓰는데 사용
주로 파일을 이용하며 객체 직렬화와 관련있음없음 PrintStream 버퍼를 이용하며, 추가적인 print 관련 기능 (print, printf, println 메서드) PushbackInputStream 없음 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능 (unread, push back to buffer)
1.5 문자기반 스트림 - Reader, Writer
- 위의 스트림은 모두 바이트기반의 스트림
- 바이트기반이란 입출력의 단위가 1byte라는 의미
- 자바에서는 한 문자를 의미하는 char형이 2byte이기 때문에 바이트기반의 스트림으로 2byte인 문자를 처리하는데 어려움이 있음
- 문자기반의 스트림이 제공됨. 문자 데이터를 입출력할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용하는 것이 좋음
- 문자기반 스트림의 이름은 바이트기반 스트림의 이름에서 InputStream은 Reader로, OutputStream은 Writer로 바꾸면 됨
- 단, ByteArrayInputStream에 대응하는 문자기반 스트림은 char 배열을 사용하는 CharArrayReader
- 문자기반 스트림의 읽기와 쓰기에 사용되는 메서드는 byte 배열 대신 char 배열을 사용
- 문자기반 보조스트림도 존재
2. 바이트기반 스트림
2.1 InputStream과 OutputStream
- 스트림의 종류에 따라 mark()와 reset()을 사용해 이미 읽은 데이터를 되돌려 다시 읽을 수 있음
- 이 기능을 지원하는 스트림인지 확인하는 markSupported()를 통해 알 수 있음
- flush()는 버퍼가 있는 출력스트림의 경우에만 의미가 있음
- OutputStream에 정의된 flush()는 아무 일도 하지 않음
- 프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 주기는 하지만, 스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해 반드시 닫아주여야 함
- ByteArrayInputStream과 같이 메모리를 사용하는 스트림과 System.in, System.out과 같은 표준 입출력 스트림은 닫아 주지 않아도 됨
2.2 ByteArrayInpuStream과 ByteArrayOutputStream
- 메모리, 즉 바이트배열에 데이터를 입출력 하는데 사용되는 스트림
- 주로 다른 곳에 입출력하기 전 데이터를 임시로 바이트 배열에 담아서 변환 등의 작업을 하는데 사용
- 바이트배열은 사용하는 자원이 메모리 밖에 없으므로 가비지컬렉터에 의해 자동적으로 자원을 반환하므로 close()를 이용해 스트림을 닫지 않아도 됨
- read()와 write(int b)를 사용하기 때문에 한 번에 1byte만 읽고 쓰므로 작업효율이 떨어짐
- 배열을 이용한 입출력을 사용하면 작업의 효율을 증가시킬 수 있음
3. 바이트기반의 보조스트림
3.1 FilterInputStream과 FilterOutputStream
-
InputStream/OutputStream의 자손이면서 모든 보조스트림의 조상
-
보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반스트림을 필요로 함
-
FilterInputStream/FilterOutputStream의 생성자
protected FilterInputStream(InputStream in) public FilterOutputStream(OutputStream out)
-
FilterInputStream/FilterOutputStream의 모든 메서드는 기반스트림의 메서드를 그대로 호출
- FilterInputStream/FilterOutputStream 자체로는 아무런 일도 하지 않음을 의미
-
FilterInputStream/FilterOutputStream는 상속을 통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩해야 함
3.2 BufferedInputStream과 BufferedOutputStream
-
스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조스트림
-
한 바이트씩 입출력하는 것보다는 버퍼(바이트배열)를 이용해 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문에 대부분의 입출력 작업에서 사용됨
-
BufferedInputStream의 생성자
생성자 설명 BufferedInputStream(InputStream in, int size) 주어진 InputStream인스턴스를 입력소스로 하며 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedInputStream인스턴스를 생성 BufferedInputStream(InputStream in) 주어진 InputStream인스턴스를 입력소스로 하며 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192 byte 크기의 버퍼를 갖게 됨 - 버퍼 크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋음
- 프로그램에서 입력소스로부터 데이터를 읽기 위해 처음으로 read를 호출하면, BufferedInputStream은 입력소스로부터 버퍼 크기만큼의 데이터를 일거와 자신의 내부 버퍼에 저장
- 프로그램에서는 BuffferedInputStream의 버퍼에 저장된 데이터를 읽으면 됨
- 외부의 입력소스로부터 읽는 것보다 내부의 버퍼로부터 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아짐
- 프로그램에서 버퍼에 저장된 모든 데이터를 다 읽고 그 다음 데이터를 읽기위해 read 메서드가 호출되면, BufferedInputStream은 입력소스로부터 다시 버퍼크기만큼의 데이터를 읽어다 버퍼에 저장해 놓음
-
BufferedOutputStream의 생성자와 메서드
메서드 / 생성자 설명 BufferedOutputStream(OutputStream out, int size) 주어진 OutputStream 인스턴스를 출력소스로하며 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedOutputStream 인스턴스를 생성 BufferedOutputStream(OutputStream out) 주어진 OutputStream인스턴스를 출력소스로하며 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192byte 크기의 버퍼를 갖게 됨 flush() 버퍼의 모든 내용을 출력소스에 출력한 다음, 버퍼를 비움 close() flush()를 호출해서 버퍼의 모든 내용을 출력소스에 출력하고, BufferedOutputStream 인스턴스가 사용하던 모든 자원을 반환 - 버퍼를 이용해 출력소스와 작업을 하게 됨
- 프로그램에서 write 메서드를 이용한 출력이 BufferedOutputStream의 버퍼에 저장됨
- 버퍼가 가득 차면, 그 때 버퍼의 모든 내용을 출력소스에 출력한 후, 버퍼를 지우고 다시 프로그램으로부터 출력을 저장할 준비를 함
- 버퍼가 가득 찼을 때만 출력소스에 출력을 하기 때문에, 마지막 출력부분이 출력소스에 쓰이지 못하고 BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있음
- 프로그램에서 모든 출력작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해 마지막에 버퍼에 있는 모든 내용이 출력소스에 출력되도록 해야 함
- close()는 flush()를 호출해 버퍼의 내용을 출력스트림에 쓰도록 한 후, BufferedOutputStream인스턴스의 참조변수에 null을 지정함으로써 사용하던 자원들이 반환되게 함
3.3 DataInputStream과 DataOutputStream
- DataInputStream은 DataInput 인터페이스를, DataOutputStream은 DataOutput 인터페이스를 각각 구현하였기 때문에, 데이터를 읽고 쓰는데 있어서 byte 단위가 아닌, 8가지 기본 자료형의 단위로 읽고 쓸 수 있다는 장점이 있음
- DataOutputStream이 출력하는 형식은 각 기본 자료형 값을 16진수로 표현하여 저장함
- int값을 출력하면, 4byte의 16진수로 출력됨
- 각 자료형의 크기가 다르므로, 출력한 데이터를 다시 읽어올 때는 출력했을 때의 순서를 염두에 두어야 함
- DataInputStream의 readInt()와 같이 데이터를 읽는 메서드는 더 이상 읽을 데이터가 없으면 EOFException을 발생시킴
- 다른 입력스트림들과는 달리 무한반복문과 EOFException을 처리하는 catch문을 이용해 데이터를 읽음
- try 블럭 내에서 스트림을 닫아주는 방법보다는, 작업도중에 예외가 발생해 스트림을 닫지 못하고 try 블럭을 빠져나갈 수 있기 때문에, finally 블럭을 이용해 스트림을 닫아주는 것이 더 확실한 방법
- JDK1.7부터는 try-with-resources문을 이용해 close()를 직접 호출하지 않아도 자동호출되도록 할 수 있음
3.4 SequenceInputStream
-
여러 개의 입력스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 도와줌
-
생성자를 제외하고 나머지 작업은 다른 입력스트림과 다르지 않음
-
큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업을 수행할 때 사용하면 좋음
-
SequenceInputStream의 생성자
메서드 / 생성자 설명 SequenceInputStream(Enumeration e) Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결 SequenceInputStream(InputStream s1, InputStream s2) 두 개의 입력스크림을 하나로 연결 - Vector에 연결할 입력스트림들을 저장한 다음 Vector의 Enumeration elements()를 호출해서 생성자의 매개변수로 사용
- Vector에 저장된 순서대로 입력되므로 순서에 주의
3.5 PrintStream
- 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공
- 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행
- PrintStream과 PrintWriter는 거의 같은 기능을 가지고 있지만 PrintWriter가 PrintStream에 비해 다양한 언어의 문자를 처리하는데 적합하기 때문에 가능하면 PrintWriter를 사용하는 것이 좋음
- print()나 println()을 이용해 출력하는 중 PrintStream의 기반스트림에서 IOException이 발생하면 checkError()를 통해 인지할 수 있음
- println()이나 print()는 예외를 던지지 않고 내부에서 처리하도록 정의함. println()과 같은 메서드는 매우 자주 사용되는 것이기 때문
4. 문자기반 스트림
4.1 Reader와 Writer
- InputStream/OutputStream과 같은 역할을 함. byte 배열 대신 char 배열을 사용한다는 것이 차이점
- Reader, Writer와 자손들은 여러 종류의 인코딩과 자바에서 사용하는 유니코드간의 변환을 자동적으로 처리해줌
- Reader는 특정 인코딩을 읽어서 유니코드로 변환하고 Writer는 유니코드를 특정 인코딩으로 변환하여 저장
4.2 FileReader와 FileWriter
- 파일로부터 텍스트데이터를 읽고, 파일에 쓰는데 사용됨
- 사용방법은 FileInputStream/FileOutputStream과 다르지 않음
4.3 PipedReader와 PipedWriter
- 쓰레드 간에 데이터를 주고받을 때 사용됨
- 다른 스트림과는 달리 입력과 출력스트림을 하나의 스트림으로 연결해서 데이터를 주고받음
- 스트림을 생성한 다음 어느 한쪽 쓰레드에서 connect()를 호출해서 입력 스트림과 출력 스트림을 연결. 입출력을 마친 후 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힘
4.4 StringReader와 StringWriter
-
입출력 대상이 메모리인 스트림
-
StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며 StringWriter의 메서드를 이용해 저장된 데이터를 얻을 수 있음
StringBuffer getBuffer() StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환
String toString() StringWriter에 출력된 (StringBuffer에 저장된) 문자열을 반환
5. 문자기반의 보조스트림
5.1 BufferedReader와 BufferedWriter
- 버퍼를 이용해 입출력의 효율을 높일 수 있도록 해주는 역할을 함
- BufferedReader의 readLine()을 사용하면 데이터를 라인단위로 읽을 수 있고, BufferedWriter는 newLine()이라는 줄바꿈 해주는 메서드를 가지고 있음
5.2 InputStreamReader와 OutputStreamWriter
- 바이트기반 스트림을 문자기반 스트림으로 연결시켜주는 역할
- 바이트기반 스트림의 데이터를 지정된 인코딩의 문자 데이터로 변환하는 작업을 수행
6. 표준입출력과 File
6.1 표준입출력 - System.in, System.out, System.err
- 표준 입출력: 콘솔을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미
System.in 콘솔로부터 데이터를 입력받는데 사용
System.out 콘솔로부터 데이터를 출력하는데 사용
System.err 콘솔로부터 데이터를 출력하는데 사용
- in, out, err은 System 클래스에 선언된 클래스변수(static 변수)
- out, err, in 타입은 실제로는 버퍼를 이용하는 BufferedInputStream과 BuffeeredOutputStream의 인스턴스를 사용
- 콘솔 입력은 버퍼를 가지고 있기 때문에 Backspace키를 이용해서 편집이 가능하며 한 번에 버퍼의 크기만큼 입력이 가능
- 콘솔에 데이터를 입력하고 Enter키를 누르면 입력대기상태에서 벗어나 입력된 데이터를 읽기 시작하고 입력된 데이터를 모두 읽으면 다시 입력대기 상태가 됨
6.2 표준입출력의 대상변경 - setOut(), setErr(), setIn()
메서드 | 설명 |
---|---|
static void setOut(PrintStream out) | System.out의 출력을 지정된 PrintStream으로 변경 |
static void setErr(PrintStream err) | System.err의 출력을 지정된 PrintStream으로 변경 |
static void setIn(PrintStream in) | System.in의 입력을 지정된 PrintStream으로 변경 |
6.3 RandomAccessFile
- 하나의 클래스로 파일에 대한 입력과 출력을 모두 할 수 있음
- DataInput 인터페이스와 DataOutput 인터페이스를 모두 구현했기 때문에 읽기와 쓰기가 모두 가능
- 기본자료형 단위로 데이터를 읽고 쓸 수 있음
- 장점: 파일의 어느 위치에나 읽시/쓰기가 가능하다는 것
- 다른 입출력 클래스들은 입출력소스에 순차적으로 읽기/쓰기를 하기 때문에 읽기와 쓰기가 제한적인데 반해 RandomAccessFile 클래스는 파일에 읽고 쓰는 위치에 제한이 없음
- 내부적으로 파일 포인터를 사용. 입출력 시에 작업이 수행되는 곳이 바로 파일 포인터가 위치한 곳이 됨
- 파일 포인터의 위치는 파일의 제일 첫 부분(0부터 시작). 읽기 또는 쓰기를 수행할 때마다 작업이 수행된 다음 위치로 이동
- 순차적으로 읽기나 쓰기를 한다면, 파일 포인터를 이동시키기 위해 별도의 작업이 필요하지 않음
- 파일의 임의의 위치에 있는 내용에 대해 작업하고자 한다면, 먼저 파일 포인터를 원하는 위치로 옮긴 다름 작업 해야 함
- 현재 작업 중인 파일에서 파일 포인터의 위치를 알고 싶을 때는 getFilePointer()을 사용.
- 파일 포인터의 위치를 옮기기 위해서는 seek(long pos)나 skipBytes(int n)를 사용
- 파일 포인터의 위치는 파일의 제일 첫 부분(0부터 시작). 읽기 또는 쓰기를 수행할 때마다 작업이 수행된 다음 위치로 이동
6.4 File
- File 클래스를 통해 파일과 디렉토리를 다룰 수 있음
- File 인스턴스는 파일일 수도 있고 디렉토리일 수도 있음
- File 인스턴스를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아님
- 파일명이나 디렉토리명으로 지정된 문자열이 유효하지 않더라도 컴파일 에러나 예외를 발생시키지 않음
- 새로운 파일을 생성하기 위해서는 File 인스턴스를 생성한 다음, 출력 스트림을 생성하거나 createNewFile()을 호출해야 함
7. 직렬화(Serialization)
7.1 직렬화란?
- 직렬화(Serialization): 객체를 데이터 스트림으로 만드는 것을 의미
- 객체에 저장된 데이터를 스트림에 쓰기위해 연속적인 데이터로 변환하는 것
- 역직렬화(deserialization): 스트림으로부터 데이터를 읽어서 객체를 만드는 것
Review
- 객체: 클래스에 정의된 인스턴스 변수의 집합
- 클래스 변수나 메서드가 포함되지 않음
- 오직 인스턴스변수들로만 구성됨
- 객체에는 메서드가 포함되지 않음
- 인스턴스 변수는 인스턴스마다 다른 값을 가질 수 있어야하기 때문에 별도의 메모리공간이 필요하지만 메서드는 변하는 것이 아니라 메모리를 낭비해가면서 인스턴스마다 같은 내용의 코드를 포함시킬 이유가 없음
- 객체를 저장한다는 것은 객체의 모든 인스턴스 변수의 값을 저장한다는 것과 같은 의미
- 어떤 객체를 저장하고자 한다면, 현재 객체의 모든 인스턴스 변수의 값을 저장하면 됨
- 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 됨
7.2 ObjectInputStream, ObjectOutputStream
- 직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용하고 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용
- 기반스트림을 필요로 하는 보조 스트림
- 객체를 생성할 때 입출력(직렬화/역직렬화)할 스트림을 지정해주어야 함
- 직렬화 과정
- 출력할 스트림(FileOutputStream)을 생성해 이를 기반으로 하는 ObjectOutputStream()을 생성
- ObjectOutputStream의 writeObject(Object obj)를 사용해 객체를 출력하면, 객체가 파일에 직렬화되어 저장됨
- 역직렬화 과정
- 입력스트림을 사용하고 writeObject(Object obj) 대신 readObject()를 사용해 저장된 데이터를 읽기만 하면 객체로 역직렬화됨
- readObj()의 반환타입이 Object이기 때문에 객체 원래의 타입으로 형변환해주어야 함
7.3 직렬화가 가능한 클래스 만들기 - Serializable, transient
- 직렬화가 가능한 클래스를 만들기 위해서는 직렬화하고자 하는 클래스가 java.io.Serializable 인터페이스를 구현하도록 하면 됨
- Serializable 인터페이스는 아무런 내용 없는 빈 인터페이스. 직렬화를 고려해 작성한 클래스인지를 판단하는 기준이 됨
- Serializable을 구현한 클래스를 상속받으면, Serializable을 구현하지 않아도 됨
- 부모 클래스에서 Serializable을 구현한 경우 자식 클래스에서는 Serializable을 구현하지 않아도 직렬화 가능. 이 경우 자식 클래스 객체를 직렬화하면 조상 클래스에 정의된 인스턴스변수도 함께 직렬화 됨
- 부모 클래스에서 Serializable을 구현하지 않은채 자식 클래스에서 직렬화를 하기 위해서는 자식 클래스에서 Serializable을 구현해주어야 함. 이때 자식 클래스의 객체를 직렬화하면 부모 클래스에 정의된 인스턴스변수는 직렬화 대상에서 제외됨
- 부모 클래스에 정의된 인스턴스 변수를 직렬화 대상에 포함시키기 위해서는 부모 클래스에 Serializabel을 구현하던가, 자식 클래스에서 조상의 인스턴스 변수들이 직렬화되도록 처리하는 코드르 직접 추가해주어야 함
- 직렬화 여부는 인스턴스변수의 타입이 아닌 실제로 연결된 객체의 종류에 의해 결정됨
- Object 클래스는 Serializable을 구현하지 않았기 때문에 직렬화할 수 없음
- 직렬화하고자 하는 객체의 클래스에 직렬화가 안 되는 객체에 대한 참조를 포함하고 있다면, 제어자 transient를 붙여 직렬화 대상에서 제외되도록 할 수 있음
- 보안상 직렬화되면 안 되는 값에 대해서도 transient를 사용할 수 있음
- transient가 붙은 인스턴스 변수의 값은 그 타입의 기본값으로 직렬화 됨
- 객체를 역직렬화 할 때는 직렬화할 때의 순서와 일치해야 하기 때문에 직렬화할 객체가 많을 경우 각 객체를 개별적으로 직렬화하는 것보다 ArrayList와 같은 컬렉션에 저장해서 직렬화하는 것이 좋음
- 역직렬화시 ArrayList 하나만 역직렬화하면 되므로 역직렬화할 객체의 순서를 고려하지 않아도 되기 때문
7.4 직렬화가능한 클래스의 버전관리
- 직렬화된 객체를 역직렬화할 때는 직렬화했을 때와 같은 클래스를 사용해야 함
- 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화는 실패하며 예외가 발생
- 예외 내용: 직렬화할 때와 역직렬화 할 때의 클래스의 버전이 같아야 하지만 다르다는 것
- 객체가 직렬화될 때 클래스에 정의된 멤버들의 정보를 이용해 serialVersionUID라는 클래스의 버전을 자동생성해 직렬화 내용에 포함
- 역직렬화할 때 클래스의 버전을 비교함으로써 직렬화할 때의 클래스의 버전과 일치하는지 확인 가능
- static 변수나 상수 또는 transient가 붙은 인스턴스 변수가 추가되는 경우에는 직렬화에 영향을 미치지 않기 때문에 클래스의 버전을 다르게 인식하도록 할 필요가 없음
- 클래스의 버전을 수동으로 관리하기 위해서는 serialVersionUID를 추가로 정의해야 함
- 클래스 내에 serialVersionUID를 정의해주면, 클래스의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지 않음
- serialVersionUID의 값은 정수값이면 어떤 값으로도 지정 가능하지만, 서로 다른 클래스간에 같은 값을 갖지 않도록 serialver.exe를 사용해 생성된 값을 사용하는 것이 일반적
- serialver.exe 뒤에 SerialVersionUID를 얻고자 하는 클래스의 이름만 적어주면 클래스의 serialVersionUID를 알아낼 수 있음
- serialver.exe는 클래스에 serialVersionUID가 정의되어 있으면 그 값을 출력하고 정의되어 있지 않으면 자동 생성한 값을 출력
- serialver.exe에 의해 생성되는 serialVersionUID 값은 클래스의 멤버들에 대한 정보를 바탕으로 하기 때문에 이 정보가 변경되지 않는 한 항상 같은 값을 생성
Chapter 15 끝!!!