메소드를 정의하기 위해서는 몇 가지 요소를 필요로 한다.


반드시 필요한 것은 리턴형과 메소드 이름이다.


나머지 접근자나 한정자 등은 선택적으로 사용한다.


접근자는 메소드의 접근 범위를 결정하며, 생략할 수 있다.


접근자가 생략되면 같은 패키지(디렉토리) 안에 있는


클래스나 객체로부터 접근을 허용하게 된다.


한정자는 특별한 제한이나 메소드의 특성을 결정하며, 역시 생략할 수 있다.


키워드는 다음과 같다.


1. static : 이 메소드는 객체 생성 없이 호출할 수 있는 메소드임을 의미한다.


보통 public 메소드로 정의되며 내부에 있는 인스턴스 변수를 이용할 수 없다.


2. final : 이 메소드는 클래스를 상속할 때 재정의할 수 없음을 의미한다.


3. native : 이 메소드는 C언어나 C++언어로 구현된 메소드임을 의미한다.


따라서, 이 메소드는 {}로 이루어진 몸체를 가지고 있지 않다.


4. synchronized : 이 메소드는 멀티스레드와 관련된 것으로 동시에


하나의 스레드만 호출할 수 있음을 의미한다.

먼저 일반적으로 사용되는 ObjectOutputStream의 구조를 보자

FileOutputStream ostream = new FileOutputStream("t.tmp");
ObjectOutputStream p = new ObjectOutputStream(ostream);

p.writeInt(12345);
p.writeObject("Today");
p.writeObject(new Date());

p.flush();
ostream.close();


이를 보면 대부분 ObjectOutputStream은 FileOutputStream과 같이 사용되는 것을 볼 수 있다.

java에서 구성되고 있는 구조를 보자



이를 보면 ObjectOutputStream은 OutputStream을 상속받고 있으며,

Source를 보면 OutputStream을 상속받으면서도, 동시에 OutputStream을 맴버 변수로 가지고 있다.

때문에 ObjectOutputStream은 OutputStream을 생성자에서 인자로 받을수 있다.

이때 FileOutputStream은 OutputStream을 상속받기 때문에

ObjectOutputStream의 생성자 인자로 들어갈 수가 있다.



위에서 보듯이 ObjectOutputStream은 어떤 객체이든 writeObject()를 수행하면, 모두 기록이 가능하다(단, Serializable하다면)

하지만, Serializable의 내부를 보면 아무것도 없다! 전혀!!!

왜? Serializable은 단지 직렬화 가능한 객체라는 것을 표시하는 것 뿐이기 때문이다.

이는 다른 관점에서 이야기하면 Serializable을 구현한 객체는 직렬화를 위해 그 어떤 작업이나 특성을 가지지 않는다는 것을 의미한다.

그럼 여기서 왜 java는 위와 같은 구조를 가지며, 어떻게 Serializable 표시가 된 객체를 저장하고 읽어올수가 있는것일까?

정답은 Class에 있다.

ObjectOutputStream의 writeObject()는 Object를 인자로 받아

Object의 getName() 메소드를 통해 해당 Class의 이름을 String으로 알아낸다.

이미 눈치빠르신 분은 알겠지만, ObjectInputStream에서 반대로 forName()과 같은 메소드를 통해 다시 Object를 Class화 시킨다.

(이때, UTF-8을 이용하여 정확한 Class명을 얻어올수 있다. 자세한것은 생략)

이와 비슷한 방법으로 Field들도 단위적으로 저장하고 읽어올수 있다.

이런 식으로 하여 Object를 그의 특성에 맞게 저장하고, 다시 그대로 복원이 가능해진 것이다.






그렇다면, 이를 이용해 간단한 SimpleObjectOutputStream을 설계해 보자.

이는 java library처럼 객체내에 reference 변수가 있다면 그까지 저장하진 않고, 단순 객체만을 저장하고, 읽어오도록 하자.

이때 중요한것은, 해당 Object에 대한 정보를 SimpleObjectOutputStream이 알아내서 처리하는 구조가 아닌,

해당 Object에게 OutputStream을 전달하여 해당 Object가 실제로 자기 특성에 맞게 저장하는 모습이 더 낫다고 생각한다.

때문에, Serializable은 writeObject()와 readObject()를 구현해야만 하며, SimpleObjectOutputStream은 내부적으로 해당 객체의 writeObject()를 호출해주어야 한다.



구조는 java library와 전적으로 동일하다.

하지만, 이때 SimpleObjectOutputStream은 인자로 받은 Object의 특성을 모두 알아내어 OutputStream에게 보내는 것이 아니라,

Object에게 OutputStream을 인자로 보내어 Object 스스로 알아서 저장하고 읽어오게 하는 방법으로 설계하면 된다.
◈ 자바언어의 개요

