-
[TIL] 2022년 10월 3일 월요일카테고리 없음 2022. 10. 3. 12:13
7. 직렬화(Serialization)
- 직렬화는 객체를 컴퓨터에 저장했다가 재사용하거나 네트워크를 통해 컴퓨터끼리 객체를 주고받는 행위를 가능하게 함
7.1 직렬화란?
- 직렬화(Serialization)란 객체를 데이터 스트림으로 만드는 것
- 객체를 저장한다는 것은 객체의 모든 인스턴스 변수의 값을 저장한다는 것이고, 저장한 객체를 다시 생성하는 것은 저장된 값을 읽어 생성한 객체의 인스턴스 변수에 저장하는 것
- 객체를 저장하고 전송하기 위해서는 객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환(serialization)해야하고, 스트림에서 데이터를 읽어오기 위해서는 연속적인 데이터를 스트림에서 읽어와 객체로 만드는 작업(deserialization)이 필요함
7.2 ObjectInputStream, ObjectOutputStream
- ObjectInputStream과 ObjectOutputStream은 각각 InputStream과 OutputStream 클래스를 상속받지만 기반 스트림이 필요한 보조 스트림으로 객체를 생성할 때 직렬화/역직렬화 할 스트림이 필요
- 스트림에 객체를 출력하는 직렬화는 ObjectOutputStream을 사용
- 스트림에서 객체를 입력하는역직렬화는 ObjectInputStream을 사용
- 직렬화와 역직렬화를 할 때는 각각 writeObject()와 readObject() 메소드를 구현해야 함
- readObject()메소드는 반환타입이 Object라서 객체의 원래 타입으로 형변환이 필요
ObjectInputStream ObjectOutputStream void defaultReadObject() void defaultWriteObject() int read() void write(byte[] buf) int read(byte[] buf, int off, int len) void write(byte[] buf, int off, int len) boolean readBoolean() void writeBoolean(boolean val) byte readByte() void writeByte(int val) void writeBytes(String str) char readChar() void wirteChar(int val) void writeChars(String str) double readDouble() void writeDouble(double val) float readFloat() void writeFloat(int val) int readInt() void writeInt(int val) long readLong() void writeLong(long val) short readShort() void writeObject(Object obj) Object readObject() void writeShort(int val) int readUnsignedByte() int readUnsignedShort() Object readUnshared(Object obj) void writeUnshared(Object obj) String readUTF() void writeUTF(String str) - defaultReadObject() 메소드와 defaultWriteObject() 메소드는 자동 직렬화를 수행
- readUnshared(Object obj)와 writeUnshared(Object obj)는 readObject(Object obj), writeObject(Object obj)와 'Read / Write the specified object to the ObjectInputStream / ObjectOutputStream.', 'Read / Write an "unshared" object to the ObjectInputStream / ObjectOutputStream.'로 큰 차이가 없는데 "unshared"라는 단어를 제외하면 차이가 없다. 그래서 "unshared"의 의미가 무엇인지 찾아봤다. 분배되지 않는, 유일한의 뜻으로 해석되는 것 같은데 딱 떨어지지않아 스택오버플로우의 힘을 빌렸다. 배경지식이 없고 영어 지식도 짧아 정확하게 설명할 수 없지만 "unshared" 가 들어간 readUnshared(Object obj)와 writeUnshared(Object obj)는 보안과 관련된 경우 사용하는 듯 하다. 클래스 필드에 private나 package접근제한자를 참조한 필드가 있고 클래스는 바깥 클래스를 참조하는게 어려운 경우, 내부 객체가 고유하게 참조되는 것을 보장하여 역직렬화과정에서 방어적으로 복사하게 한다는 것 같다고 읽힌다. 사실 메소드만 설명하는 부분이라 내가 더 파고들기는 어려울 것 같고 궁금하다면 링크를 참조하세요,,
When would one use ObjectInputStream.readUnshared() vs .readObject()?
There are two similar methods in the Java ObjectInputStream: readUnshared() and readObject() The documentation states: public Object readUnshared() throws IOException, ClassNotFoundException...
stackoverflow.com
7.3 직렬화가 가능한 클래스 만들기 - Serializable, transient
- 직렬화하고자 하는 클래스는 java.io.Serializable인터페이스를 구현하면 됨
- 구현할 메소드가 없는 빈 인터페이스이지만 Serializable인터페이스를 구현하지 않으면 직렬화와 역직렬화 모두 할 수 없음
- Serializable를 구현한 클래스를 상속받는다면 Serializable인터페이스를 구현하지 않아도 됨
- 조상클래스가 Serializable를 구현하지 않았어도 자식 클래스에서 Serializable를 구현하면 직렬화와 역직렬화를 할 수 있으나 조상 클래스에서 정의한 변수는 직렬화의 대상이 될 수 없음
- 이를 직렬화 대상에 포함하기 위해서는 조상클래스가 Serializable인터페이스를 구현하게 하는 방법과 자식 클래스에서 조상의 인스턴스를 직렬화할 수 있도록 처리하는 코드를 추가하는 방법이 있음
- 직렬화가 안 되는 객체를 포함하고 있는 경우나 보안상 직렬화하면 안 되는 값이 있는 경우 transient를 사용하면 해당 인스턴스변수의 값은 기본값으로 직렬화 됨
더보기예제에 사용할 UserInfo 클래스
package ch15; public class UserInfo implements java.io.Serializable { String name; String password; int age; public UserInfo() { this("Unknown", "1111", 0); } public UserInfo(String name, String password, int age) { this.name = name; this.password = password; this.age = age; } @Override public String toString() { return "(" + name + ", " + password + "," + age + ')'; } }
직렬화 예제
package ch15; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; public class SerialEx1 { public static void main(String[] args) { try { String fileName = "UserInfo.ser"; FileOutputStream fos = new FileOutputStream(fileName); BufferedOutputStream bos = new BufferedOutputStream(fos); ObjectOutputStream out = new ObjectOutputStream(bos); UserInfo u1 = new UserInfo("JavaWoman", "1234", 30); UserInfo u2 = new UserInfo("JavaGirl", "4321", 15); ArrayList<UserInfo> list = new ArrayList<>(); list.add(u1); list.add(u2); out.writeObject(u1); out.writeObject(u2); out.writeObject(list); out.close(); System.out.println("직렬화가 잘 끝났습니다."); }catch (IOException e){ e.printStackTrace(); } } }
역직렬화 예제
package ch15; import java.io.*; import java.util.ArrayList; public class SerialEx2 { public static void main(String[] args) { try { String fileName = "UserInfo.ser"; FileInputStream fis = new FileInputStream(fileName); BufferedInputStream bis = new BufferedInputStream(fis); ObjectInputStream in = new ObjectInputStream(bis); UserInfo u1 = (UserInfo)in.readObject(); UserInfo u2 = (UserInfo)in.readObject(); ArrayList list = (ArrayList)in.readObject(); System.out.println(u1); System.out.println(u2); System.out.println(list); in.close(); }catch (Exception e){ e.printStackTrace(); } } }
private void writeObject(ObjectOutputStream out) throws IOException{ out.writeUTF(name); out.writeUTF(password); out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ name = in.readUTF(); password = in.readUTF(); in.defaultReadObject(); }
- 직렬화되지 않은 조상으로부터 상속받은 인스턴스변수를 직렬화하는 방법으로 미리 정해진 규칙이라 따르면 된다고 함
7.4 직렬화가능한 클래스의 버전관리
- 직렬화된 객체를 역직렬화할 때 직렬화했을 때와 같은 클래스를 사용해야함 → 클래스의 이름이 같더라도 내용이 변경된 경우 예외 발생
- 객체를 직렬화할 때 클래스에 정의된 멤버들의 정보로 serialVersionUID 클래스 버전을 자동생성하여 직렬화 내용에 포함
- static 변수나 상수, transient가 붙은 인스턴스 변수가 추가되는 경우 직렬화에 영향이 없어 클래스 버전을 다르게 볼 필요 없음
- 네트워크로 객체를 직렬화하여 전송하는 경우 보내는 쪽과 받는 쪽 모두 같은 버전을 갖고 있어야 하는데 클래스가 조금만 변경되어도 재배포해야한다면 프로그램 관리가 어려움 → 클래스 버전을 수동으로 관리
static final long serialVersionUID = '';
- serialVersionUID를 정의하면 클래스의 내용이 바뀌어도 클래스 버전이 바뀌지 않음
- serialver.exe를 이용하여 생성된 값을 사용하는 것이 보통