상세 컨텐츠

본문 제목

객체 직렬화 ( Object Serialization )

프로그래밍/JAVA

by 라제폰 2009. 1. 23. 16:55

본문

객체 직렬화 ( Object Serialization )

 

객체의 내용을 파일 혹은 네트워크를 통하여 바이트 스트림으로 입출력

객체의 내용을 입출력 형식을 생각하지 않고 손쉽게 저장, 전송할 수 있다.

 

객체를 저장장치( 파일, 데이타베이스등)에 저장, 검색함으로써 객체의 경량 영속성 제공->자바빈즈에서 유용

 

객체 직렬화의 비밀

 

객체가 입출력되는 스트림은 분명 바이트 스트림일테니 바이트로 계산되지 않는다면 입출력도 불가능하다

그러므로 객체를 바이트로 계산해내는 기능이 제공되는 것이 분명하다.

 

직렬화라는 말은 무언가 일렬로 되어 있지 않은 것을 일렬로 만드는 작업을 뜻한다.

 

java.io.Serializable : 메소드도 없고 변수도 serializableVersionUID 하나만 가진 인터페이스이다.

객체를 직렬화 할수 있는지 없는지를 판별해주는 아주 중요한 역할을 한다.

 

java.lang.Object 클래스는 java.io.Serializable 인터페이스를 구현하지 않는다.

java.lang.Ojbect 클래스는 직렬화 될수 없으므로 스트림에서 사용할수 없다는 말이된다 

 

java.lang.String 클래스는 java.io.Serializable 인터페이스를 구현하고 있다.

스트링 객체는 스트림상에서 이리저리 돌아 다닐수 있다는 얘기가 된다.

화면의 창객체를 띄우고 스트림으로 보내서 다른 컴퓨터에서 받아 똑같은 창을 뛰우는 일도 할수있다.

배열도 통체로 직렬화 시켜서 스트림위에 올려 놓을수 있다.

 

도대체 어떻게 객체들이 바이트들의 연속으로 변하는 것일까?

몰라도 된다.

java.io.ObjectInputStream과 java.io.ObjectOutputStream에서 이를 제공해준다.

 

객체가 직렬화 될때 객체와 상관없는 static 필드는 제외된다.

transient라고 선언된 인스턴스 필드들도 제외가 된다.

transient는 특정변수가 전송되거나 저장되어도 쓸모가 없는 변수에 사용한다.

 

ObjectInputStream, ObjectOutputStream 클래스를 사용하여 입출력

 

성능을 향상시킨 객체 직렬화 방법

http://wwwipd.ira.uka.de/~hauma/

 

OutputStream       ObjectOutput

(abstract)            ( interface)

   |                             |

 -----------------------

 ObjectOutputStream         new ObjectOutputStream(OutputStream out)

 output destination   

 (outputStream)

 

InputStream       ObjectInput

(abstract)            ( interface)

   |                             |

 -----------------------

 ObjectInputStream         new ObjectInputStream(InputStream in)

 Input destination   

 (InputStream)

 

ObjectOutput 인터페이스

 

기본 자료형의 자료 및 객체를 출력하기 위한 메소드 정의

DataOutput 인터페이스의 하위 인터페이스

 

★ 메소드

writeObject( Object obj)

flush()

close()

 

import java.io.*;