▶ Simple

자바의 개발자들은 C++ 프로그래밍 언어를 기반으로 이 언어를 개발하였지만, 자주 사용하지 않거나 거의 사용하지 않는 객체 지향적인 특징의 많은 부분을 제거하였다. C++언어는 객체 지향적인 프로그래밍 언어이며, 매우 강력한 기능을 제공한다. 그러나 강력한 기능을 제공하도록 설계한 많은 언어와 마찬가지로 오류가 포함되거나 이해하기 어려운 코드를 작성하는 경우처럼 일부 특성은 프로그래머들에게 문제를 야기하고 있다. 소프트웨어 엔지니어링에 필요한 대부분의 비용을 창조적인 활동보다는 코드 정비에 사용하곤 하였다. 그래서 강력하지만 이해하기 힘든 코드보다는 이해하기 쉬운 코드로 변환함으로써 소프트웨어 유지비용을 상당히 절약할 수 있다. 특히 자바는 다음과 같은 면에서 C++(C)와 차이가 난다.

- 자바는 struct와 union, pointer 데이터형을 지원하지 않는다.

- 자바는 typedef나 #define 을 지원하지 않는다.

- 자바에서는 특정 연사자의 처리 방법이 다르며, 연산자 다중 정의(operator overloading)를 허용하지 않는다.

- 자바는 다중 상속(multiple inheritance)을 지원하지 않는다.

- 자바는 명령 인수의 처리에서 C나 C++와는 다르다.

- 자바는 java.lang 패키지의 일부로서 String 클래스를 제공한다. 이것은 C나 C++에서 사용하는 널(null)로 끝나는 문자 배열과는 다른 것이다.

- 자바는 메모리 할당과 회수(쓰레기 수집)를 위한 자동 시스템을 제공하므로, C나 C++에서 사용하는 메모리 할당과 회수 함수를 사용할 필요가 없다.

▶ Object-oriented

객체 지향 디자인을 함으로써 인터페이스의 정의를 명확히 할 수 있고, 재사용 가능한 코드를 만들 수 있게 하였다. 간단하게 말해서, 객체 지향 디자인은 데이터(객체)와 그 데이터와의 인터페이스의 디자인에 초점을 둔다. 또, 객체 지향 디자인은 "plug and play"를 정의하기 위한 메커니즘이다.


▶ Network-Savvy

자바는 HTTP나 FTP같은 TCP/IP 프로토콜을 처리하는 많은 라이브러리를 가지고 있다. 이러한 점들이 C나 C++보다 네트워크 접속을 더 쉽게 할 수 있는 특징이다. 자바 응용들은 프로그래머가 자신의 로컬 파일 시스템에 액세스하는 것과 같은 식으로 URL을 통해서 네트워크에 있는 객체를 열고 접근할 수 있다.

▶ Robust

튼튼한 소프트웨어란 프로그램에 버그가 있더라도 쉽게 문제를 유발하지 않는다는 것을 뜻한다. 튼튼한 소프트웨어를 추구하는 프로그래밍 언어는 보통 프로그래머가 소스 코드를 작성할 때 더 많은 제약을 둔다. 이러한 제한에는 데이터형과 포인터의 사용 등이 포함된다. C언어는 컴파일과 실행 시에, 호환되는 데이터형을 확인하는데 굉장히 느슨하다는 평을 받아왔다. 그러나 C++에서는 강력하게 이 데이터형을 규제하므로, C의 느슨한 접근법을 제한하고 있다.

이 입력에 대한 규제가 더 강력하며, 예를 들어 프로그래머는 자료 변환에서 임의의 정수를 포인터로 변환할 수 없다. 또한 자바는 포인터 연상을 지원하지 않는 대신에 배열을 지원한다. 이러한 단순한 접근법을 통해 C 프로그래머의 메모리 내 임의의 영역 접근을 일부 제거하였다. 특히 자바는 프로그래머가 포인터를 통해 메모리를 겹쳐 쓰거나 다른 데이터를 손상시키는 것을 허용하지 않는다.


▶ Secure

자바는 분산 네트워크 환경에서 사용되는 것을 전제로 만들어졌다. 데이터 접근을 제한하기 위해 공용키(public key) 암호화 기법을 자바에 포함시켰다. 포인터에 대한 자바의 제한은 허용하지 않는 메모리에 개발자가 접근하는 것을 금지한다. 자바는 이러한 방식을 통해 보안성이 더 놓은 소프트웨어 환경을 제공한다.

▶ Architecture Neutral

자바는 네트워크 상의 응용들을 지원하기 위해 만들어졌다. 대개 네트워크는 다양한 CPU와 OS 아키텍쳐를 가지고 있다. 자바 응용이 네트워크의 어떤 상황에도 실행되게 하기 위해 컴파일러는 아키텍처 중립적 객체 파일 포맷을 만들어낸다. 또한, 네트워크뿐만 아니라 단일 시스템 소프트웨어 배포에도 유용하다.

