Java 직렬화 소개

1. 소개

직렬화는 객체의 상태를 바이트 스트림으로 변환하는 것입니다. 역 직렬화는 그 반대입니다. 다르게 말하면 직렬화는 Java 객체를 바이트의 정적 스트림 (시퀀스)으로 변환 한 다음 데이터베이스에 저장하거나 네트워크를 통해 전송할 수 있습니다.

2. 직렬화 및 역 직렬화

직렬화 프로세스는 인스턴스 독립적입니다. 즉, 객체는 한 플랫폼에서 직렬화되고 다른 플랫폼에서는 역 직렬화 될 수 있습니다. 직렬화에 적합한 클래스는 특수 마커 인터페이스 Serializable 을 구현해야합니다 .

ObjectInputStreamObjectOutputStream 은 모두 java.io.InputStreamjava.io.OutputStream을 각각 확장하는 고급 클래스입니다 . ObjectOutputStream 은 기본 유형 및 객체 그래프를 OutputStream 에 바이트 스트림으로 쓸 수 있습니다 . 이러한 스트림은 이후에 ObjectInputStream을 사용하여 읽을 수 있습니다 .

ObjectOutputStream 에서 가장 중요한 메서드 는 다음과 같습니다.

public final void writeObject(Object o) throws IOException;

직렬화 가능한 개체를 가져 와서 바이트 시퀀스 (스트림)로 변환합니다. 마찬가지로 ObjectInputStream 에서 가장 중요한 메서드 는 다음과 같습니다.

public final Object readObject() throws IOException, ClassNotFoundException;

바이트 스트림을 읽고 다시 Java 객체로 변환 할 수 있습니다. 그런 다음 원래 개체로 다시 캐스팅 할 수 있습니다.

Person 클래스를 사용한 직렬화를 설명해 보겠습니다 . 참고 (객체 반대로) 및 직렬화되지 않는 정적 필드는 클래스에 속한다 . 또한 transient 키워드를 사용하여 직렬화 중에 클래스 필드를 무시할 수 있습니다 .

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

아래 테스트는 Person 유형의 객체를 로컬 파일에 저장 한 다음이 값을 다시 읽어 오는 예를 보여줍니다 .

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

우리는 사용 ObjectOutputStream에를 사용하여 파일이 객체의 상태를 저장하기위한 FileOutputStream에를 . 프로젝트 디렉토리에 “yourfile.txt” 파일 이 생성됩니다. 이 파일은 FileInputStream을 사용하여로드됩니다 . ObjectInputStream 은이 스트림을 선택하여 p2 라는 새 객체로 변환합니다 .

마지막으로로드 된 객체의 상태를 테스트하고 원래 객체의 상태와 일치합니다.

로드 된 객체는 명시 적으로 Person 유형 으로 캐스팅되어야합니다 .

3. 자바 직렬화주의 사항

Java의 직렬화와 관련된 몇 가지주의 사항이 있습니다.

3.1. 상속 및 구성

클래스가 java.io.Serializable 인터페이스를 구현할 때 모든 하위 클래스도 직렬화 가능합니다. 반대로 객체에 다른 객체에 대한 참조가있는 경우 이러한 객체는 Serializable 인터페이스를 별도로 구현해야합니다. 그렇지 않으면 NotSerializableException 이 발생합니다.

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

직렬화 가능 개체의 필드 중 하나가 개체 배열로 구성되어 있으면 이러한 모든 개체도 직렬화 가능해야합니다. 그렇지 않으면 NotSerializableException 이 발생합니다.

3.2. 시리얼 버전 UID

JVM은 버전 ( long ) 번호를 각 직렬화 가능한 클래스와 연관시킵니다 . 저장된 객체와로드 된 객체가 동일한 속성을 가지며 따라서 직렬화에서 호환되는지 확인하는 데 사용됩니다.

이 번호는 대부분의 IDE에서 자동으로 생성 할 수 있으며 클래스 이름, 속성 및 관련 액세스 수정자를 기반으로합니다. 모든 변경으로 인해 숫자가 달라지고 InvalidClassException 이 발생할 수 있습니다 .

직렬화 가능한 클래스가 serialVersionUID를 선언하지 않으면 JVM은 런타임에 자동으로 하나를 생성합니다. 그러나 생성 된 것이 컴파일러에 종속되어 예기치 않은 InvalidClassExceptions 가 발생할 수 있으므로 각 클래스는 serialVersionUID 를 선언하는 것이 좋습니다 .

3.3. 자바의 커스텀 직렬화

Java는 객체를 직렬화 할 수있는 기본 방법을 지정합니다. Java 클래스는이 기본 동작을 재정의 할 수 있습니다. 사용자 지정 직렬화는 직렬화 할 수없는 속성이있는 개체를 직렬화하려고 할 때 특히 유용 할 수 있습니다. 이것은 직렬화하려는 클래스 내부에 두 가지 메서드를 제공하여 수행 할 수 있습니다.

private void writeObject(ObjectOutputStream out) throws IOException;

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

이러한 메서드를 사용하면 직렬화 할 수없는 속성을 직렬화 할 수있는 다른 형식으로 직렬화 할 수 있습니다.

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

다음 단위 테스트는이 사용자 지정 직렬화를 테스트합니다.

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

이 코드에서는 사용자 지정 직렬화로 Address 를 직렬화하여 직렬화 할 수없는 속성을 저장하는 방법을 확인합니다 . NotSerializableException 을 피하기 위해 직렬화 할 수없는 속성을 임시 로 표시해야합니다 .

4. 결론

이 빠른 자습서에서는 Java 직렬화를 검토하고 유의해야 할 중요한 사항을 논의했으며 사용자 지정 직렬화를 수행하는 방법을 보여주었습니다.

항상 그렇듯이이 자습서에서 사용 된 소스 코드는 GitHub에서 사용할 수 있습니다.