class ObjectOutputTest
{   public static void main(String[] args)
    {   try
        {   ObjectOutput s = new ObjectOutputStream(
                new FileOutputStream( "objectstream.dat" ) );
            s.writeObject( "Today" );
            s.writeObject( new java.util.Date() );
            s.flush();
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

 

ObjectInput 인터페이스

 

기본 자료형의 자료 및 객체를 입력하기 위한 메소드 정의

DataInput 인터페이스의 하위 인터페이스

 

★ 메소드

Object readObject() throws ClassNotFoundException

int read()

int read(byte[] buf)

int read(byte buf[], int off, int len)

long skip( long n )

int available()

close()

 

import java.io.*;

class ObjectInputTest
{   public static void main(String[] args)
    {   try
        {   ObjectInput s = new ObjectInputStream(
                new FileInputStream( "objectstream.dat" ) );
            String today = (String) s.readObject();
            java.util.Date date = (java.util.Date) s.readObject();
            System.out.println(today);
            System.out.println(date);
        } catch( Exception e )
        {   e.printStackTrace(); }
    }
}

 

Serializable 인터페이스와 디폴트 객체 직렬화 매카니즘

 

Serializable 인터페이스를 구현하지 않는 클래스의 객체는 객체 직렬화로 입출력 할 수 없다.

NoSerializableException 이 발생

 

많은 표준 클래스들은 이미 Serializable 인터페이스를 구현하고 있다.

 

객체가 직렬화에 의해 입출력 될때, Serializable 인터페이스를 구현하는 최상위 클래스 부터 시작하여

transient로 선언되지 않은 인스턴스 변수가 입출력 된다.

인스턴스 변수가 객체를 참조하는 경우 그 객체도 입출력된다.

( 그 객체의 클래스도 Serializable 인터페이스를 구현하여야 한다. )

객체가 입력되면 해당 클래스의 객체가 생성되고, 그 인스턴스 변수의 값이 입력으로 부터 셋팅된다.

 

NonSerial                     ---------------------

MyList(Serializable)      |  int  v1                       |

                                     ---------------------

                                    | int v2                          |

                                    | transient String v3      |

                                    | MyList next    --------->

                                    ---------------------

node1                 node2                 node3

 next    ------>   next   -------->

 

import java.io.*;

class NonSerial
{
    int v1;
    public NonSerial()  // needed for serialzation
    {}
    public NonSerial( int v1 )
    {   this.v1 = v1;
    }
}

public class MyList extends NonSerial implements Serializable
{
    int v2;
    transient String v3;
    MyList next;

    public MyList(int v1, int v2, String v3)
    {   super(v1);
        this.v2 = v2;
        this.v3 = v3;
    }
   
    public void print()
    {   System.out.print(v1 + ", " + v2 + ", " + v3 + ": ");
        if (next == null)
            System.out.println();
        else
            next.print();
    }

    public static void main(String[] args)
    {
        MyList node1 = new MyList(1, 11, "first");
        MyList node2 = new MyList(2, 12, "second");
        MyList node3 = new MyList(3, 13, "third");
        node1.next = node2;
        node2.next = node3;
        node3.next = null;
        try
        {   FileOutputStream outFile
                = new FileOutputStream("test.out");
            ObjectOutput out = new ObjectOutputStream(outFile);
            out.writeObject(node1);
            out.close();

            FileInputStream inFile
                = new FileInputStream("test.out");
            ObjectInput in = new ObjectInputStream(inFile);
            MyList inNode = (MyList) in.readObject();
            node1.print();
            inNode.print();
            in.close();
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

1, 11, first: 2, 12, second: 3, 13, third:
0, 11, null: 0, 12, null: 0, 13, null:

 

Serializable을 구현하는 클래스에 다음 메소드가 정의되면, 그 클래스에 직접정의되어 있는

( 상위 혹은 하위 클래스에 정의 된 것은 제외)

인스턴스 변수가 입출력 되려고 할때, 대신 다음 메소드가 호출된다.

 

private void writeObject(ObjectOutputStream stream) throws IOException

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException

 

import java.io.*;

public class MyList2 extends NonSerial implements Serializable
{
    int v2;
    transient String v3;
    MyList2 next;

    private void writeObject(ObjectOutputStream stream)
        throws IOException
    {   stream.defaultWriteObject();
        stream.writeInt(v1);
        System.out.println("Writing MyList2 class part of object...");
    }
    private void readObject(ObjectInputStream stream)
        throws IOException, ClassNotFoundException
    {   stream.defaultReadObject();
        v1 = stream.readInt();
        System.out.println("Reading MyList2 class part of object...");
    }
    public MyList2(int v1, int v2, String v3)
    {   super(v1);
        this.v2 = v2;
        this.v3 = v3;
    }
   
    public void print()
    {   System.out.print(v1 + ", " + v2 + ", " + v3 + ": ");
        if (next == null)
            System.out.println();
        else
            next.print();
    }

    public static void main(String[] args)
    {
        MyList2 node1 = new MyList2(1, 11, "first");
        MyList2 node2 = new MyList2(2, 12, "second");
        MyList2 node3 = new MyList2(3, 13, "third");
        node1.next = node2;
        node2.next = node3;
        node3.next = null;
        try
        {   FileOutputStream outFile
                = new FileOutputStream("test.out");
            ObjectOutput out = new ObjectOutputStream(outFile);
            out.writeObject(node1);
            out.close();

            FileInputStream inFile
                = new FileInputStream("test.out");
            ObjectInput in = new ObjectInputStream(inFile);
            MyList2 inNode = (MyList2) in.readObject();
            node1.print();
            inNode.print();
            in.close();
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

Writing MyList2 class part of object...
Writing MyList2 class part of object...
Writing MyList2 class part of object...
Reading MyList2 class part of object...
Reading MyList2 class part of object...
Reading MyList2 class part of object...
1, 11, first: 2, 12, second: 3, 13, thi
1, 11, null: 2, 12, null: 3, 13, null:

 

 

어느 한 객체가 직렬화 되기 위해서는 그 객체가 상속받는 클래스에 대한 정보도 모두 포함되어야 하기 때문에 직렬화된 정보에는 상위클래스 정보도 포함된다.

단, 상위클래스가 Serializable 인터페이스를 구현하지 않으면 포함될수 없다.

만일 이렇게 되었다면 복원시 문제가 된다. 그런경우를 위해 제한적이나마 직렬화에 관여 할수 있도록 마련된 클래스가 있으니 다음의 메소드이다.

 

private void writeObject ( java.io.ObjectOutputStream out ) throws IOException

private void readObject ( java.io.ObjectInputStream in )

                                                  throws IOException, ClassNotFoundException

 

객체가 직렬화 될때 writeObject() 메소드가 그 객체에 존재한다면 이 메소드를 이용해서 직렬화된 것에 자신만의 표현을 덧붙일수 가 있다.

이렇게 덧붙이기위해서는 항상 기본적인 직렬화 작업을 하는 defaultReadObject(), defaultWriteObject 메소드를 호출해야한다.

만일 객체에 readObject(),writeObject()가 없다면 ObjectInputStream의 defaultReadObject()메소드와 ObjectOutputStream의 defaultWriteObjet() 메소드만이 객체를 직렬화 하는데 쓰이게 된다.

 

import java.io.*;  
public class MySerialization extends UnSerializable implements Serializable {
        int i;
        public MySerialization(int i) {
                this.i=i;
                j=i*2;
        }
        private void writeObject(java.io.ObjectOutputStream out)  throws IOException {
                System.out.println("writeObject");
                out.defaultWriteObject(); // 반드시 호출 없으면 i 값 저장안됨
                out.writeInt(j);
        }
        private void readObject(ObjectInputStream in)

                                                  throws IOException, ClassNotFoundException {
                System.out.println("readObject");
                in.defaultReadObject(); // 반드시 호출
                j=in.readInt();
        }
 
        public String toString() {
                return i+", "+j;
        }
 
        public static void main(String[] args) throws Exception {
                FileOutputStream fos=new FileOutputStream("_imsi");
                ObjectOutputStream oos=new ObjectOutputStream(fos);
                oos.writeObject(new MySerialization(1000));
                oos.close();
 
                FileInputStream fis=new FileInputStream("_imsi");
                ObjectInputStream ois=new ObjectInputStream(fis);
                MySerialization ser=(MySerialization)ois.readObject();
                ois.close();
                System.out.println("read : "+ser);
        }
}

 

class UnSerializable // implements Serializable 이렇게 하는것이 가장좋다.
{
 int j;
}

writeObject  ----> 자동으로 호출

readObject

read : 1000, 2000

 

UnSerializable 클래스에는 인자가 있는 생성자 + 인자 없는 생성자 또는 인자 없는 생성자 또는 표기 안함의  생성자가 반드시 있어야 한다. 복원시 인자 없는 생성자가 사용되기 때문이다.

 

지금까지는 객체 직렬화와 복원에 관해서 간접적으로 관여한 예이지만 아예 이작엇을 자신의 코드로 대체할수 있는 수단도 있다.

바로 java.io.Externalizable 인터페이스이다.

 

Externalizable 인터페이스

 

이 인터페이스는 java.io.Serializable 인터페이스를 상속하며 다음과 같은 메소드가있다.

private void readExternal ( java.io.ObjectInput in )

 

private void writeExternal ( java.io.ObjectOutput out )

 

직렬화와 복원에 관한 모든 제어를 할수 있는 강력한 방법이다.

 

Serializable

        |

Externalizable

: writeExternal( ObjectOutput stream ) throws IOException

  readExteranl( ObjectInput stream ) throws IOException

 

객체 직렬화 방식에 대한 완전한 제어

보다 효율적인 직렬화가 가능

 

Externalizable 인터페이스를 구현하는 클래스의 객체가 입출력 될 때는 writeExternal, readExternal 메소드가 그 객체의 전체 입출력을 담당한다.

 

import java.io.*;

public class ExternalizableTest implements Externalizable
    // need to be public to be externalized
{
    int v;
    public void writeExternal(ObjectOutput stream)
        throws IOException
    {   stream.writeInt(v);
        System.out.println("Writing entire object...");
    }
    public void readExternal(ObjectInput stream)
        throws IOException
    {   v = stream.readInt();
        System.out.println("Reading entire object...");
    }

    public static void main(String[] args)
    {
        ExternalizableTest node1 = new ExternalizableTest();
        node1.v = 1;
        try
        {   FileOutputStream outFile
                = new FileOutputStream("test.out");
            ObjectOutput out = new ObjectOutputStream(outFile);
            out.writeObject(node1);
            out.close();

            FileInputStream inFile
                = new FileInputStream("test.out");
            ObjectInput in = new ObjectInputStream(inFile);
            ExternalizableTest inNode
                = (ExternalizableTest) in.readObject();
            System.out.println(node1.v);
            System.out.println(inNode.v);
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

Writing entire object...
Reading entire object...
1
1

 

import java.io.*;

public class ExternalizableTest implements Externalizable
    // need to be public to be externalized
{
    int v;
    public void writeExternal(ObjectOutput stream)
        throws IOException
    {   stream.writeInt(v);
        System.out.println("Writing entire object...");
    }
    public void readExternal(ObjectInput stream)
        throws IOException
    {   v = stream.readInt();
        System.out.println("Reading entire object...");
    }

    public static void main(String[] args)
    {
        ExternalizableTest node1 = new ExternalizableTest();
        node1.v = 1;
        try
        {   FileOutputStream outFile
                = new FileOutputStream("test.out");
            ObjectOutput out = new ObjectOutputStream(outFile);
            out.writeObject(node1);
            out.close();

            FileInputStream inFile
                = new FileInputStream("test.out");
            ObjectInput in = new ObjectInputStream(inFile);
            ExternalizableTest inNode
                = (ExternalizableTest) in.readObject();
            System.out.println(node1.v);
            System.out.println(inNode.v);
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

 

import   import java.io.*;
public class ExternalizableTest implements Externalizable  {
        int i;
        public ExternalizableTest() {
        }
        public ExternalizableTest(int i) {
                this.i=i;
        }
        public void readExternal(ObjectInput stream) throws IOException {
                System.out.println("readExternal()");
                i=stream.readInt();
        }
 
        public void writeExternal(ObjectOutput stream) throws IOException {
                System.out.println("writeExternal()");
                stream.writeInt(i);
        }
        public String toString() {
                return Integer.toString(i);
        }
 
        public static void main(String[] args) throws Exception {
                ExternalizableTest ext1=new ExternalizableTest(1000);
 
                FileOutputStream out=new FileOutputStream("_imsi");
                ObjectOutputStream oos=new ObjectOutputStream(out);
                oos.writeObject(ext1);
 
                FileInputStream in=new FileInputStream("_imsi");
                ObjectInputStream ois=new ObjectInputStream(in);
                ExternalizableTest ext2=(ExternalizableTest)ois.readObject();
 
                System.out.println(ext1);
                System.out.println(ext2);
        }
}

writeExternal()

readExternal()

1000

1000

인자없는 생성자가 반드시 필요 직렬화된 데이터를 읽어들여서 객체를 만들기 위해서는 먼저 인자없는 생성자를 이용한다.

인자있는 생성자만 선언되어있다면 java.lang.NoSuchMethodError가 발생하기 된다.

 

여러 객체의 직렬화

 

객체의 필드가 객체 참조값인 경우, 그 객체 또한 직렬화 된다.

각 ObjectOutputStream, ObjectInputStream객체는 그 객체를 통하여  현재까지 입출력된 객체들을 기억하여 비교함으로써 동일한 객체를 중복되게 입출력 하지 않는다.

--> 객체 참조의 공유 및 원형 참조 문제를 해결

 

ObjectOutputStream의 reset() 메소드를 호출하면, 현재까지 기억된 객체들을 버린다.

 

배열도 Object이므로 객체 직렬화롤 입출력 될수 있다.

 

import java.io.*;

class ObjectSharingTest
{
    public static void main(String[] args)
    {
        MyList node1 = new MyList(1, 11, "first");
        MyList node2 = new MyList(2, 12, "second");
        MyList node3 = new MyList(3, 13, "third");
        node1.next = node3;
        node2.next = node3;
        try
        {   FileOutputStream outFile
                = new FileOutputStream("test.out");
            ObjectOutputStream out = new ObjectOutputStream(outFile);
            out.writeObject(node1);
            out.writeObject(node2);
            out.writeObject(node3);
            out.writeObject( new int[] {1, 2, 3, 4} );
            out.close();

            FileInputStream inFile
                = new FileInputStream("test.out");
            ObjectInput in = new ObjectInputStream(inFile);
            MyList inNode1 = (MyList) in.readObject();
            MyList inNode2 = (MyList) in.readObject();
            MyList inNode3 = (MyList) in.readObject();
            System.out.println( inNode1 );
            System.out.println( inNode1.next );
            System.out.println( inNode2 );
            System.out.println( inNode2.next );
            System.out.println( inNode3 );
            int[] inArray = (int[]) in.readObject();
            for(int i = 0; i < inArray.length; ++i)
                System.out.print( inArray[i] );
            System.out.println();
            in.close();
        }
        catch( Exception e )
        {   e.printStackTrace(); }
    }
}

MyList@2498b5
MyList@25ab41
MyList@e3e60
MyList@25ab41
MyList@25ab41
1234

 

객체 직렬화와 클래스 파일

 

객체가 출력될 때, 그 객체의 정확한 클래스 이름도 함께 객체 스트림에 출력된다.

 

객체가 입력될 때, 자바 가상머쉰은 그 객체의 클래스 파일을 읽어들여서 객체를 생성한다.

 

객체를 읽어들일때 해당 클래스 파일을 찾지 못하면 ClassNotFoundException이 발생한다.

 

객체 직렬화 관련 예외 클래스

 

IOException

  - ObjectStreamException

    - NotSerializableException

    - NotActiveException

    - OptionalDataException

    - WriteAbortException

    - StreamCorruptException

    - InvalidClassException

    - InvalidObjectException


관련글 더보기