자바 컴파일러는 특별한 컴퓨터 아키텍처와 관계없는 바이트코드 명령어를 생성시킴으로써 이러한 것을 처리한다.

▶ Portable

아키텍처 중립적이라는 말은 이식성을 가지고 있다는 것을 포함한다. 그러나 이식성이 높을 경우 단점은 하드웨어가 수치 연산을 해석하는 방식에 문제가 있을 수 있습니다. C나 C++에서 소스 코드는 하드웨어 플랫폼에 따라 약간 다르게 실현되는데, 이것은 해당 플랫폼이 수치 연산을 하는 방식이 다르기 때문이다. 자바에서는 이러한 면을 단순하게 구현하였다. 자바에서 정수형인 int는 부호를 사용한 2의 보수인 32비트 정수이다. 실수형인 float는 IEEE754표준에 정의한 32비트 부동 소수점 수이다.


▶ High Performance

자바의 바이트코드는 해석할 수 있기 때문에, 때로는 특정 하드웨어 플랫폼을 위해 직접 컴파일하고 실행하는 것만큼 성능이 뛰어나지는 않다. 자바의 컴파일 과정에서는 바이트코드를 특정 하드웨어 플랫폼을 위한 기계어 코드로 바꾸는 옵션이 포함되어 있다. 이것을 사용하면, 전통적인 컴파일이나 불러오기 과정과 같은 효과를 제공한다. Sun Microsystems는 자사의 테스트 결과, 바이트코드를 이러한 기계어 코드로 변환하는 성능은 C나 C++ 프로그램을 직접 컴파일 하는 것과 차이가 없다고 발표한 바 있다.

▶ Multithreaded

자바를 이용할 경우 한 번에 여러 작업을 수행할 수 있는 애플리케이션을 작성할 수 있다. 자바는 C.A.R. Hoare의 모니터와 조건 패러다임을 바탕으로 다중스레드 이벤트를 허용하는 시스템 루틴에 기반을 두고 있기 때문에, 프로그래머들은 프로그램 내에서 실시간 상호 작용할 수 있다.

▶ Dynamic

부모 클래스가 바뀌면, 완전히 재컴파일해야하는 C++ 코드와는 달리, 자바는 이러한 종속성을 완화시키는 인터페이스 메소드를 사용한다. 그 결과 자바 프로그램들은 종속적인 클라이언트 객체에 영향을 미치지 않고 객체 라이브러리에 새로운 메소드나 인스턴스 값을 추가할 수 있다.
Java에서 Serializable 이라는 Interface가 있다.

이 Interface는 객체 직렬화인데, 이는 어떤 구조화된 객체를

Bytes Stream으로 전송하는 것을 말한다.

Serializable Interface를 구현한 Class는 직렬화가 가능하여

writeObject(Object obj) 나 readObject(Object obj) 등을 통해

전송이 가능하다.

Serializable의 예를 보도록 하자

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();
}
}
}


이 예제는 MyList Class가 Serializable Interface를 상속받지 않은 Class인

NonSerial 이라는 Class를 상속 받고 Serializable을 구현하였기에

NonSerial Class에 있는 v1 변수의 값은 기록되지 않고,

MyList에 있는 v2 변수와 next 변수만이 전송되어 기록되어지는 모습을 보인다.

MyList의 내부적인 모습은 다음과 같다.



이때 transient로 선언이 된 v3는 Serializable Interface를 구현한 MyList Class의

멤버 변수 임에도 불구하고 Serializable 특성을 가지지 않게 되어

실제 writeObject()나 readObject()함수를 사용하여도 전송되지 않는다.

따라서 위에 MyList를 실행한 결과는

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

이 된다.

또한 "test.out"의 파일을 열어보면 다음과 같은 기록이 보인다.

ы ?sr ?MyList?熬[?f? ?I ?v2L ?nextt ?LMyList;xp ?sq ~ ?sq ~
p

위에 MyList라는 문자가 듬성듬성 보인다.

이를 이용해 읽어들일때에는 다시 MyList를 생성하여 그 객체에 채워 넣은뒤 리턴하게 된다.



이때 주의 해야 할 점은 Serializable을 이용하여 전송을 할경우

위의 예처럼 Node1만을 writeObject()하여도 Node1,2,3가 모두

연달아 전송이 된다는 것이다. 이는 Node1에 있는 next 변수가 node2를 가리키고 있기때문에 따라가서 실제 객체를 저장하는 것이며

읽을때에도 또안 readObject()를 수행하면 자동으로 연결되어 있는 객체는 모두 read 된다.

유의 할 것!!!

+ Recent posts