java korea lectures

131
System.out.println("Thread of Class A is runnign now..."); }}} 한한 한한 한한한한한 한한 한한한 한한 한한한 한한한 한한 한한한 한한한 한한 한한한한한한 한한 한한 한한. public class Test { public static void main(String[] args) { Thread t = new Thread() {// 한한한한한 한한 public void run() { System.out.println("Thread is running now.."); } };// 한한한한한 한한한 한한한 ; 한 한한 한한 t.start(); } } (2) Runnable 한한한한한한 한한한한 한한 Runnable 한한한 한한한한한한 Thread 한 한한한 한한한한 한한한한한 한한한한 한한 Runnable 한한한한한한 한한 한한 Thread 한 한한한 한한 한한한한 한한한 한 한한. 한한한한 한한한 한 한한 한한한 한한한 한한한 Runnable 한한한한한한 한한한한한 한한한한 한 한한한한 한한한한한 Thread() 한한한한 한한한 한한 한한한한. 한 한한한한한 start()한 한한한한 한한한 Thread 한한한 한한한 한한 한한한 한 한한한 start()한 한한한한한 한한. public class Test { public static void main(String[] args) { Thread t = new Thread(new A()); t.start();

Upload: nony-a

Post on 07-Aug-2015

43 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Java Korea lectures

System.out.println("Thread of Class A is runnign now...");

}}}

한편 위의 프로그램을 더욱 간단히 하기 위하여 쓰레드 구동 부분을 다음과 같이 익명클래스로 만들 수도 있다.

public class Test {

public static void main(String[] args) {

Thread t = new Thread() {// 익명클래스 시작

public void run() {

System.out.println("Thread is running now..");

}

};// 익명클래스 실행문 마감을 ; 로 해야 한다

t.start();

}

}

(2) Runnable 인터페이스를 구현하는 방법

Runnable 이라는 인터페이스에 Thread 와 관련된 클래스와 메소드들이 정의되어 있어 Runnable 인터페이스를

불러 쓰면 Thread 와 관련된 각종 기능들을 수행할 수 있다. 쓰레드를 만드는 두 번째 방법이 있는데 이것은

Runnable 인터페이스를 구현한다고 선언하고 이 클래스의 인스턴스를 Thread() 생성자의 인자로 주는 방법이다.

이 방법에서는 start()를 호출하기 위하여 Thread 타입의 객체를 하나 만들고 그 객체의 start()를 실행하여야 한다.

public class Test {

public static void main(String[] args) {

Thread t = new Thread(new A());

t.start();

Page 2: Java Korea lectures

}

}

class A implements Runnable {

public void run() {

System.out.println("Thread of Class A is runnign now...");

}

}

위에서 객체 A 가 앞의 (1)의 경우와 달리 자동으로 start() 메소드를 상속받지 못하고 있으므로 Thread 타입의 객체

t 를 만든 후 t 의 start()를 호출하는 것을 알 수 있다.

위에서 Thread 객체 생성문 Thread t = new Thread(new A()) 는 다음의 표현과 같다.

Runnable r = new A();// 타겟 객체

Thread t = new Thread(r);

위와 같은 경우에 객체 r 을 쓰레드 t 의 target 이라고 한다.

한편 애플릿에서 init() 메소드 내에서 start()를 호출하는 경우에는 다음과 같이 target 으로 this 를 사용할 수 있다.

Page 3: Java Korea lectures

public void init() {

Thread t = new Thread(this);

t.start();

}

참고로 Runnable 인터페이스는 구현해야 할 메소드로 한 하나 run() 만을 포함하고 있다.

위에 소개한 Runnable 구현 방식도 내부클래스를 사용하여 다시 작성할 수 있다.

public class Test {

public static void main(String[] args) {

Thread t = new Thread(new A());

t.start();

}

static class A implements Runnable {

public void run() {

System.out.println("Thread of Class A is runnign now...");

Page 4: Java Korea lectures

}

}

}

한편 (1)에서는 new A().start()와 같이 start()를 직접 호출하였는데 여기서는 A 객체를 target 으로 하여 Thread 객체

t 를 먼저 만든 후 t.start()를 구동하고 있다. 이렇게 해야 하는 이유는 Runnable 은 start() 메소드를 제공하지 않기

때문이다.

위의 내부클래스를 사용하는 방식은 다시 익명클래스를 사용하는 방법으로 고쳐 쓸 수가 있다.

public class Test {

public static void main(String[] args) {

Runnable r = new Runnable() {

public void run() {

System.out.println("Thread is runnign now");

}

};// 익명클래스 실행문 마감을 ; 로 해야 한다

Thread t = new Thread(r);

t.start();

Page 5: Java Korea lectures

}

}

지금까지는 run() 이 정의되어 있는 객체의 쓰레드를 구동하는 방법만을 설명하였다. 그러나 다른 객체에 있는

run() 메소드를 구동하려면 target r 자리에 다른 객체의 참조를 쓰면 된다.

(1)과 (2)에서 소개한 쓰레드 생성법 두가지를 정리하면 다음과 같다.

1) Thread 의 하위클래스를 사용하고 run()의 내용을 중복구현

2) Runnable 을 구현하는 클래스를 선언하고 이 객체의 인스턴스를 Thread() 생성자의 인자(target)으로 전달

자바는 다중 계승, 즉 둘 이상의 상위 클래스로부터 동시에 계승받는 것을 허용하지 않는다. 따라서 앞의 1)의

방법은 Test 가 이미 Thread 라는 클래스로부터 한번 계승을 받았기 때문에 TestThread 가 또 다른 클래스로부터

계승을 받을 필요가 있을 때 다중 계승이 되어 오류가 발생한다. 그러나 2)의 방법은 Test 가 쓰레드를 사용할 수

있으면서 필요하다면 동시에 다른 클래스로부터 계승을 받을 수 있다는 장점이 있다.

기타

쓰레드는 다음과 같은 메소드들을 제공한다.

String getName()// 쓰레드의 이름을 리턴

void setName(String s)// 쓰레드의 이름을 지정

Page 6: Java Korea lectures

아래는 쓰레드에 이름을 주어 초기화 한 후에 그 이름을 화면에 출력하도록 위의 예제에서 main() 부분을 고친

것이다.

public static void main(String[] args) {

Runnable r = new A();

Thread t = new Thread(r, "FirstThread");

t.start();

System.out.println("Thread name = " + t.getName());

}

출력:

Thread name = FirstThread

Thread of Class A is running now...

쓰레드의 종료

run() 메소드가 리턴되면 쓰레드도 종료되는데 이때 쓰레드가 dead 되었다고 한다. 이러한 dead 쓰레드를 다시

실행시키려면 start()를 다시 호출하여 run() 메소드가 실행되도록 하여야만 한다. 한편 어떤 쓰레드가 stop()을

호출하면 이 쓰레드는 바로 dead 가 된다.

t.stop();// 외부에서 쓰레드 t 를 죽일 때

Page 7: Java Korea lectures

Thread.currentThread().stop();// 쓰레드가 스스로 죽을 때

쓰레드의 상태

쓰레드 객체에 start()를 호출하면 쓰레드가 바로 실행되는 것이 아니라 "new thread" 상태에서 "alive" 상태로 간다.

alive 상태 내부에서도 이 쓰레드가 현재 실행중인가 아닌가에 따라 다시 runnable 또는 not runnable 상태로

나뉘어지게 된다. 그리고 쓰레드가 종료되면 dead 상태로 간다. 즉 크게 보면 쓰레드는 아래와 같은 상태 변화를

격게 된다.

new thread --> alive --> dead

alive 상태에 있는 동안은 (runnable 상태이든 not runnable 상태이든) isAlive() 메소드 호출에 대해 true 를 리턴한다.

alive 상태 내에서도 runnable 이 아닌 상태를 다음과 같이 여러 세부적인 중간상태와 ready 상태로 다시 나눌 수

있다. 이 중간상태와 ready 상태들은 모두 어떤 변화를 기다리는 상태이며 항상 ready 를 거쳐서만 runnable 상태

즉, CPU 의 서비스를 받는 상태로 갈 수 있다.

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

↓ ↓ ↑

not runnable --> ready --> runnable

( waiting,

suspend,

asleep,

Page 8: Java Korea lectures

blocked )

그림: alive 상태 내부

위의 그림에서 상태의 이동 원칙은 다음과 같다. ready 상태에서는 thread scheduler 에 의해서 runnable 상태로만 갈

수 있고, runnable 상태에서는 ready 상태 또는 not runnable (즉, 모든 중간상태)로 갈 수 있다. 중간 상태에서는

runnable 상태로 직접 갈 수는 없고 반드시 ready 상태를 거쳐서만 갈 수 있다.

runnable 상태에서 ready 상태 또는 not runnable (즉, 모든 중간상태)로 가는 경우를 다음과 같이 나눌 수 있다.

1) thread scheduling 에 의한 이동

2) 특정 메소드 호출에 의한 이동

1) thread scheduling 에 의한 이동

먼저 쓰레드의 우선순위에 대하여 설명하겠다. 모든 쓰레드는 우선순위를 갖는데 스케줄러가 ready 상태에 있는

쓰레드들을 runnable 상태로 바꿀 때 즉, 실행시킬 때 이 우선순위를 참조한다. 높은 우선순위의 쓰레드를 먼저

서비스 하며 동일한 우선순위의 쓰레드들은 "랜덤"하게 선택한다. (즉, 오래 기다렸다고 먼저 runnable 이 된다는

보장은 없다.)

우선순위는 1 부터 10 까지의 값을 가지는데 (큰값이 높은 우선순위임) 초기의 디폴트 우선순위 값은 5 이다.

우선순위는 쓰레드를 처음 만들 때(즉 생성자에서) 임의의 값으로 지정할 수는 없으며 일단 쓰레드를 디폴트

우선순위로 생성한 후에 setPriority() 메소드를 이용하여 변경하여야 한다. (우선순위의 확인은 getPriority()로

한다.)

Page 9: Java Korea lectures

예를들어 쓰레드 우선순위를 하나 올리는 코드 부분은 다음과 같다.

int oldP = MyThread.getPriority();

int newP = Math.min(oldP+1, Thread.MAX_PRIORITY);

MyThread.setPriority(newP);

쓰레드 클래스가 제공하는 상수로 MAX_PRIORITY 는 10, MIN_PRIORITY 는 1, NORM_PRIORITY 는 5 의 값을

갖는다. 보통 사용자의 입력을 처리하는 처리 쓰레드에 높은 우선순위를 주며 배치작업을 하는 쓰레드는 낮은

우선순위를 주는 것이 일반적이다.

(주의: 높은 우선순위를 갖는 쓰레드와 낮은 우선순위를 갖는 쓰레드가 공존하고 있을 때 낮은 우선순위를 가진

쓰레드가 계속 실행의 기회가 없는 것은 아니다. 단 처리시간이 전체적으로 적게 배정될 뿐이다. 쓰레드에 실제로

우선순위가 적용되는 구체적인 현상은 플랫폼마다 다를 수 있다.)

2) 특정 메소드 호출에 의한 이동

다음과 같은 메소드를 사용하면 프로그램에서 쓰레드의 상태를 바꾸게 할 수 있다.

yield()

쓰레드를 스스로 ready 상태로 이동시키는 메소드이다. 어떤 쓰레드가 yield()를 호출한 때 다른 쓰레드들이 ready

상태에서 기다리고 있었으면 그 쓰레드가 먼저 실행될 수 있으며 기다리는 쓰레드가 없었으면 이 쓰레드가 바로

다시 runnable 상태로 갈 수 있다. yield()는 어떤 쓰레드가 CPU 시간을 독점할 우려가 있을 때 사용되는데 OS 에

Page 10: Java Korea lectures

따라서 yield()를 반드시 필요로 하는 경우도 있고 (예: MS 윈도우), yield()를 호출하지 않아도 OS 가 자동으로

CPU 독점을 제한하는 경우도 있다 (예: UNIX). yield()는 static 메소드이므로 직접 호출할 수 있다.

try {

t.yield()

} catch (InterrruptedException e) { }

suspend()

쓰레드가의 동작을 강제로 멈추게 하는데 사용된다. 쓰레드는 즉시 suspend 상태로 가며 여기서 reaume() 호출을

받아야만 ready 상태로 갈 수 있다. (이 두 메소드는 non-static 이다.) suspend()는 스스로 부를 수도 있고 다른

쓰레드에서 부를 수도 있으나 resume()은 다른 쓰레드에서 불러줄 수 밖에 없다 (자신은 suspend 되어 있으므로

호출이 불가능하므로).

sleep()

쓰레드가 지정된 시간 동안 동작을 멈추게 하는데 다음과 같이 ms 단위 또는 ns 단위로 멈추는 시간을 조정할 수

있다. (이것은 static 메소드이다.) 지정된 시간이 지나면 이 쓰레드는 바로 실행되는 것이 아니라 ready 상태로

간다.

sleep(long millisec);// InterruptedException 을 throw

sleep(long milliSec, int nanoSec);// InterruptedException 을 throw

sleep()에서는 InterruptedException 예외를 받아서 나올 수 있다.

Page 11: Java Korea lectures

try {

t.sleep(10)// sleep 기본 단위는 ms

} catch (InterrruptedException e) { }

block()

예를들어 네트웍으로부터 데이터 수신을 기다리는 경우에 데이터가 도착할 때까지 오래 기다릴 수도 있다.

이와같이 I/O 에 오랜시간을 기다리게 되면 자바는 자동으로 이 쓰레드를 block 상태로 보낸다. 나중에 데이터를

읽을 수 있어 해당 I/O 가 이루어지면 쓰레드는 ready 상태로 가게 된다. 한편 쓰레드는 synchronized 로 선언된

코드부분의 monitor 를 lock 하지 못하는 경우도 block 이 될 수 있다. (뒤에 설명함)

기타 쓰레드가 제공하는 일반적이 메소드는 다음과 같다.

isAlive()// 쓰레드가 살아있는지 확인

join() // 다른 쓰레드가 종료하면 바로 시작하도록 대기함

예를 들어 어떤 쓰레드 nowt 가 현재 실행중인 쓰레드이면 nowt.join()을 호출한 곳에서 프로그램이 기다리다가

쓰레드 nowt 가 종료하면 프로그램이 그곳에서부터 계속된다.

nowt.join();

한편 join() 될 때까지 기다리는 최대시간을 지정할 수도 있다.

Page 12: Java Korea lectures

nowt.join(long milliSec, int nanoSec);

스케쥴러의 동작 모드

자바에서는 스케줄러가 다음의 두가지중 하나로 구현된다.

- pre-emptive(선점형) 모드

- time-sliced 형

pre-emptive(선점형) 모드에서는 어떤 쓰레드가 자신이 하던일을 마치거나 중간 대기상태로 되면 ready 상태에

있던(즉, 기다리던) 다른 쓰레드들 중 높은 우선순위의 것부터 runnable 이 되는 방식이다(예: Solaris). 한편 어떤

경우에는 쓰레드가 실행도중에도 (즉, runnable 상태에서) ready 로 강제로 내려올 수도 있는데 이 경우는 다음과

같다.

- block 될 I/O 를 요구한 경우

- 더 높은 우선순위의 쓰레드가 실행되려고 하는 경우

한편 time-sliced 형은 round-robon 방식이라고도 하는데 일정한 시간단위로 쓰레드를 돌아가면서 동작시키는

방식이다. (예: 매킨토시)

쓰레드 사용 예제

Page 13: Java Korea lectures

다음은 쓰레드를 두 개 만드는 예로 클래스 A 의 생성자에서 쓰레드를 만든다. 각 쓰레드는 평균 500ms 간격으로

화면에 자신의 쓰레드이름을 출력한다.

public class Test {

public static void main(String[] args) {

new A("FirstThread");

new A("SecondThread");

}

}

class A implements Runnable {

Thread t;

String threadName;

public A(String s) {

t = new Thread(this);

this.threadName = s;

t.start();

Page 14: Java Korea lectures

}

public void run() {

while(true) {

System.out.println(" I am " + threadName);

try {

t.sleep((int) (Math.random()*1000.0));

} catch (InterruptedException e) { }

}

}

}

위의 프로그램에서는 각 쓰레드가 자신의 이름을 화면에 출력한 후에 sleep()을 호출하여 0 과 1 초 사이의 랜덤한

시간만큼을 sleep 하고 있다(평균 500ms).

다음은 쓰레드 동작시간 관리용으로 별도의 쓰레드를 만들어 사용하는 예이다. 아래의 프로그램에서는 1 초동안

250ms 간격으로 I am FirstThread 를 4 번 출력하고 다시 1 초동안 250ms 간격으로 I am SecondThread 를 4 번

출력하는 프로그램이다. 프로그램 실행결과는 다음과 같다.

I am FirstThread

Page 15: Java Korea lectures

I am FirstThread

I am FirstThread

I am FirstThread

I am SecondThread

I am SecondThread

I am SecondThread

I am SecondThread

I am FirstThread

. . .

아래에서 쓰레드 t3 는 시간을 조절하기 위하여 사용한 더미 쓰레드이다.

public class Test {

public static void main(String[] args) {

Thread t1 = (new A()).m("FirstThread");

Thread t2 = (new A()).m("SecondThread");

Thread t3 = new Thread("Dummy");// 타이머용 쓰레드

t1.start();

Page 16: Java Korea lectures

t2.start();

while(true) {

try {

t2.suspend();

t3.sleep(1000);// 1 초간 휴식

t2.resume();

t1.suspend();

t3.sleep(1000);// 1 초간 휴식

t1.resume();

} catch (InterruptedException e) { }

}

}

}

class A implements Runnable {

Thread t;

String threadName;

Page 17: Java Korea lectures

public Thread m(String s) {

t = new Thread(this);

this.threadName = s;

return t;

}

public void run() {

while(true) {

System.out.println(" I am " + threadName);

try {

t.sleep(250);

} catch (InterruptedException e) { }

}

}

}

Page 18: Java Korea lectures

동기화(Synchronized)

어떤 변수를 여러 쓰레드가 공동으로 엑세스하는 경우에 한 쓰레드가 이 변수를 사용하여 중요한 처리를 하는

도중에 다른 메소드에 의하여 이 변수의 값이 변하는 경우가 발생할 수 있다. 이와같이 여러 쓰레드들이 어떤

데이터를 공유하는 경우에 발생하는 문제를 피하기 위하여 동기화와 기능을 제공한다. 멀티쓰레드 환경에서

여러 쓰레드간의 동기화를 제공하는 기법으로 다음과 같은 두가지를 배운다.

1) 모니터의 사용

2) wait()와 notify()의 사용

1) 모니터의 사용

자바에서 모든 객체는 flag 를 가지고 있는데 이것을 lock flag 로 사용할 수 있다. 즉, 어느 코드블록을 (또는

메소드전체를) synchronized 로 선언한 경우에 이 객체의 lock flag 를 점유한 쓰레드만 이 동기화 부분을

실행하도록 하여 어느 순간에 한 쓰레드만 이 코드블록을 엑세스하게 한다.

모든 객체는 monitor 를 가지고 있다. 어떤 쓰레드가 모니터를 잡는 때는 sychronized 로 선언된 메소드 또는

코드블록 부분을 실행하게 되는 것이다. 즉, 이 모니터의 소유권을 한 쓰레드만 잡을 수 있으며 이때 다른

쓰레드들이 이 객체의 sychronized 메소드들을 접근하는 것을 block 시키는 것이다.

다시말하면 모든 객체는 고유의 lock(즉 모니터)를 가지고 있으며 어느 한 순간에 이 lock 을 엑세스하는 쓰레드는

하나뿐이다. 만일 이 lock 을 다른 쓰레드가 잡고 있으면 이 쓰레드는 block 상태로 가고 그 lock 이 사용가능해졌을

때 ready 상태로 가게 된다. 한편 lock 을 잡고 있던 쓰레드는 synchronized 코드블록을 다 처리하고 나면

자동적으로 lock 을 놓도록 되어있다.

메소드 또는 코드블록을 synchronized 로 세트하는 방법은 다음과 같다. 먼저 메소드 전체를 synchronized 로

Page 19: Java Korea lectures

세트하려면 메소드 선언문에서 다음과 같이 한다.

public synchronized void myMethod() {

// 보호할 코드

}

메소드 전체가 아니라 메소드내의 코드블록을 synchronized 로 세트하는 방법은 다음과 같다.

synchronized(this) {

// 보호할 코드

}

이때는 synchronized()으 인자에 어떤 객체의 모니터를 잡을지를 명시해야 하는데 자신의 객체의 모니터를

잡으려면 this 를 쓰고 다른 객체의 모니터를 잡으려면 그 객체의 참조를 쓴다.

synchronized(otherObj) {

// 보호할 코드

}

2) wait(), notify()의 사용

Page 20: Java Korea lectures

앞에 설명한 동기화는 둘 이상의 쓰레드가 동시에 어떤 코드블록을 접근함으로써 발생하는 문제는 해결해 준다.

그러나 쓰레드간의 어떤 통신을 제공하지는 못한다. 예를들어 한 쓰레드가 어떤 동기화 부분의 사용을 완료한

경우 이 사실을 다를 쓰레드에게 알려주기 위하여 wait()와 notify()를 사용한다. 어떤 쓰레드가 wait()를 호출하면

waiting 상태로 가게 되는데 이 상태에서 ready 로 나오려면 notify()나 notifyAll()을 받아야만 한다.

모든 객체는 두 개의 큐를 가지고 있는데 하나는 앞의 1)에서 설명한 synchronized 처리를 위한 lock 큐이고 하나는

쓰레드간 통신을 위한 waiting 큐이다. wait() 는 객체가 쓰레드의 실행을 중지하고 waiting 큐에 들어가서 어떤

상태변화(notify()의 발생)를 기다리게 하는 것이며, notify()는 이 객체를 사용하려고 기다리고 있는 쓰레드들 중

한 쓰레드에게 이 객체에 어떤 변화가 발생했음을 (즉, 모니터를 잡을 수 있음을) 알려 그 쓰레드가 waiting 큐에서

나와 계속 실행하도록 하는 것이다. 만일 모든 기다리는 쓰레드에게 이 사실을 동시에 알리려면 notifyAll()을

호출해야 한다.

여기서 주의할 것은 notify 는 횟수는 축적되지는 않는다는 것이다. 즉, notify 는 한번 알리면 그만이고 과거의

notify 발생 횟수는 기록되지 않는다. 임의의 synchronized 코드를 가지고 있는 객체는 모두 monitor 객체가 될 수

있으며 wait()와 noify()를 사용할 수 있는 것이다.

쓰레드의 상태 변화의 관점에서 설명하면 임의의 모니터 객체는 wait() 호출로 waiting 상태로 가며 notify()의

호출로 ready 상태로 가는 것이다.

중요한 것은 wait()와 notify()는 모두 synchronized 코드부분 내에서만 호출할 수 있다는 것이다. 만일 synchronized

블록 외부에서 wait()를 호츨하면 (즉, 모니터를 잡지 않고 호출하면) 자바는 IllegalMonitorStateException 을

발생한다. 보통 wait()는 while 문 내에서 호출하게 되며 notify()는 블록내에서 아무곳에서나 해도 된다. wait()는

interrrupt 를 받으면 InterruptedException 을 발생하므로 이를 처리해주어야 한다.

try {

wait();

Page 21: Java Korea lectures

} catch(InterrruptedException e) { }

이상의 내용을 정리하면 다음과 같다.

wait()를 호출하는 순간 호출한 쓰레드에서 이루어지는 것은

- CPU 의 사용을 중단 (waiting 상태로 간다.)

- 객체 lock 을 놓는다.

- 객체의 waiting 큐로 들어간다.

notify()를 호출하는 순간 호출한 쓰레드에서 이루어지는 것은

- 어떤 쓰레드 하나가 waiting 큐에서 나와 ready 상태로 간다.

- notify()를 받은 쓰레드는 반드시 monitor lock 을 얻어야만 계속 진행될 수 있다. (즉, notify 되었다고 바로

쓰레드가 실행되는 것은 아니다.

gc(garbage collection)

gc 는 프로그래머가 메모리 관리를 하지 않아서 발생할 수 있는 메모리 부족 현상을 피하도록 하는 것이 1차적인

목적이라고할 수 있다. 한편 gc 는 dangling refernce 문제를 피할 수 있도록 하는데 이것은 다른 곳에서 참조하고

잇는 객체를 프로그래머가 실수로 지우는 일이 없도록 함으로써 (c 의 delete 같은 것이 없으므로) 다른

객체참조가 예측불가능한 내용을 가리키는 일이 발생하지 않도록 한다. 즉, 다른 참조변수가 참조하고 있는

Page 22: Java Korea lectures

객체는 절대로 gc 하지 않는다.

기본적으로 프로그램에서 더 이상 참조하지 않는 객체는 자동으로 gc()의 대상이 된다. 예를들어 어떤 메소드를

호출하여 결과를 얻었고 더 이상 이메소드를 호출하지 않는다면 그 메소드의 내부 변수들은 사용이 종료된

것이다. 또는 어떤 객체참조 변수를 null 로 한 경우에도 이 참조가 가리키던 객체는 사용하지 못하게 되며 이

객체는 이제 gc 의 대상이 된다.

그러나 gc 는 수시로 일어나는 것은 아니며 메모리가 부족하다고 판단될 때에 동작한다. 그 이유는 gc 를

처리하는데 시간이 필요하므로 시스템의 성능를 떨어뜨리지 않기 위하여 가능한 gc 는 최소한으로 실행하는

것이다. 따라서 gc 는 보통 낮은 우선순위를 갖는 백그라운드 작업으로 처리된다. 프로그래머는 언제 gc 가

실행되는지도 알 수 없으며 명시적으로 gc 를 구동시킬 수도 없다. 또한 gc 가 어떤 순서로 즉 어떤 객체부터

메모리를 회수하는 지도 알 수 없다.

그러나 프로그래머가 다음과 같은 방법으로 gc 가 즉시 실행되도록 할 수는 있다.

1) 현재시각을 나타내는 객체를 얻는다.

2) 이 객체의 gc()를 실행한다.

Runtime rt =new Runtime.getRuntime();

rt.gc();

어떤 객체에 gc 가 이루어지기 직전에 이 객체에 대하여 어떤 작업을 하고 싶으면 finalize()를 이용하여 원하는

동작을 지정해둘 수가 있다. finalize()는 자바가 gc()를 하기 직전에 객체를 상대로 부르는 메소드인데 각 객체에

Page 23: Java Korea lectures

대하여 finalize()를 "한번만" 구동한다. finalize()는 Object 클래스에 정의되어 있으며 사용법은 다음과 같으며

예외를 발생한다.

protected void finalize() throws Throwable

따라서 finalize()는 try-catch 문 내에서 사용하여야 한다. 프로그래머는 finalize() 메소드에서 이 객체와 관련되어

추가로 사용하던 메모리 등을 내놓거나 이 객체에서 오픈한 파일을 확실하게 닫게 할 수 있다. 한편 finalize()

에서는 반드시 상위클래스의 finaize()도 구동하여야 한다.

(java 49)

protected void finalize() throws Throwable {

try {

close(file);

. . .

} finally {

super.finalize();

}

}

finalize() 메소드 내에서 try-catch 를 사용할 수 있지만 처리안된(uncaught) 예외는 단순히 무시된다.

Page 24: Java Korea lectures

finalize()를 protected 로 선언해야 하는 것은 외부의 사용자가 임의로 이 메소드를 구동할 수 없도록 하기

위해서이다.

이때 메모리가 부족하면 gc 이 실행되고 그 이후에도 메모리가 부족하면 OutOfMemoryError 가 발생한다.

자바로 구현되지 않은 코드가 있고 이것이 자바의 gc 를 이용할 수 없는 상황에서는 finalize() 메소드를 사용하는

것이 필요하다.

9. 애플릿

아래는 Simple 이라는 애플릿을 정의하는 예로 init(), start(), stop(), destroy() 메소드가 호출되는 때를 설명하고

있다.

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

public class Simple extends java.applet.Applet {

. . .

public void init()

{ . . . }/* 애플릿이 로드되거나 재로드될 때 처음으로 수행되는 코드 */

public void start()

Page 25: Java Korea lectures

{ . . . }/* 애플릿이 로드되거나 사용자가 재방문했을 때 수행되는 코드 */

public void stop()

{ . . . }/* 애플릿이 있는 페이지를 떠나거나 브라우저를 중지했을 때 *

/* 수행되는 코드 */

public void destroy()

{ . . . }/* 로드를 취소하였을 때(cleanup) 수행되는 코드 */

}

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

java.applet 패키지

자바에서 애플릿 프로그램을 작성하기 위해서는 반드시 Applet 클래스로부터 계승된 클래스를 사용하여야

하는데 Applet 클래스는 java.applet 패키지에 속해 있다. 참고로 Applet 클래스는 Panel 클래스의 하위클래스이다.

Applet 클래스의 주요 메소드는 다음과 같다.

void destroy();/* 애플릿의 종료 */

String getAppletInfo();/* 애플릿의 정보를 얻는다 */

URL getCodeBase();/* 코드 URL 을 얻는다 */

URL getDocumentBase();/* 도큐멘트 URL 을 얻는다 */

Page 26: Java Korea lectures

Image getImage(URL);/* URL 로부터 이미지를 얻는다 */

Image getImage(URL, String); /* URL 로부터 name 에 해당하는 이미지를 얻는다 */

void init();/* 애플릿 초기화 */

void play(URL);/* 해당 URL 의 오디오 파일을 연주한다 */

void start();/* 애플릿 시작 */

void stop();/* 애플릿 멈춤 */

void showDocument(URL);/* URL 의 도큐멘트를 보여준다 */

AppletContext getAppletContext();/* 애플릿 환경의 핸들을 얻는다 */

}

애플릿은 반드시 다음을 만족하여야 한다.

import java.applet.* ;

public class Test extends Applet {

생성자를 호출한 다음에 바로 init()을 호출하도록 되어있다 (다음에는 start())

애플릿은 Panel 에서 상속받았으므로, 그래픽 환경을 기본으로 하고 있다. 따라서 화면에 출력을 하려면 그래픽

처리를 하여야 한다. 즉 println()으로 바로 출력되지 않으며 글씨를 쓰려면 paint() 메소드를 사용한다.

Page 27: Java Korea lectures

public void paint(Graphics g ) {

g.drawString("Hello", 25, 25) ;

}

init()

한번만 호출된다. 애플릿이 처음 생성되고 브리우저에 로드될 때 호출되는 메소드이다.

start()

init() 이후에 애플릿이 동작하기를 시작할 때 구동되는 메소드이다.

stop()

애플릿이 동작을 멈출 때 구동된다. 브라우저 아이콘화된 경우, 다른 URL 로 접속된 경우에 호출된다.

AWT thread

화면 디스플레이를 처리하는 별도의 쓰레드

appletviewer

<html>

Page 28: Java Korea lectures

<head>

</head><body>

<applet code=HelloWorld.class width=100 height=100>

</applet>

</body>

</html>

자바를 처리할 수 있는 브라우저는 applet tag 사이에 있는 일반 HTML 은 무시한다.

archive

자바 1.1 에서는 여러 파일을 압축하여 한번에 다운로드할 수 있도록 jar(Java ARchive)를 지원하는데 브라우저가

서버로부터 다운로드해야 할 jar 파일이 있을 때 사용한다. JAR 를 사용하면 파일(바이트코드)의 다운로드 속도를

상당히 개선할 수 있으며 파일의 관리도 매우 편리하게 된다.

archive = "mysamples.jar, morefiles.jar"

주의할 것은 archive 파일이 있더라도 애플릿이 처음 실행될 때 필요한 code 는 반드시 지정되어야 한다. (class

파일)

codebase

Page 29: Java Korea lectures

code 가 가리키는 파일을 찾는 기준 디렉토리를 나타낸다(URL 타입으로 표현). 즉 이곳부터 시작하여 상대적인

위치를 code 에 나타내면 된다. 예를 들어 아래의 코드는 MyWork/java/samples/HelloWorld.class 의 바이트 코드를

읽어온다.

<applet code=samples/HelloWorld.class codebase=MyWork/java width=100 height=100>

codebase 를 지정하지 않으면

alt

브라우저가 applet tag 자체는 이해하나 내용을 처리하지는 못하는 경우 즉, 자바 에플릿을 수행할 수는 없을 때

애플릿 내용 대신 보여줄 문장을 지정한다. 이 대신 보여지는 문서는 애플릿이 나타났을 자리에 보여진다.

alt = "Use Netscape 4.0 or more version"

주의: <applet> 태그는 반드시 <body>와 </body> 사이에 와야 한다.

PARAM 태그

HTML 페이지로부터 애플릿에게 파라미터를 전단하는데 사용된다.

<param name=X value=ABC>

value 의 값은 String 이어야 한다. " " 몰 묶을 수도 있고 안 안묶어도 된다.

Page 30: Java Korea lectures

파라미터 값을 읽을 때에는 getParameter() 메소드를 사용한다.

String getParameter(String `)

위에서 name 은 대소문자를 구분하지 않으나 (즉 섞어서 사용해도 되나) getParameter()이 리턴한 String 값은

대소문자를 구분하며 param 태그의 value 에 지정된 값이 그대로 나타난다. 참고로 String 값은 해당 정수로

바꾸려면 parseInt()를 사용한다. 예를들어 파라미터 name 이 AGE 인 경우 이를 방아 정수값으로 변환하려면

다음과 같이 한다.

int age = 0;

age = Interger.parseInt(getParameter("AGE"));

getParameter() 호출시 해당 파라미터가 HTML 페이지 내에 없으면 null-pointer exception 이 발생한다.

주의: <param> 태그는 반드시 <applet>와 </applet> 사이에 와야 한다.

Applet 이 제공하는 URL 용 메소드

getDocumentBase()

현재 나타나고 있는 페이지의 HTML 문서가 있는 디렉토리를 URL 타입으로 리턴한다.

Page 31: Java Korea lectures

getCodeBase()

애플릿 코드를 가져온 곳의 디렉토리를 URL 타입으로 리턴한다.

예)

Applet 클래스가 AWT 패키지의 컴포넌트 클래스, 컨테이너 클래스, Pannel 클래스의 하위클래스이므로 이들이

제공하는 각종 메소드들을 이용할 수 있다는 것을 기억해야 한다. 예를 들면 컴포넌트로서 그림그리기 기능,

이벤트 처리 기능 등을 가지며 컨테이너로서 레이아웃 매니저 등을 가진다.

import java.awt.* ;

import java.net.* ;

public class A extends java.applet.Applet {

String s;

TextArea ta;

public void init() {

ta = new TextArea(5, 30);

setLayout(new BorderLayout());

add(ta, "South");// 아래쪽에 TextArea 추가

s = getParameter("param2");// html 문서의 파라미터 읽기

Page 32: Java Korea lectures

}

public void paint(Graphics g) {

g.drawString(s, 25, 25) ;

URL u2 = getCodeBase();

URL u1 = getDocumentBase();

ta.append("CodeBase = "+ u2.toString()+ "\n");

ta.append("DocumentBase = "+ u1.toString());

}

}

다음과 같은 html 문서를 만들어야 하며 appletviewer A.html 을 입력한다.

<HTML>

<applet code=A.class width=300 height=300>

<param name="param2" value="Hello World Applet !">

</applet>

</HTML>

Page 33: Java Korea lectures

위에서 param2 는 " "를 안해도 된다.

화면에 나타나는 것은 다음과 같다.

이 URL 객체를 사용하여 동화상이나 사운드를 다운로드 받을 수 있다.

Image getImage(URL base, String target); // target: 이미지를 담고 있는 파일 이름

AudioClip getAudioClip(URL base, String target);

import java.awt.* ;

import java.lang.applet.Applet ;

public class ImageTest extends Applet {

Image duke;

public void init() {

duke = getImage(getDocumentBase(), "duke.gif") ;

}

public void paint(Graphics g) {

Page 34: Java Korea lectures

g.drawImage(duke, 25, 25, this) ;

// this(애플릿)는 image observer 역할을 한다.

}

}

오디오

public void paint(Graphics g) {

g.drawString("AudioTest", 25, 25) ;

play(getDocumentBase(), "cuckoo.au") ;

}

HTML 화일은 애플릿에게 환경구성 정보를 전달할 수 있으며 이를 위하여

<param> 태그를 사용한다.

<param name=image value="myImage.gif">

public class ImageTest extends Applet {

image im;

Page 35: Java Korea lectures

public void init() {

URL url = getDocumentBase();

String imageName = getParameter("image");

im = getImage(url, imageName);

}

public void paint(Graphics g) {

g.drawImage(im, 0, 0, this) ;

}

}

10. AWT, 컴포넌트

자바는 화면상에 그래픽으로 어떤 정보를 보여주거나 메뉴를 편리하게 제공하기 위하여 즉, 편리한

GUI(Graphical User Interface)를 지원하기 위하여 Abstract Window Toolkit(AWT) 패키지 java.awt 를 제공한다. awt

패키지를 이용하면 그림을 그리기 위하여 새로 윈도우를 만들 필요 없이 부라우저가 만들어 놓은 화면에 쉽게

그림을 그릴 수 있는데, awt 패키지에 속하는 대표적인 클래스는 다음과 같다.

· java.awt.Graphics: 폰트, 색깔, 다각형 등을 그리는 클래스를 제공

Page 36: Java Korea lectures

· java.awt.Component: 버튼, 메뉴, 대화상자, 리스트박스 등의 GUI 를 제공

· java.awt.LayoutManager: 한 화면(container) 내에 컴포넌트들의 구성을 제공

· java.awt.image: 화면상의 이미지 처리를 제공하는 패키지

한편 키보드 입력, 마우스 입력 등의 이벤트를 처리하기 위해서는 Event 클래스를 사용한다.

java.awt 의 Graphics 관련 클래스 체계 (일부)

Object

-- Graphics

-- Color

-- Event

-- Font

-- Image

-- Toolkit

. . .

java.awt 의 Component 및 Container 관련 클래스 체계 (일부)

Page 37: Java Korea lectures

Object

--MenuComponent

-- MenuBar

-- MenuItem

-- Menu

--Component

-- Button

-- Label

-- List

-- Checkbox

-- Choice

-- Scrollbar

-- TextComponent

-- Container

-- Panel

-- Applet

Page 38: Java Korea lectures

-- Window

-- Frame

. . .

paint()

자바 실시간 환경(runtime environment)은 컴포넌트들이 언제 화면에 그림을 그릴 것인가를 지정해주어야 한다.

그림은 경우에 따라 자동으로 다시 그리는 경우도 있고 사용자의 프로그램에 의해 다시 그리게 되는 경우도 있다.

자바 시스템에는 GUI 를 전담하여 관리하는 "GUI 쓰레드"가 있어 사용자의 각종입력 이벤트를 받아 처리하고

필요한 때에 paint()를 호출하거나 한다. 즉, paint()가 호출되는 방법은 다음과 같이 두가지가 있다.

- GUI 쓰레드에 의한 자동 호출(spotaneous painting)

- 사용자가 명시적으로 호출

자동호출

애플릿에서 init()이 호출된 다음에 paint()가 자동으로 호출된다. 또한 브라우저나 프레임이 나타나게 되었을 때

paint() 메소드가 호출된다. 이러한 자동호출을 GUI 쓰레드가 처리해주는 것이다. GUI 쓰레드가 paint()를

자동호출하는 경우를 정리하면 다음과 같다.

Page 39: Java Korea lectures

- 화면에 처음 나타날 때

- 아이콘에서 확대될 때

- init() 이 실행된 이후 (애플릿에서)

- 전에 보였던 애플릿으로 다시 접속될 때

사용자 호출

프로그램에서 사용자가 paint()를 호출하는 것인데 paint()는 인자로 Graphics 객체를 필요로 한다. - 이러한

그리기를 저수준(low-level)의 그리기라고도 한다.

public void paint(Graphics g)

한편 사용자가 그림을 "다시" 그릴 때에는 paint()를 호출하면 안되고 반드시 repaint()를 호출하여야 한다. 그런데

repaint()를 호출하였다고 바로 그림을 그리는 것은 아니며 AWT 가 update()를 구동할 수 있도록 특정 플래그를 1

로 세트하여 둔다. update()는 컴포넌트가 제공하는데 컴포넌트인데 화면을 (배경색으로) 지우고 다시 paint()를

부른다.

repaint() --> update() --> paint()

한편 자신의 그림을 단순히 다시그리는 경우(예를들면 화면 크기조정)에는 AWT 가 update()를 부르지 않고 바로

paint()를 호출한다.

update() 메소드가 하는 일을 정리하면 다음과 같다.

Page 40: Java Korea lectures

- 화면을 배경색으로 지운다.

- 그림 그리는 색을 foreground 색으로 지정한다.

- Graphics 객체를 인자로 하여 paint()를 호출한다.

앞의 화면을 지우지 않고 계속 겹쳐 그리고 싶으면 다음과 같이 update()를 override 해두면 된다.

public void update(Graphics g) {

paint(g);

}

Graphics 클래스

java.awt.Graphics 클래스

Graphics 는 선긋기, 텍스트 쓰기, 페인팅 등 각종 그림을 그리는 메소드를 제공하는 abstract 클래스이다. 아래는

Graphics 클래스의 선언과 Graphics 가 제공하는 메소드의 일부분이다. Graphics 객체의 사용이 끝나면 dispose()를

호출하여 윈도우의 자원을 되돌려 주어야 한다.

paint() 메소드의 인자로 전달되는데 사용되는 Graphics 클래스는 선그리지, 원그리기, 색지정 등의 여러 가지

저수준의 그림을 그릴 수 있는 메소드들을 제공한다.

Page 41: Java Korea lectures

하는데 Graphics 클래스가 제공하는 기능을 크게 나누면 다음과 같다.

색지정

폰트지정

그리기 및 색칠하기

클리핑

setColor(Color); /* 현재 칼라를 지정한다 */

dispose();/* 객체의 사용 종료 */

drawLine(int, int, int, int);/* 지정한 좌표에 선을 긋는 메소드 */

drawRect(int, int, int, int);/* 사각형을 그린다 */

drawString(String, int, int);/* 지정한 위치에 글자를 찍는다 */

Font getFont(); /* 현재 사용중인 폰트를 알아낸다 */

setFont(Font); /* 지정한 폰트로 바꾼다 */

Graphic 객체는 그림을 그리기 위한 색지정, paint 모드, 폰트, 클리핑 지역 등을 기억하기 위하여 상태정보를

가지고 있는데 이 정보를 임의로 지정하거나 읽을 수 있다.

Page 42: Java Korea lectures

색지정

setColor() 사용하며 13 개의 기본 색이 있다. (Color.red 등) 아래와 같이 색을 임의로 만들 수도 있다.

public void paint(Graphics g) {

int red=80, green=200, blue=0;// 각각 0 과 255 사이의 정수를 사용

Color c = new Color(red, green, blue);

g.setColor(c);// 먼저 그린 그림의 색을 바꾸지는 않는다

. . .

폰트지정

setFont()를 사용한다.

그리기

그림을 그리려면 Graphics 객체가 필요한데 예를들어 paint() 메소드내에서 그림을 그릴 때에는 paint()의 인자로

Graphic 객체를 awt 가 넘겨주므로 이것을 사용하면 된다. (update() 메소드도 Graphics 객체를 인자로 받는다.)

그러나 paint() 메소드를 사용하지 않고 그림을 그리려면 Graphic 객체를 다음과 같은 방법으로 얻어야 한다.

- 만일 Graphic 객체를 하나 가지고 있으면 이의 사본을 create()로 만들면 된다

- 어떤 컴포넌트에서 작업중이면 이 컴포넌트의 getGraphic() 메소드를 호출한다.

Page 43: Java Korea lectures

한편 그래픽 객체가 그림을 그릴 수 있는 대상은 다음과 같다.

컴포넌트

Image

프린터

모든 종류의 컴포넌트에 그래픽을 그릴 수 있으나 실제로 사용되는 것은 Applet, Canvas, Frame, Panel 뿐이다.

drawString("Hello", 100, 200)// (x,y)=(100, 200) 위치부터 글자를 쓴다. (좌하지점)

drawLine(x1, y1, x2, y2)

drawPolyLine(polyXs, polyYs, dim)

drawRect(left, top, width, height)

fillRect(left, top, width, height)

drawPolygon(xArray, yArray, dim)

fillPolygon(xArray, yArray, dim)

drawArc(left, top, width, height, start_arc, arc_add)// 3 시방향이 0 도

fillArc(left, top, width, height, start_arc, arc_add)// add_arc 는 음수도 가능하다.

Page 44: Java Korea lectures

drawImage(Image im, int x, int y, ImageObderver obs)

drawString("Hello", 100, 200) 에서 (x,y)=(100, 200) 위치에는 글자 'H'자의 왼쪽 아래 부분이 온다.

Image 처리

다른 그림 파일로부터 이미지를 받아오려면 getImage()를 사용한다.

getImage(URL url);// 그림 파일을 가리키는 URL 사용

getImage(URL url, String file);// url 디렉토리의 file 파일을 사용

위와같이 getImage()를 호출하면 Image 객체를 얻는데 이때 실제 그림 데이터까지 확보된 것은 아니다. 즉,

데이터는 아직 네트웍으로부터 전송되어 오지 않았어도 getImage() 메소드는 바로 Image 객체를 리턴한다.

그림을 그리기 위하여 drawImage()를 호출하면 이때 이미지가 다운로드되기 시작한다. 이렇게 이미지가

다운로드되는 상태를 보고 받는 객체가 바로 drawImage() 내에 지정한 ImageObserver 객체가 된다. 실제로

ImageObserver 는 인터페이스이고 Component 객체가 이것을 구현하고 있다. 따라서 임의의 컴포넌트는 (

애플릿을 포함하여) ImageObserver 가 될 수 있다.

한편 빈 이미지 객체를 새로 만들수도 있는데 컴포넌트 클래스의 creatImage() 메소드를 사용하면 된다.

Image im = creatImage(x, y);// x, y 는 픽셀단위 크기

아래는 애플릿에서 Image 객체를 새로 만들고 Graphics 객체를 통하여 빨간색 박스를 그리는 방법을 소개하고

Page 45: Java Korea lectures

있다.

publc class MyImage extends Applet {

Image im;

public void init() {

im = createImage(250, 250);

Graphics gim = im.getGraphics();

gim.setColor(Color.red):

gim.fillRect(100, 100, 50, 50);

}

public void paint(Graphics g) {

g.drawImage(im, 30, 30, this);

}

}

클리핑

그래픽이 그림을 그릴 수 있는 영역(이 영역을 clip region 이라 한다)을 지정하는 것으로 클립영역 외부에 그림을

그리는 것을 제한하는 것이다. 모든 그래픽 객체는 clip region 을 가지고 있다. (JDK 1.1 에서는 이 영역이

Page 46: Java Korea lectures

사각형이나 1.2 에서는 임의의 모양을 가질 수 있다. ) 애플릿의 클립영역은 기본적으로 애플릿의 크기가 된다.

클립영역을 바꾸려면 setClip() 메소드를 사용한다.

setClip(x, y, width, height)

참고:

getBounds();// 클립영역의 크기를 나타내는 Rectangle 객체를 리턴

컴포넌트

앞에서 설명한 Graphics 객체를 이용한 저수준의 그림그리기의 수준을 넘어서 체크 박스, 리스트, 메뉴 버튼,

메뉴등을 쉽게 작성할 수 있는데 이때 각 목적에 맞는 컴포넌트 객체를 선택하여 이용한다. 이러한 목적으로

사용되는 클래스는 java.awt.Component 클래스와 java.awt.MenuComponent 클래스의 하위클래스들로 구성된다.

콤포넌트와 컨테이너

컴포넌트는 버튼, 라벨과 같이 GUI 를 위하여 화면에 보이는 그림 단위이며 이것이 화면에 보이려면 어떤

컨테이너에 실려야 한다. (이때 add() 메소드를 사용한다.) 즉, 컨테이너는 다수의 컴포넌트 또는 다른 컨테이너

자체를 포함하는 화면 단위이다.

예) 프레임 생성

import java.awt.*;

Page 47: Java Korea lectures

public class Test {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

f.setSize(100,100);// 없으면 크기가 0*0 인 프레임 생성

f.add("South", new Button("Test"));

f.setVisible(true);// 반드시 있어야 화면에 나타난다

Dimension d = f.getSize();// f 의 크기를 리턴

System.out.println("width = " + d.width);

System.out.println("height = " + d.height);

System.out.println("size = " + d.toString());

}

}

위 프로그램을 실행하면 버튼을 하나 가진 프레임이 나타나고 다음과 같은 내용이 출력된다.

width = 100

height = 200

Page 48: Java Korea lectures

size = java.awt.Dimension[width=100,height=200]

클래스 체계

Object

--Component

-- Button

-- TextArea

-- TextField

-- List

-- Vanvas

-- Container

-- Panel

-- Applet

-- Window

-- Frame

-- ScrollPane

Page 49: Java Korea lectures

. . .

===== 이벤트 간단히 소개 ====

Component

&#8729; TextArea

글자의 입출력용 화면을 만든다. 생성자는 다음과 같다. (4 개중 2 개만 소개)

TextArea(String title, int rows, int columns);// 제목과 크기 지정

TextArea(rows, int columns);

각 글자의 입력 이벤트를 처리하기 위하여 TextListener 를 구현하여야 하다. 입력의 전체 종료를 처리하려면

리턴키 확인으로는 안되고 별도의 종료버튼을 만들어야 한다. (리턴키는 일반 데이터 입력으로 처리되므로)

TextArea ta = new TextArea("multiple lines", 5, 30) ;

ta.addTextListener(this);

add(ta) ;// 현재의 컨테이너에 추가

Page 50: Java Korea lectures

필요하면 스크롤바가 자동으로 나타난다.

만일 읽기 전용으로 하려면 다음과 같이 setEditable() 메소드를 사용하여 지정할 수 있다.

ta.setEditable(false) ;

&#8729; TextField

한줄의 텍스트를 입력받을 수 있다.

TextField tf = new TextField("Single line", 30) ;

tf.addActionListener(this);

add(tf) ;

한줄의 입력을 처리하기 위하여 리턴키가 입력되면 ActionListener 를 구동할 수 있다. 즉 actionPerformed()

메소드를 통하여 텍스트의 입력을 처리하게 된다. 한편 키입력을 하나하나를 각각 처리하려면 ActionListener

외에 TextListener 를 추가로 등록하여야 한다.

TextArea 와 마찬가지로 다음과 같이 읽기전용으로 세트할 수 있다.

setEditable(false) ; // 읽기 전용으로 할 수 있다.

TextArea 와 달리 스크롤바는 나타내지는 않는다. 따라서 좌우를 보려면 방향키를 눌러야 한다.

Page 51: Java Korea lectures

&#8729; 버튼

ActionListener 인터페이스에 정의되어 있다.

Button b = new Button("test");// 버튼 생성

b.addActionListener(this);// 등록

add(b);

버튼이 눌리면 actionPerfromed()가 자동으로 호출된다.

public void actionPerfromed(ActionEvent ev) {

System.out.printlm(((Button) ev.getSource()).getActionCommand());

}

getActionCommand()는 디폴트로 버튼의 이름을 리턴한다. 이 값을 바꾸려면 다음과 같이 setActionCommnad()를

사용한다.

Button b = new Button("test");

b.setActionCommnad("Test butten is pressed");

Page 52: Java Korea lectures

&#8729; Canvas

Canvas 는 빈 공간을 나타내는데 키 입력받기, 그림그리기 등 모든 종류의 입력을 받아들일 수 있다. 초기의

크기는 0x0 이므로 반드시 크기를 세로 세트하여야 한다. 필요에 따라 KeyListener, MouseListener,

MouseMotionListener 객체들을 추가하여야 한다.

주의: 캔버스에서 키입력을 받으려면 미리 requestFocus()를 호출하여야 한다.

&#8729; 체크박스

on/off 압력을 받는데 사용되며 ItemListener 인터페이스에 정의되어 있다.

Checkbox c1 = new Checkbox("First", true);

Checkbox c2 = new Checkbox("Second", false);

c1.addItemListener(this) ;

c2.addItemListener(this) ;

add(c1) ;

add(c2) ;

체크박스가 눌리면 itemStateChanged() 메소드가 자동으로 호출되며 어떤 항목이 선택되었는지는

getStateChange() 로 알 수 있다.

Page 53: Java Korea lectures

class MyCheckbox implements ItemListener {

public void itemStateChanged(ItemEvent ev) {

if (ev.getStateChange() == ItemEvent.SELECTED) {

System.out.println(ev.getItem());

}

}

}

위에서 setItem()은 특정 체크박스의 라벨 이름을 얻는다.

&#8729; 라디오버튼 (체크박스 그룹) - 주의: 이것은 컴포넌트 클래스는 아니다.

CheckboxGroup 을 하나 만든 후 체크박스들을 이 그룹 안으로 묶어 두면 라디오 버튼이 된다.

CheckboxGoup cg = new Checkboxgroup();

Checkbox c1 = new Checkbox("First", cg, true);

Checkbox c2 = new Checkbox("Second", cg, false);

c1.addItemListener(this) ;

Page 54: Java Korea lectures

c2.addItemListener(this) ;

add(c1) ;

add(c2) ;

&#8729; Choice

여러 "리스트" 중 하나를 선택하게 하며 ItemListener 인터페이스에 정의되어 있다.

Choice c = new Choice() ;

c.addItem("Ffirst") ;// 리스트 목록에 추가한다.

c.addItem("Second") ;

c.addItem("Third") ;

add(c) ;// 컨테이너에 c 를 추가한다.

&#8729; FileDialog

파일을 선택하는데 사용하는 Dialog 화면으로 미리 포맷이 만들어져 있다. 사용자가 OK 를

누르면 리턴된다.

FileDialog f = new FileDialog(parentFrame, "MyFileDialog");

f.setVisible(true);// 사용자가 입력할 때까지 여기서 기다린다.

Page 55: Java Korea lectures

// 따라서 별도의 이벤트 처리가 필요없다.

String filename = f.getFile();// 파일이름을 받는다.

&#8729; Label

한 라인의 문자열을 단순히 출력만 한다. 스크롤바등 다른 기능을 제공하지 않는다.

Label lab = new Label("test line") ;

add(lab) ;

&#8729; List

각 항목이 String 으로 되어 있는 하나 또는 다수의 리스트를 보이고 이를 선택할 수 있도록 한다. 한번에 보이는

항목의 수를 5 개이며 동시에 여러개의 항목선택을 허용한다(true).

List ls = new List(5, true) ;

단일 클릭은 단순히 항목을 선택만 하고 더블클릭을 하여야 해당 이벤트가 동작한다. 항목수가 많으면 자동으로

우측에 스크롤바가 나타난다. 주의할 것은 List 는 ActionListner 인터페이스가 처리한다. List 의 항목추가는

addItem()으로 한다.

List ls = new List(5, true) ;

ls.addItem("Ffirst") ;// 리스트 목록에 추가한다.

ls.addItem("Second") ;

Page 56: Java Korea lectures

add(ls) ;// 컨테이너에 ls 를 추가한다.

&#8729; ScrollPane

하나의 컴포넌트 (예: Panel) 만을 포함할 수 있으며 독립적으로 이 컴포넌트에게 스크롤을 제공한다. 이벤트

처리는 포함된 ScrollPane 이 아니라 포함된 컴포넌트로부터 받는다.

Frame f = new Frame("Test ScrollPane");

Panel p = new Panel();

ScrollPane sp = new ScrollPane();

p.setlayout(new GridLayout (3, 3));

sp.add(p);

f.add(sp, "Center");

f.setSize(300, 300)

f.setVisible(true);

&#8729; Scrollbar

Scrollbar sb = new Scrollbar(Scrollbar.HORIZONTAL, 625, 25, 600, 700);

Page 57: Java Korea lectures

2) Container Component (4 개)

java.awt.Container

Container 는 앞에 설명한 Component 객체들을 화면에 구성하는데 사용되는 abstract 클래스로 Component 에서

계승된 클래스이다. 아래는 Component 클래스 선언과 Component 가 제공하는 주요 메소드이다. 어떤 Component

를 Container 에 추가하려면 add() 메소드를 사용하며, 화면 구성을 지정하기 위하여 setLayout()메소드를

사용한다.

add(Component);/* 컴포넌트를 추가 */

locate(int , int );/* 컴포넌트위치 지정 */

remove(Component);/* 컴포넌트를 제거 */

setLayout(LayoutManager mgr); /* 화면 배치를 지정 */

컨테이너는 컴포넌트를 담기 위한 그릇인데 컨테이너 자시도 하나의 컴포넌트다 된다. (즉, 다른 컨테이너에

포함될 수 있다.)

Object

--Component

-- Button

-- TextArea

Page 58: Java Korea lectures

-- Container

-- Panel

-- Applet

-- Window

-- Frame

-- ScrollPane

. . .

그러나 Container 는 abstract 클래스이므로 이를 바로 사용할 수 없고 프로그램 작성시에는 Container 로부터 다시

계승된 클래스 Frame, Dialog, Pannel등을 사용하여야 한다.

자바의 모든 GUI 는 Frame 또는 Applet 에 담기는데 애플리케이션에서는 Frame 을 애플리에서는 Applet 을

이용하게 된다. 하나의 GUI 화면을 여러 파트로 기능별로 나눌 때 Panel 을 사용하는데 Panel 에는 또 다른 Panel

들이 계속 포함될 수 있어 복잡한 화면을 구성하도록 할 수 있다.

Applet

setSize(), setBound()를 사용할 수 있다 - 최종 제품에서는 애플릿의 크기를 HTML 에서 지정하여야 한다. setSize()

를 이용한 크기조절 코드는 삭제해야 한다.

Frame

Page 59: Java Korea lectures

Window 에서 상속되었는데 제목과 크기조절 기능이 추가된 창이라 할 수 있다.

public class MyFrame extends Frame {

* 프레임이 화면에 보이려면 setVisible()과 setSize() 가 모두 호출되어야 한다.

* 해당 윈도우를 닫으면 WindowListener 인터페이스의 windowClosing() 메소드가 호출된다.

* 프레임 내에서는 키입력을 바로 받을 수 없으므로 Canvas 나 Panel 을 이용하여야 한다.

(cert 9-25)

Panel

Panel 객체는 Window 나 Frame 객체에 포함(add)되어야만 화면에 보일 수 있다. 즉, 단독으로 화면에 나타나지

못한다. Panel 을 사용하는 이유는 한 프레임의 내부를 다시 여러 특성을 갖는 부분으로 나누고자 할 때이다. 즉,

각각의 Panel 에 서로다른 LayoutManager 를 지정할 수 있다. Panel 은 Panel 을 포함할 수 있다.

f.add(pan) ; // 컴포넌트를 컨테이너 f 에 포함시킨다.

f.setVisible(true) ; // 프레임 f 를 보이게 한다.

Dialog

프레임과 유사하며 단독으로 화면에 보일 수 있다.

Page 60: Java Korea lectures

dialog d = new Dialog(f, "Dialog", false) ; // modal 을 허용하지 않음

d.add("Center", new Label("Hello:)) ;

d.pack() ;

Dialog 는 보통 처음부터 화면에 보이는 용도로 사용하지 않고 어떤 다른 동작의 결과를 알려주는 등의 목적으로

사용한다. (pop up) 이때 다음의 코드가 필요하다. - Dialog 는 특히 재사용하는 것이 유리하다.

d.setVisible(true);

닫기: 확인 버튼 입력 등의 이벤트에 닫을 수 있다.

public void actionPerformed(ActionEvent ev) {

d.setVisible(true) ;

}

또는 Frame 을 닫을 때처럼 해당 윈도우를 닫을 때 WindowListener 인터페이스의 windowClosing() 메소드가

호출되는데 이때 다음을 부르면 된다.

d.setVisible(false);

3) Menu Component (4 개) - MenuComponent 클래스에 속한다. (Component 가 아니라)

Page 61: Java Korea lectures

Menu 는 여러 가지 메뉴를 만드는데 사용된다. 메뉴를 구성하기 위해서는 우선 메뉴가 위치하기 위한 메뉴바가

필요하고 서브메뉴들을 구성하여야 한다. 여기서는 Frame 을 계승하여 TestMenu 클래스를 정의하였다.

자바는 pull-down 과 pop-up 두가지 메뉴를 제공한다. pull-down 메뉴는 menu bar 객체를 통하여 지원되며 menu bar

는 Frame 을 통하여만 보여진다.

메뉴는 일반 컴포넌트 처럼 임의의 장소에 만들 수 없고 menu container(즉, menu bar)에만 메뉴를 만들 수 있다.

메뉴바(menu bar)를 프레임에 추가하려면 setMenuBar() 메소드를 사용한다. 그런 이후에 메뉴를 메뉴바에 추가할

수 있고, 메뉴항목 (또는 하위메뉴)를 메뉴에 추가할 수 있다.

특정메뉴 하나를 Help 메뉴로 등록할 수 있다. setHelpMenu(Menu) 메소드를 사용하여 등록하며 보통 메뉴바의

우측에 등록된다.

MenuBar

모든 (트리구조의) 메뉴의 시작점이 되는데 Frame 객체에만 포함될 수 있다.

Frame f = new Frame("Menu creat");

MenuBar mb = new MenuBar() ;

f.setMenuBar(mb) ;

Menu(메뉴)

pull-down 메뉴의 기본이 되는 하나의 컬럼을 말한다. 메뉴는 메뉴바에 또는 다른 메뉴에만(하위메뉴로) 등록될

수 있다.

Page 62: Java Korea lectures

Frame f = new Frame("Menu creat");

MenuBar mb = new MenuBar() ;

f.setMenuBar(mb) ;

Menu m1 = new Menu("File") ;

Menu m2 = new Menu("Edit") ;

Menu m3 = new Menu("Help") ;

mb.add(m1) ;

mb.add(m2) ;

mb.add(m3) ;

mb.setHelpMenu(m3) ;

주의: MenuBar 자체는 listener 를 지원하지 않는다.

ActionListener 를 메뉴 객체에 추가할 수도 있지만 일반적인 방법은 아니다.

MenuItem

메뉴의 각 항목을 말한다. 이것은 반드시 메뉴 객체에 추가되어야 한다. 메뉴의 선택을 알기 위하여 MenuItem 에

ActionListener 를 구현하여 사용한다.

Page 63: Java Korea lectures

Menu m1 = new Menu("File") ;

MenuItem mi1 = new MenuItem("Save");

MenuItem mi2 = new MenuItem("Load");

mi1.addActionListener(this);

mi2.addActionListener(this);

m1.add(mi1);

m1.add(mi2);

m1.addSeperator();

CheckboxMenuItem

항목을 체크하여 선택할 수 있는 메뉴이다. 이 항목은 ItemListener 를 구현하여야 한다.

Menu m1 = new Menu("File") ;

MenuItem mi1 = new MenuItem("Save");

CheckboxMenuItem mi2 = new CheckboxMenuItem("Check Items");

mi1.addItemListener(this);

Page 64: Java Korea lectures

mi2.addItemListener(this);

m1.add(mi1);

m1.add(mi2);

PopUpMenu

ActionListener 인터페이스에서 처리된다.

컴포넌트 환경의 설정

1) setSize()

컴포넌트의 크기를 지정한다.

setSize(int width, int height);

컴포넌트의 크기를 알려면 getSize()를 호출한다. 이것은 Dimension 객체를 리턴하는데 width 와 height 의

멤버변수에 가로, 세로의 크기가 들어있다.

컴포넌트의 위치와 크기를 동시에 바꿀 수 있는데 다음과 같이 setBound()를 이용한다. (setBound()는 가능한

사용하지 말고 뒤에서 설명할 배치관리자에게 배치를 맡기는 것이 낫다.)

Page 65: Java Korea lectures

setBound(int x, int y, int width, int height);

주의! x, y 등의 값은 모니터 화면상의 절대 위치가 아니라 컴포넌트가 속한 컨테이너 내부에서의 상대적인

위치이다.

2) 색지정

컴포넌트가 화면에 나타나는 색을 바꿀 수 잇는데 다음과 같은 메소드가 사용된다

setForeground(Color c)// 컴포넌트 윤곽의 색을 바꾼다

setBackground(Color c)// 배경색을 바꾼다.

위에서 색을 지정하는 인자 c 로는 java.awt.Color 클래스에 있는 임의의 색을 사용할 수 있다.

Color.red, Color.blue 등. 한편 c 로 다음과 같이 임의의 혼합배색을 사용할 수도 있다.

int r=80, g=200, b=0;// r, g, b 는 0 과 255 사이의 정수를 사용

Color c = new Color(r, g, b);

3) 폰트지정

setFont() 메소드를 사용하여 폰트를 지정할 수 있는데 폰트의 종류로는 java.awt.Font 클래스의 폰트를 사용할 수

있다. 한편 폰트는 다음과 같이 선택한다.

Page 66: Java Korea lectures

Font f = new Font("TimesRoman", Font.PLAIN, 14);

폰트 이름으로는 TimesRoman 외에도 Dialog, Helvetica, Courier 등이 있다.

폰트 스타일로는 Font.PLAIN 외에도 Font.BOLD, Font.ITALIC, Font.BOLD+Font.ITALIC 등을 사용할 수 있다.

한편 폰트의 종류는 다음과 같이 getFontList()로 알아볼 수 있다

Frame f = new Frame("Printing") ;

ToolKit t = f.getToolKit() ;

String [] str = t.getFontList();// ??

* 만일 어떤 컴포넌트의 색 또는 폰트를 명시적으로 지정하기 않으면 바로 상위의 컨테이너의 색 또는 폰트의

세팅을 따른다

4) setEnabled(boolean b)

b 가 true 이면 컴포넌트가 정상적으로 화면에 나타나고 사용자의 입력에 응답하며 false 이면 화면에 흐려지고

사용자의 입력에 응답하지 않는다.

5) setVisible(boolean b)

Page 67: Java Korea lectures

컴포넌트가 (특히 Frame) 화면에 나타나게 하거나 안보이게 한다.

6) Printing

java.awt.Graphics 객체를 이용한다.

Frame f = new Frame("Printing") ;

ToolKit t = f.getToolKit() ;

PrintJob job = t.getPrintJob(f, "MyPrintJob", null);

Graphics g = job.getGrapics() ;// Graphic 객체를 프린터와 연결

f.printComponent(g) ;// 각 페이지마다 Graphic 객체를 얻는다

// f.printAll(g) ; 한번에 페이지를 만든다

g.dispose() ; // 프린터로 송출

job.end() ; // 프린트 작업 종료

Layout Manger (배치관리자)

컨테이너에 컴포넌트를 배치하거나 크기를 조절하기 위하여 배치관리자 (Layout Manager) 객체의 도움을 받는다.

배치관리자가 자동으로 배치한 컴포넌트의 위치와 크기가 마음에 안들때는 물론 사용자가 임의로 이를 바꿀 수

있는데 이때 setLocation(). setSize(), setBound()등의 메소드를 이용한다.

Page 68: Java Korea lectures

Panel 이나 Frame 같은 Container 를 하나 만들면 디폴트 Layout Manger 가 자동으로 생성된다. 이러한 시스템이

기본으로 제공하는 배치관리자는 컴포넌트를 적절히 배치하는데 이것이 프랫폼마다 조금씩 다를 수가 있다.

따라서 보다 "정확히 컴포넌트를 배치"하기 위하여 사용자는 명시적으로 배치관리자를 새로 지정하여 사용하는

것이 일반적이다.

자바의 awt Toolkit 은 다음과 같이 5 가지의 배치관리자를 제공한다.

FlowLayout- Panel 과 Applet 의 디폴트 배치관리자

GridLayout

BorderLayout- Window, Frame, Dialog 의 디폴트 배치관리자

CardLayout

GridBagLayout

1) FlowLayout

모든 자바 컴포넌트는 가능한 최소의 크기를 갖기를 원한다. (지정된 글자들이 보일 수 있기에 충분한 가능한

작은 크기). 배치관리자 관리자는 이 크기(이를 preferred size 라 한다.)를 최대한 존중하며 컴포넌트들을 배치하나

필요시는 이 크기를 바꿀 수도 있다. 물론 컴포넌트들의 간격(gap)도 배치관리자가 알아서 재정한다. (디폴트값은

5 픽셀)

(guide 287)

Page 69: Java Korea lectures

디폴트로 중앙정렬이다. 우측정렬을 하려면

setLayout(new FlowLayout(FlowLayout.RIGHT));

2) GridLayout

preferred size 를 무시하고 바둑판식 배치를 한다. 애플릿 화면의 크기를 바꾸면 각 컴포넌트의 크기도 비례하여

바뀐다. 만일 선언된 row * column 수보다 더 많은 컴포넌트를 이 컨테이너에 add() 하면 row 의 수는 유지하면서

column 을 임의로 추가한다.

3) BorderLayout

preferred size 를 어느정도 존중한다. (위두가지 방식의 중간)

5 개의 영역으로 나눈다.

setLayout(new BorderLayout());

add(new Button("a"), BorderLayout.NORTH);

add("South", new Button("2"));

동서남북, 중앙의 각영역을 가득채우기 위하여 BorderLayout 은 컴포넌트의 모양이 늘어나게 한다. 단 Checkbox

는 크기가 일정하며 모양이 늘어나지 않는다.

(예)

Page 70: Java Korea lectures

f.setLayout(new FlowLauout()) ; // 배치관리자를 하나 만든 후 프레임 f 에 설치

한다.

f.pack() // 프레임내 컴포넌트들을 적절히 배치한다.

11. 이벤트 처리

Event 클래스: 어떤 사건이 발생했는지를 설명하는 클래스

이벤트소스: 이벤트를 발생한 객체

이벤트처리기(Event Handler): 이벤트가 발생하였을 때 Event 객체를 받아 처리하는 메소드

예를들어 버튼을 마우스로 누르면 ActionEvent 라는 종류의 이벤트 객체가 발생하며 이때 이벤트소스는 버튼이

된다.

JDK1.1 이후 : delegation event model

컴포넌트 구현시 각 이벤트마다 지정된 이벤트 처리 루틴(이를 리스너라 한다)을 컴포넌트 외부에 별도로

구현해두고 이 리스너에게 이벤트를 처리하도록 위임(delegation)하는 방식이다. 어떤 컴포넌트에 이벤트가

발생하면 각 이벤트를 처리하기 위한 이벤트처리기(event handler)를 포함하고 있는 리스너(listener)에게 이

이벤트가 전달된다. 여기서 리스너란 Listener 인터페이스를 구현하고 있는 클래스를 말하는데 컴포넌트의

구현과 리스너가 별도로 구현된다는 것이 특징이다.

Page 71: Java Korea lectures

각 이벤트는 종류별로 지정된 listener 에게만 보고된다. 따라서 모든 발생가능한 이벤트에 대하여 해당 리스너

클래스를 미리 선언하고 필요한 내용을 구현(implements)해 두어야

한다.

이벤트처리 방식

1) 리스너 구현 (위임 방식)

2) 직접 구현

3) 어댑터 이용

1) 리스너구현 방식

이벤트 소스 ----> 이벤트 리스너

(처리를 위임)

이벤트가 발생하였을 때 이를 처리하는 메소드로 Event 객체를 받아서 처리하게 된다.

(참고)

JDK1.0 에서는 hierarchal event model 을 사용하는데 이 방식에서는 어떤 이벤트를 받은 컴포넌트가 이를 처리할

수 없으면 처리가능할 때까지 계속 상위 컨테이너에게 이벤트를 전달하는 방식이다. 이것은 구조가 단순하나

이벤트의 필터기능을 구현하기가 어렵고 (부피가 큰) handleEvent 메소드를 구현하여야 한다. 즉 대형

Page 72: Java Korea lectures

프로그램이거나 사용자 인터페이스가 복잡한 경우 사용하기가 불편하다.

JDK1.1 에서는 delegation event model 을 사용하는데 이것은 1.0 에 비하여 다음과 같은 장점을 가지고 있다.

- 이벤트의 필터기능(어댑터)를 구현하기가 쉽다.

- 분산형 작업에서 사용하기 편리하다.

- 자바 빈스를 지원하기가 용이하다.

먼저 각 컴포넌트마다 지정된 이벤트 처리 루틴(이를 event listener 라 한다)을 등록해두고 있어 이것이 이벤트를

종류별로 직접 처리하도록 한다. 즉 이벤트 처리루틴이 컴포넌트 외부에 별도로 구현된다. 즉, 이벤트 타입마다

각각 다른 등록용 메소드를 사용하여야 한다.

각 이벤트는 종류별로 지정된 리스너에게만 보고된다. 따라서 모든 이벤트에 대하여 해당 listener 클래스 (또는

인터페이스)를 미리 선언하고 이 listener 클래스를 구현(implements)해 두어야 한다. 물론 각 listener

인터페이스에는 어떤 메소드가 어떤 이벤트들을 받아 처리할지를 정의하고 있다.

어떤 컴포넌트에서 이벤트(예: Action)이 발생하면 컴포넌트는 ActionHandler 에게 이의 처리를 위임하는데

ActionHandler 에는 actionPerformed() 와 같은 메소드내에처 필요한 일을 처리하게 된다.

자바 1.1 이상에서 제공하는 리스너 방식의 이벤트 처리 절차는 다음과 같다.

1) Listener 인터페이스를 구현할 클래스를 하나 정의한다. Listener 인터페이스에는 다음과 같은 것들이 있다.

ActionListener

Page 73: Java Korea lectures

MouseMotionListener

FocusListener

2) 위에서 만든 클래스의 인스턴스를 만든다.

3) 이벤트가 발생할 컴포넌트에게 다음의 사항을 알려준다.

어떤 타입의 이벤트에 관심이 있는지

어떤 객체가 이벤트를 처리할 것인지 (addXXXListener() 메소드 사용)

아래에서는 버튼 컴포넌트에서 발생하는 Action 이벤트를 MyHandler 라는 Listener 에게 등록하는 프로그램이다.

import java.awt.*;

import MyHandler;

public class A {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

b.addActionListener(new MyHandler());// 등록

Page 74: Java Korea lectures

f.add("Center", b);

f.pack()

f.setVisible(true);

}

}

Button 컴포넌트는 버튼이 눌렸을 때 발생하는 이벤트를 처리할 객체를 등록하기 위하여

addActionListener(ActionListener) 메소드를 제공한다. 이 프로그램에서는 이 핸들러의 이름이 MyHander

클래스인데 이것은 아래와 같이 ActionListener 인터페이스를 미리 구현하고 있어야 한다. (이벤트 종류별로 각각

다른 Listener 인터페이스들이 정의되어 있다.)

import java.awt.event.*;

public class MyHandler implements ActionListener {

public void actionPerfromed(ActionEvent ev) {

System.out.println("action performed . . . ");

}

}

ActionListener 인터페이스의 경우는 actionPerfromed(ActionEvent ev) 메소드 하나만 선언되어 있는데 이것이 바로

Page 75: Java Korea lectures

ActionEvent 를 받는 메소드이다. 인터페이스를 구현한 경우는 모든 메소드를 모두 구현해야 한다. (ActionListener

의 경우는 하나만 구현하면 됨)

이벤트 클래스 체계

최상위의 이벤트 관련 클래스는 EventObject 인데 이것은 java,lang 패키지에 속행 있다.

java.util.EventObjectgetSource() // 이벤트를 발생시킨 객체를 리턴

-- java.awt.AWTEventgetID()// 이벤트의 ID 를 리턴한다

-- ActionEvent// 컴포넌트의 구동시에 발생

-- ItemEvent// List, Choice, Checkbox 에서 아이템 선택시 발생

-- TextEvent// 텍스트가 변경될 때 발생

-- PaintEvent// 컴포넌트가 paint 될 때 발생

-- WindowEvent// 윈도우 작업시 발생 (아이콘화 등)

-- AdjustmentEvent// 스크롤바 등 컴포넌트의 조정시에 발생

-- ComponentEvent

-- ContainerEvent// 컴포넌트를 컨테이너에 추가 또는 삭제시 발생

-- FocusEvent// 컴포넌트가 포커스를 받을 때 발생(마우스 이동)

Page 76: Java Korea lectures

-- InputEvent//

-- KeyEvent// 키보드 입력시 발생

-- MouseEvent// 마우스 동작시 발생

기타 Listener 인터페이스들

이벤트의 종류

Semantic 이벤트: action 이벤트, item 이벤트, dajustment 이벤트

저수준 이벤트:

ActionEvent

버튼누름, 엔터키 입력, 메뉴의 선택, 이스트의 더블클릭

ItemEvent

리스트에서 항목 선택, choice 박스에서 선택, 메뉴의 선택, checkbox 클릭

AdjustmentEvent

스크롤 바의 이동

Page 77: Java Korea lectures

자바는 한 컴포넌트의 한 이벤트에 대하여 여러개의 리스너 동작을 구현하도록 할 수 있다. 즉, 다수의 addListener

를 정의하는 것을 허용한다. 단 어떤 핸들러가 먼저 구동되는지는 알 수 없다. 만일 처리 순서가 반드시 지켜져야

한다면 한 리스너가 다른 리스너를 차례로 구동하는 방식으로 프로그램을 작성하여야 한다.

예)

2) 직접구현 (Explicit Event 처리) 방식

한편 이벤트 처리에는 앞에 설명한 delegation 모델 즉, Listener 객체에게 이벤트 처리를 위임하는 방법외에도

해당 컴포넌트에서 직접 이벤트를 처리하는 다음과 같은 방식도 가능하다.

- 컴포넌트의 하위클래스를 하나 만든다.

- 하위 클래스의 생성자에서 enableEvents(AWTEvent.XXX_RVENT_MASK)를 호출한다.

- procesXXXEvent() 메소드를 정의하며 이 내부에서 super 클래스의 버전을 구동한다.

3) 이벤트 어댑터

지금까지 설명한 방식은 각 리스너의 모든 메소드를 (내용이 없어도) 모두 구현해야 하는 불편함이 있다. 이를

피하기 위하여 즉, 필요한 메소드만 구현하면 되도록 각 리스너 인터페이스별로 어댑터 크래스를 제공하고 있다.

주의: 어댑터 클래스는 상속받아서 사용한다.

Page 78: Java Korea lectures

class A extends ActionListenerAdaptor {

// 관련 메소드를 모두 구현하지 않아도 되어 편리하다.

}

이벤트 처리 예제

1) 리스너 방식

import java.awt.*;

import java.awt.event.*;

// 이벤트 등록 클래스

public class A {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

f.add(b, "South");

f.setSize(100, 100);

f.setVisible(true);

Page 79: Java Korea lectures

b.addActionListener(new B());// 등록

}

}

// 이벤트 처리 클래스

class B implements ActionListener {

public void actionPerformed(ActionEvent ev) {

System.out.println("TestButton is pressed ");

}

}

위의 내용을 아래와 같이 한 클래스 내에서 처리할 수도 있다. 그러려면 이 클래스 선언문에 implements

ActionListener 을 구현하여야 하며 addActionListener() 메소드의 인자로 자기자신의 객체를 사용하여야 한다.

import java.awt.*;

import java.awt.event.*;

public class A implements ActionListener {

public static void main(String[] args) {

Page 80: Java Korea lectures

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

f.add(b, "South");

f.setSize(100, 100);

f.setVisible(true);

A a = new A();

b.addActionListener(a);// 등록

}

public void actionPerformed(ActionEvent ev) {

System.out.println(ev.getActionCommand());

}

}

즉, 클래스 A 가 바로 ActionListener 인터페이스를 구현하고 있으므로 이 클래스내에서 ationPerformed()를

구현하면된다. 위에서 getActionCommand()는 이벤트를 발생시킨 컴포넌트의 이름(여기서는 Button 이름인

"Test")을 리턴한다. 한편 이벤트를 발생시킨 객체를 알려면 setSource()를 사용하면 된다.

Object which = ev.getSource();

Page 81: Java Korea lectures

if (which == b) b.setEnable(true);

. . .

2) 상속방식

예를들어 버튼에 관한 이벤트 처리를 하려면 Button 을 상속받은 클래스를 하나 만들고(아래의 예에서는

MyButton) 그 클래스내에서 enableEvent() 메소드(이벤트의 활성화)와 processActionEvent() 메소드(이벤트의 처리)

를 구현해 두는 방식이다. 이방식은 컴포넌트가 위임을 하지 않고 스스로 이벤트를 처리하게 하는 방식이다. 즉

Button 을 상속한 MyButton 이 이벤트의 처리를 해주는 것이다.

import java.awt.*;

import java.awt.event.*;

// 이벤트 발생 클래스

public class A {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

MyButton b = new MyButton("Test");// 새로운 버튼 생성

f.add(b, "South");

Page 82: Java Korea lectures

f.setSize(100, 100);

f.setVisible(true);

}

}

// 이벤트 처리 클래스 Button 을 상속받음

class MyButton extends Button {

MyButton(String s) {

super(s);

enableEvents(AWTEvent.ACTION_EVENT_MASK);

}

public void processActionEvent(ActionEvent ev) {

System.out.println("TestButton is pressed ");

}

}

3) 어댑터 클래스의 사용

Page 83: Java Korea lectures

리스너 방식에서 필요했던 모든 메소드들의 empty 메소드들을 미리 제공함으로써 리스너 인터페이스를

구현하는 클래스에서 불필요한 메소드들을 모두 구현하지 않아도 되게 한다. 특히 어댑터 클래스를 내부

클래스로 사용하면 매우 편리하다.

import java.awt.*;

import java.awt.event.*;

// 마우스 이벤트 발생 클래스

public class A {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

f.add(b, "South");

f.setSize(100, 100);

f.setVisible(true);

b.addMouseListener(new B());

}

}

Page 84: Java Korea lectures

// 이벤트 처리 클래스

class B extends MouseAdapter {

public void mouseClicked(MouseEvent ev) {

System.out.println("TestButton is pressed ");

}

}

한편 위의 예에서 클래스 B 정의 부분을 클래스 A 의 내부로 옮겨 둘 수 있는데 (즉, 클래스 B 를 클래스 A 의

내부클래스로 선언) 그러려면 클래스 B 의 인스턴스를 만들기 전에 클래스 A 의 인스턴스를 다음과 같이 먼저

만들어주어야 한다.

public static void main(String[] args) {

. . .

A a = new A();

b.addMouseListener(a.new B());// 내부클래스 생성자

. . .

또는 위의 마지막 두 문장은 다음과 같이 줄여서 쓸 수도 있다.

Page 85: Java Korea lectures

b.addMouseListener((new A()).new B());

한편 위와 같이 내부클래스 B 의 인스턴스를 만들기 전에 외부클래스 A 의 인스턴스를 만드는 것이 불편하면

다음과 같이 내부클래스를 static 으로 선언하는 방법도 있다.

static class B extends MouseAdapter

물론 이때는 b.addMouseListener() 부분을 다음과 같이 쓸 수 있다.

b.addMouseListener(new B());

위의 방식은 또한 익명클래스를 사용하여 더욱 간단히 만들 수 있다. 즉, addMouseListener() 메소드의 인자로

필요한 리스너 객체를 직접 '(' 과 ')' 내에 구현하는 방안이 가능하다. 이 방식이 가장 간편하다고 볼 수 있다.

import java.awt.*;

import java.awt.event.*;

public class A {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

Page 86: Java Korea lectures

f.add(b, "South");

f.setSize(100, 100);

f.setVisible(true);

final A a = new A();

b.addMouseListener( new MouseAdapter() {

public void mouseClicked(MouseEvent ev) {

a.other();

}

}

);

}

public void other() {

System.out.println("TestButton is pressed ");

}

}

Page 87: Java Korea lectures

한편 위의 프로그램에서는 익명클래스에서 다시 외부의 메소드(other())를 호출하는 것을 보이고 있다. 만일

간단한 작업만 한다면 a.other()를 호출하는 자리에 그 기능을 바로 구현해 두면된다.

아래와 같이 한 클래스에서 모든 것을 처리할 수도 있다. 이때에는 extends MouseAdapter 를 하여야 한다.

(implements MouseListener 를 한 경우에는 MouseListener 의 모든 메소드를 구현하여야 한다.)

import java.awt.*;

import java.awt.event.*;

public class A extends MouseAdapter {

public static void main(String[] args) {

Frame f = new Frame();// 프레임 생성

Button b = new Button("Test");// 버튼 생성

f.add(b, "South");

f.setSize(100, 100);

f.setVisible(true);

A a = new A();

b.addMouseListener(a);

}

Page 88: Java Korea lectures

public void mouseClicked(MouseEvent ev) {

other();

}

public void other() {

System.out.println("TestButton is pressed ");

}

}

위에서는 addMouseListener() 호출시 인자로 이 클래스의 객체 a 를 사용하고 있는데 애플릿을 사용하는 경우는

this 를 쓰면된다.

컴포넌트별 이벤트 처리예

앞에서 버튼을 누르는 것과 마우스의 클릭을 예로 들었는데 이외의 컴포넌트별 발생가능한 이벤트를 아래 표에

정리하였다.

표. 컴포넌트별 발생가능한 이벤트

1) 예를들어 Action 이벤트를 발생시킬 수 있는 경우는 버튼을 눌렀을 때, 텍스트필드, 메뉴, 리스트에서 리턴키를

Page 89: Java Korea lectures

입력하였을 때이다.

actionListener 크래스는 다음과 같은 메소드를 제공한다.

getActionCommand()// 컴포넌트 이름을 리턴한다.

getModifiers()// Shift 나 Ctrl 키를 누를 것을 알려준다.

한편 상위 클래스인 EventObject 는 다음과 같은 메소드를 지원한다.

getSource()// 이벤트를 발생시킨 컴포넌트 객체를 리턴

Mouse, Component, Focus, Key, MouseMotion 이벤트 이벤트를 발생시킬 수 있는 컴포넌트는 메뉴를 제외한 모든

컴포넌트이다. 한편 Text 이벤트를 발생시킬 수 있는 컴포넌트는 TextArea, TextField 뿐이며 Item 이벤트는

체크박스, 초이스, 리스트 등에서 발생할 수 있다.

각 이벤트와 관련된 리스너 인터페이스, 어댑터, 그리고 관련 메소드들을 아래 표에 정리하였다.

2) Adjustment 이벤트

스크롤바에서 발생한다.

3) Focus 이벤트

TextArea, TextField 에서 많이 이용한다.

Page 90: Java Korea lectures

아래의 예제는 텍스트 필드에 Focus 가 오면 TextArea 에 문자열을 출력하는 예제이다.

import java.awt.*;

import java.awt.event.*;

public class A extends FocusAdapter {

Frame f = new Frame("Focus ");

TextField tf;

TextArea ta;

public static void main(String[] args) {

(new A()).Main();// non-static 메소드를 호출한다.

}

public void Main() {

tf = new TextField(20);

ta = new TextArea(10, 20);

ta.setEditable(false);

f.add(tf, "North");

Page 91: Java Korea lectures

f.add(ta, "Center");

f.setSize(300, 300);

f.setVisible(true);

tf.addFocusListener(this);

}

public void focusGained(FocusEvent ev) {

ta.append("Focus gained\n");

}

public void focusLost(FocusEvent ev) {

ta.append("Focus lost\n");

}

}

4) Item 이벤트

다음과 같은 하나의 메소드만 있으므로 어댑터 클래스는 없다.

Page 92: Java Korea lectures

itemStateChanged()

ItemEvent 클래스가 지원하는 메소드는 다음과 같다.

Object getItem()// 아이템과 관련된 객체를 리턴

int getStateChange()// 아이템의 선택 여부를 알려줌

5) Mouse 이벤트

MouseListener 또는 MouseAdapter 가 제공하는 메소드는 다음과 같다.

mouseClicked(MouseEvent e)// 마우스 버튼 클릭

mousePressed(MouseEvent e)// 마우스 버튼 누름

mouseReleased(MouseEvent e)// 마우스 버튼 뗌

mouseEntered(MouseEvent e)// 컴포넌트에 마우스 커서가 들어옴

mouseExited(MouseEvent e)// 컴포넌트에 마우스 커서가 나감

MouseEvent 클래스가 제공하는 메소드는 다음과 같다.

int getX()

int getY()

Page 93: Java Korea lectures

Point getPoint()

int getClickCount()// 연속적이 클릭 횟수 리턴

앞에서 설명한 InputEvent 클래스가 제공하는 메소드들을 제공한다.

6) MouseMotion 이벤트

마우스가 컴포넌트 상에서 움직일 때 발생한다.

MouseMotionListener 또는 MouseMotionAdapter 가 제공하는 메소드는 다음과 같다.

mouseDragged(MouseEvent e)// 마우스 버튼을 누른 상태에서 이동

mouseMoved(MouseEvent e)// 버튼을 누르지 않은 상태에서 이동

(예) 마우스의 움직임을 확인

public class MouseMove implements MouseListener, MouseMotionListener {

Frame frame = new("Mouse Dragging");

TextField textfield = new TextField(20);

public static void main(String[] args) {

Page 94: Java Korea lectures

frame.addMouseListener(this);

frame.addMouseMotionListener(this);

. . .

}

리스너 객체내에서 해당 이벤트 처리부분

public void mouseDragged (MouseEvent e) {

String s = "Mouse drag: x = " + e.getX() + " y = " + e.getY());

textfield.setText(s):

}

주의: mouseDragged() 메소드뿐 아니라 mouseMeved() 등 리스너에 정의된 모든 메소드를 구현하여야 한다.

7) Text 이벤트

TextArea, TextField 컴포넌트에서 텍스트의 변화가 있으면 발생한다. 리스너는 TextListener 이며 이것은 다음의

메소드 하나만 제공한다.

textValueChanged(TextEvent ev)

Page 95: Java Korea lectures

TextEvent 클래스는 특별한 메소드를 제공하지는 않는다. 아래의 예제는 텍스트필드에 텍스트의 변화가 있을

때마다 변화된 최종 내용, 즉, 입력한 글자가 아니라 글자 입력후 TextField 에 남아 있는 문자열을 출력해준다.

주의: 탭을 누르면 탭이 입력되지 않고 다른 컴포넌트로 이동한다.

import java.awt.*;

import java.awt.event.*;

public class A implements TextListener {

Frame f = new Frame("Focus ");

TextField tf;

TextArea ta;

public static void main(String[] args) {

(new A()).Main();

}

public void Main() {

tf = new TextField(20);

ta = new TextArea(10, 20);

Page 96: Java Korea lectures

f.add(tf, "North");

f.add(ta, "Center");

f.setSize(300, 300);

f.setVisible(true);

tf.addTextListener(this);

}

public void textValueChanged(TextEvent ev) {

TextComponent c = (TextComponent) ev.getSource();

ta.append("'" + c.getText() + "' is typed\n");

}

}

단 텍스트필드에서는 리턴키를 입력해도 변화가 없음을 알 수 있다. 입력의 종료를 처리하려면 별도로 버튼을

만들거나 리턴키를 인식하기 위하여 ActionListener 를 구현하여야 한다.

Page 97: Java Korea lectures

8) Key 이벤트

키보드에서 입력할 때 발생한다. 포커스를 가진 컴포넌트에서만 발생한다.

KeyListener 또는 KeyAdapter 는 다음의 세가지 메소드를 제공한다.

keyTyped(KeyEvent e)// 키 입력

keyPressed(KeyEvent e)// 키 누름

keyReleased(KeyEvent e)// 키를 뗌

KeyEvent 클래스는 다음의 메소드를 제공한다.

char getKeyChar()// 입력된 키를 알아본다.

void setKeyChar(char KeyChar)// 입력된 키를 설정한다.

int getKeyCode()// 입력된 키를 알아본다.

void setKeyCode(int KeyCode)// 입력된 키를 설정한다.

KeyEvent 클래스는 InputEvent 클래스를 상속 받았으므로 InputEvent 클래스가 제공하는 다음과 같은 메소드를

제공한다.

long getWhen()// 이벤트 발생 시각을 리턴

boolean isShiftDown()

Page 98: Java Korea lectures

boolean isControlDown()

boolean isAltDown()

boolean isMetaDown()

예제 프로그램 실행하고 a 를 입력한 결과는 다음과 같다.

import java.awt.*;

import java.awt.event.*;

public class A extends KeyAdapter {

Frame f = new Frame("키 입력 ");

TextArea ta;

public static void main(String[] args) {

(new A()).Main();

}

public void Main() {

ta = new TextArea(20, 40);

Page 99: Java Korea lectures

f.add(ta, "Center");

f.setSize(400, 400);

f.setVisible(true);

ta.addKeyListener(this);

}

public void keyTyped(KeyEvent ev) {

ta.append("\nTyped Key is ' " + ev.getKeyChar() + "'\n ");

if(ev.isShiftDown()) ta.append(" - Shift is down !\n");

}

}

텍스트 필드에서 엔터키를 입력하더라도 텍스트 필드의 내용을 변하지 않으므로 KeyEvent 를 사용하여 이를

처리할 수 없으며 엔터키를 입력하였을 때 발생하는 이벤트는 ActionEvent 이다. 이것을 처리하려면 다음과 같이

하여야 한다.

import java.awt.*;

import java.awt.event.*;

public class A extends KeyAdapter implements ActionListener {

Page 100: Java Korea lectures

Frame f = new Frame("키 입력 ");

TextField tf;

TextArea ta;

public static void main(String[] args) {

(new A()).Main();

}

public void Main() {

tf = new TextField(40);

ta = new TextArea(20, 40);

f.add(tf, "North");

f.add(ta, "Center");

f.setSize(400, 400);

f.setVisible(true);

tf.addActionListener(this);

tf.addKeyListener(this);

}

Page 101: Java Korea lectures

public void actionPerformed(ActionEvent ev) {

ta.append("Enter is entered \n");

}

public void keyTyped(KeyEvent ev) {

ta.append("Typed Key is ' " + ev.getKeyChar() + "'\n ");

if(ev.isShiftDown()) ta.append(" - Shift is down !\n");

}

}

연습문제

1. 본문의 Key 이벤트 처리 예제 프로그램에서 키가 눌리거나 떼는 구제적인 동작 발생을 확인하는 프로그램을

작성하고 Key Code 값과 Modifier 의 종류를 알려주는 프로그램을 작성하시오. 즉, keyPressed()와 keyReleased()를

구현하시오.

(답)

public void keyPressed(KeyEvent ev) {

char c = ev.getKeyChar();

Page 102: Java Korea lectures

ta.append("'" + c + "' is Pressed");

int code = ev.getKeyCode();

ta.append(" : Key Code is " + code + "\n");

if (Character.isISOControl(c)) ta.append("Ctrl Key: ");

int modifier = ev.getModifiers();

String s = KeyEvent.getKeyModifiersText(modifier);

if (s.length() > 0) ta.append(" modifier is "+ s + "\n");

}

public void keyReleased(KeyEvent ev) {

ta.append(" ..... and the key is released now \n\n");

}

2. TextArea 입력 프로그램을 이벤트를 발생하는 클래스 하나, 이벤트를 처리하는 클래스 두 개 등 모두 세 개의

클래스로 나누어 구현하여 보시오.

(답)

Page 103: Java Korea lectures

12. 스트림과 입출력

자바에서는 표준 입출력을 제공하기 위하여 System.in 과 System.out 클래스를 제공하며 표준 오류출력을

위해서는 System.err 클래스가 사용된다. 자바에서는 표준 입출력 외에 일반적인 데이터 입출력을 위해 스트림

(Stream) 클래스를 제공한다.

java.io 패키지는 기본적으로 다음의 두가지의 스트림을 제공한다.

바이트(byte) 스트림: Input Stream 과 Output Stream

문자(character) 스트림: Reader 와 Writer

이외에 다음과 같은 종류가 있다.

Data Byte 스트림

Object Byte 스트림

그리고 파일을 다루기 위한 다음의 클래스가 있다.

File 클래스

RandonAccessFile 클래스

Page 104: Java Korea lectures

자바에서는 여러 가지 입출력 방식을 지원하고 있으며 그중 핵심이 되는 것이 스트림(Stream)과 파일(File)

객체이다.

자바의 java.io 패키지가 제공하는 기능을 이해하기 위하여 다음의 클래스에 대하여 배운다.

Reader (Writer)// Unicode 단위의 읽기(쓰기) 제공 (16 비트)

InputStream (OutputStream)// 저수준의(low-level) 바이트 단위의 읽기(쓰기) 지원

FilterInputStrea (FilterOutputStream)// 다양한 타입의 읽기(쓰기) 제공 (abstract 클래스임)

DataInputStream(DataOutputStream)// 자바의 기본 타입 입력(출력)

File

RandomAccessFile

12.1 스트림을 이용하는 i/o

스트림은 크게 InputStream 과 OutputStream 두가지가 있는데 InputStream 은 파일, 스트링 등의 입력 소스로부터

데이터를 읽어 오는데 사용되며 처음 사용될 때 open 된다.

자바의 io 클래스가 제공하는 기능을 다음과 같이 세가지로 나눌 수 있으며 이들에 대하여 배운다. (읽기에

대한여만 예시)

Page 105: Java Korea lectures

저수준(low-level) input stream- 바이트 단위의 읽기

고수준(high-level) filter input stream- 아양한 타입의 읽기 제공

Reader- Unicode 단위의 읽기 제공 (16 비트)

(1) 저수준의 i/o

abstract 클래스인 InputStream (OutputStream)을 이용하여 저수준의(low-level) "바이트 단위"의 읽기(쓰기)를 할 수

있다. 바이트 단위의 i/o (또는 바이트 어레이도 가능)을 처리하며 대표적인 것이 FileInputStream 과

FilterInputStream 이며 (filter 를 통하여 파일 또는 버퍼로부터 읽을 수 있다.) 다음과 같은 메소드를 제공한다.

read()// 한 바이트를 읽는다

write()// 한 바이트를 쓴다

int available()// 블록되지 않고 읽을 수 있는 바이트 수를 리턴

void close()// fis 를 사용하지 않게 되면 반드시 close 해야 한다.

flush()// 버퍼에 있는 데이터를 출력

long skip(long offset)

OutputStream 클래스는 스트림으로 바이트(혹은 바이트 어레이)를 출력하거나 스트림을 비우는 메소드 flush()를

제공한다. OutputStream 역시 처음 만들때 open 되는데 이를 close() 메소드를 사용하여 닫을 수도 있고 자바의

자동 메모리 관리(garbage collection)에 의해 자동으로 닫게 할 수도 있다. OutputStream 이 제공하는 메소드는

다음과 같다.

Page 106: Java Korea lectures

close()/* 출력 스트림을 닫는다 */

flush()/* 스트림의 내용을 지운다 */

write()/* 한 바이트를 출력한다 */

InputStream 과 OutputStream 의 사용예는 다음과 같다.

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

예)

StringBuffer buf = new StringBuffer();

int ch;

while((ch = System.in.read()) != -1) {

buf.append(ch);

}

System.out.println(buf);

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

한편 InputStream 과 OutputStream 은 각각 FileInputStream, FileOutputStream, DataInputStream, DataOutputStream 과

같은 하위클래스들을 가지고 있으며 이들은 java.io 패키지에 정의되어 있다.

Page 107: Java Korea lectures

예) 파일의 바이트수 세기

import java.io.*;

class Test {

public static void main(String[] args) throws IOException {

InputStream is = new FileInputStream(args[0]);

int total = 0;

while(in.read() != -1) total++;

System.out.println("The file has " + total + " bytes.");

}

}

FileInputStream 의 경우 생성자는 다음과 같다. 파일명을 String 을 줄 수도 있고 File 객체를 인자로 바로 줄 수도

있다. (뒤에서 다시 설명)

new FileInputStream(String myfile)

Page 108: Java Korea lectures

new FileInputStream(File file)

FileInputStream 과 FileOutputStream

파일 입출력을 다루기 위해 사용되는 스트림으로 데이터를 순차적으로 한 바이트씩 읽거나 쓸 수 있다. 다음의

예는 FileInputStream 의 read() 메소드로 input.txt 파일의 데이터를 읽어들이고 이를 다시 FileOutputStream 의

write()메소드를 이용하여 출력 파일 output.txt 에 쓰는 예제이다.

import java.io.*;

class FileStreamsTest {

public static void main(String args[]) {

try {

FileInputStream fis = new FileInputStream("input.txt");

FileOutputStream fos = new FileOutputStream("output.txt");

int c;

while ((c = fis.read()) != -1) {

fos.write(c);

}

fis.close();

Page 109: Java Korea lectures

fos.close();

} catch (FileNotFoundException e) {

System.err.println("FileStreamsTest: Error" + e);

} catch(IOException e) {

System.err.println("IOException Error" + e);

}

}

InputStream 및 OutputStream 으로부터 상속된 기타 저수준의 입력 스트림은 다음과 같다.

InputStream

FilterInputStream

FileInputStream

ByteArrayInputStream// 바이트를 어레이 단위로 읽기

PipedInputStream// 쓰레드간의 통신에 사용

SequenceInputStream

StringBufferInputStream

Page 110: Java Korea lectures

OutputStream

FilterOutputStream

FileOutputStream

ByteArrayOutputStream

PipedOutputStream

한편 저수준 입력용 스트림의 최상위 클래스인 InputStream 는 네트웍 socket 으로부터 데이터를 읽을 때

사용한다.

(2) 고수준의 i/o 스트림

a) FilterInputStream

고수준의 입력용 스트림은 InputStream 의 하위 클래스인 FilterInputStream 로부터 상속되며 종류는 다음과 같다.

InputStream

--FilterInputStream// abstract 클래스임

--DataInputStream// 자바의 기본 타입 입력

--BufferedInputStream// 효율

Page 111: Java Korea lectures

--LineNumberInputStream

--PushbackInputStream

OutputStream

--FilterOutputStream

--PrintStream

--DataOutputStream

--BufferedOutputStream

주의할 것은 FilterInputStream 은 파일이나 소켓과 같은 device 부터 데이터를 바로 읽을 수 없으며 반드시 다른

stream 으로부터만 읽을 수 있다. (출력도 마찬가지임)

대표적인 고수준의 입력 스트림으로 DataInputStream 이 있다.

DataInputStream 과 DataOutputStream

이것은 앞에 설명한 파일 관련 스트림과 비슷하지만 바이트 뿐 아니라 자바에서 지원하는 기본 데이터 형을

사용하여 데이터를 바로 읽거나 쓸 수 있도록 한다.

Page 112: Java Korea lectures

저수준과 고수준의 입력을 연결하여(chain) 사용하는 예는 다음과 같다. 그림의 윗부분은 상위클래스, 중간은

사용된 스트림 및 파일, 아래는 데이터를 읽는 단위를 나타냈다.

(InputStream) (FilterInputStream)

| |

파일 --> FileInputStream --> DataInputStream

(byte) (float)

즉, 어떤 Filter Stream 을 사용하려면 직접 입출력을 할 수 없고 반드시 중간 (저수준) 입출력을 담당할 스트림을

붙여주어야 한다. (위에서 FileInputStream 이 필요함) 따라서 DataInputStream 의 생성자는 다음과 같다.

DataInputStream(InputStream is)

여기서 인자 is 로 FileInputStream 이나 InputStream 인 System.in 또는 소켓 입력스트림이 등이 올 수 있다.

아래에서는 test.txt 라는 FileOutputStream 을 DataOutputStream 으로 만든 후 이를 이용하는 방법을 보이고 있다.

이러한 방법은 서버에 있는 데이터 파일을 스트림을 통해 클라이언트에 보낼 때 쓰이는 방법이다. 이러한 방법은

서버에 있는 파일을 우선 FileOutputStream 으로 연결을 함으로써 파일을 조작(읽거나 쓰거나)할 수 있는 상태로

바꾼 다음 DataOutputStream 을 통해서 파일의 내용을 자유롭게 조작할 수 있게 한다.

DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.txt"));

Page 113: Java Korea lectures

(예) (exam 236)

test.txt 파일에 정수 3 을 쓰고 이를 다시 읽어 화면에 출력하는 프로그램

import java.io.*;

class A {

public static void main(String [] args) throws IOException {

File f = new File("test.txt");

FileOutputStream fos = new FileOutputStream(f);

DataOutputStream ds = new DataOutputStream(fos);

FileInputStream fis = new FileInputStream(f);

DataInputStream is = new DataInputStream(fis);

ds.writeInt(3);

int x = is.readInt();

System.out.println(x);

}

Page 114: Java Korea lectures

}

DataInputStream 은 자바의 int, float 등의 기본 데이터타입을 바로 입력할 수 있도록 하는데 이 클래스가 제공하는

메소드는 다음과 같다.

readByte()/* 스트림으로부터 한 바이트를 읽는다 */

readChar()/* 스트림으로부터 한 문자를 읽는다 */

readLine()/* 스트림으로 부터 한 라인('\n')까지을 읽는다 */

readLong()

readDouble()

readFully()

skipBytes()

readUTF()

close()// 스트림을 close 할 때는 생성된 역순으로 모두 하여야 한다.

파일의 끝에 도달하면 EOFException 을 발생한다.

한편 생성자의 인자로는 InputStream 객체만 가능하다.

Page 115: Java Korea lectures

기타 고수준 입출력 스트림의 용도는 다음과 같다.

FilterInputStream 과 FilterReader 클래스는 abstract 클래스이므로 바로 객체화할 수 없고 하위 클래스인

BufferedInputStream, DataInputStream 또는 BufferedReader 클래스를 사용한다. 임의의 개수의 FIltered- 객체를

연결시킬 수 있다.

생성자의 인자로는 InputStream 객체만 가능하다.

BufferedInputStream

이 스트림은 자체적으로 내부에 버퍼를 가지고 있어 큰 블록단위로 바이트를 읽을 수 있고 따라서 I/O 오버헤드를

줄일 수 있어 속도를 향상시킨다. 이 클래스의 생성자의 인자로는 InputStream 객체만 가능하다.

File file = new File("myfile");

OutputStream out = new FileOuputStream(file);

OutputStream o = new BufferedOuputStream(out);

flush() 메소드를 제공한다.

PushbackInputStream

최근에 읽은 바이트를 그대로 스트림에 남겨두는 스트림이다.

PrintStream

텍스트나 기본타입의 입출력에 사용된다. 문자의 입출력에만 사용되는데 대표적인 예가 System.out 과 System.err

Page 116: Java Korea lectures

객체이다.

String 은 dis 로 직접 입출력하지 않는 것이 좋다.

b) FileInputStream

파일로부터 데이터를 읽기 위하여 사용된다. 인자로 가능한 것은 다음과 같이 세가지이다.

FileInputStream(String s);

FileInputStream(File f);

FileInputStream(Filedescriptor fd);

12.2 Readers, Writers

Unicode 단위의 읽와 쓰기를 제공하는 abstract 클래스이다.

이것은 저수준과 고수준의 입출력을 모두 지원한다.

(1) 저수준의 Reader/Writer

FileReader, FileWriter// 파일로의 입출력

Page 117: Java Korea lectures

CharArrayReader, CharArrayWriter// char 어레이의 입출력

PipedReader, PipedWriter// 쓰레드간의 통신에 사용

StringReader, StringWriter// 스트링의 입출력

read(), write(), close() 등 기본적인 메소드만 제공한다.

저수준의 Reader/Writer 는 모두 Reader/Writer 를 상속 받은 클래스이다. Reader 의 경우, raf 에서 소개한 세가지

타입의 read/write 메소드를 지원하는데 raf 과 다른점은 바이트 단위의 입출력이 아니라 char 단위(즉 16 비트단위)

의 입출력을 지원한다는 것이다.

(2) 고수준의 Reader/Writer

인자로 저수준의 Reader/Writer 객체를 주어야 한다.

BufferedRreader. BufferedWriter

내부에 버퍼를 가지고 있다. 인자로는 Reader(Writer) 만 가능하다.

InputStreamReader, OutputStreamWriter

바이트 스트림과 문자(Unicode) 스트림을 변환하는데 사용된다.

예)

Page 118: Java Korea lectures

FileReader, FileWriter

LineNumberReader

텍스트 라인들의 현재의 라인 위치를 추적한다. (0 번부터 시작한다.)

new LineNumberReader(new FileReader(args[0]);

PrintWriter

PrintStream 과 유사하나 char 단위로 쓴다.

StreamTokenizer

PushbackReader

PushbackInputStream 과 유사하며 이미 읽은 곳으로 다시 되돌아 갈 수 있다.

BufferedRreader 의 사용예

String s;

InputStreamReaderir;

BufferedReaderin;

ir = new InputStreamReader(System.in);

Page 119: Java Korea lectures

in = new BufferedRreader(ir);

while(( s = in.readLine()) != null) {

System.out.println("Read: " + s);

}

다른 예

Reader in;

in = new InputStreamReader(System.in);

in = new FileReader(args[0]);

예) 파일을 읽어 화면에 쓰기

(예)- 전방식

FileInputStream fis = new FileInputStream(args[0]);

DataInputStream is = new DataInputStream(fis);

String s="";

while ((s=is.readLine()) !=null){

System.out.println(s);

Page 120: Java Korea lectures

}

DataInputStream 은 스트림으로부터 int, double 과 같은 기본변수형을 바로 읽을 수 있도록 한다.

readInt() 등등

(예)- Reader 클래스를 쓰는 새로운 방식

FileReader fis = new FileReader(args[0]);

BufferedReader is = new BufferedReader(fis);

. . .

(예) 키보드 입력 및 출력 (에코 프로그램)

InputStreamReader fis = new InputStreamReader(System.in);

BufferedReader is = new BufferedReader(fis);

* Reader 클래스는 InputStream 과 같은 상위 개념의 클래스인데 차이점은 byte 단위의 읽기가 아니라 char (16

비트) 단위의 읽기라는 것이다. 위에서 InputStreamReader 는 인자로 InputStream 객체를 필요로 한다. System.in 은

InputStream 의 객체이다.

키보드에서 입력하는 문자를 모두 파일에 하나씩 쓰는 프로그램으로 출력할 파일 이름을 args[0]으로 한다.

Page 121: Java Korea lectures

import java.io.*;

class A {

public static void main(String [] args) throws IOException {

char c;

Reader r = new InputStreamReader(System.in);

Writer w = new FileWriter(args[0]);

while ((c = (char)r.read()) != 'q') {

w.write(c);

w.flush();

}

}

}

RandomAccessFile 을 쓰려면 main() 메소드내를 다음과 같이 바꿀 수도 있다.

RandomAccessFile w = new RandomAccessFile(args[0], "rw");

Page 122: Java Korea lectures

while ((c = (char)r.read()) != 'q') {

w.write(c);

}

RandomAccessFile 객체에서는 flush()를 할 필요는 없다.

위에서 RandomAccessFile 대신에 FileOutputStream 객체를 다음과 같이 사용하여도 결과는 같다. (단순히

문자단위의 출력이므로)

FileOutputStream w = new FileOutputStream(args[0]);

한편 출력의 속도 향상을 위하여 BufferedOutputStream 객체를 사용할 수 있으며 main() 메소드를 다음과 같이

바꿀 수 있다.

FileOutputStream f = new FileOutputStream(args[0]);

BufferedOutputStream w = new BufferedOutputStream(f);

while ((c = (char)r.read()) != 'q') {

w.write(c);

}

w.flush();

Page 123: Java Korea lectures

w.close();

fs.close();

12.3 파일을 통한 i/o

(1) File 클래스

File 클래스는 파일 자체를 다루는 것이 아니라 파일을 나타내는데 사용되는 path 를 다룬다 즉, File 생성자는 어떤

경로(path)를 가리키는 File 타입의 객체를 만들 뿐이며 실제 파일을 만들거나(create) open 하지는 않는다.

실제파일을 만들거나(create) open 하려면 FileOutputSteam, RandomAcccessFile, FileInputSteam 등의 클래스를

사용하여야 한다. 파일의 생성예는 다음과 같다.

FileOutputStream fos = new FileOutputStream("test.txt:);

한편 다음과 같이 파일을 생성할 수도 있다.

File f = new File("test.txt:);

FileOutputStream fos = new FileOutputStream(f):

위와같이 FileOutputStream 을 이용하여 파일생성시 이미 파일이 존재하고 있으며 그 파일을 덮어쓰게 된다. 만일

이를 피하려면 다음에 설명할 RandomAcccessFile 을 이용하여 파일을 만들어야 한다.

Page 124: Java Korea lectures

File 객체를 통하여 파일지움(delete), 이름변경(rename), 디렉토리 생성, 파일존재확인 등을 할 수 있다. File 클래스

생성자는 다음과 같이 세가지가 있다.

File(String path)

File(String dir, String file)

File(File dir, String file)

위에서 path 가 null 이면 NullPointerException 예외를 발생한다.

디렉토리 생성은 mkdir()을 호출하여야 한다.

File 클래스가 제공하는 대표적인 메소드는 다음과 같다.

boolean exists()// 파일이 존재하는지 확인

boolean canRead()//

boolean canWrite()//

boolean delete()// 파일을 삭제한다.

boolean renameTo()// 이름을 변경

boolean equals()// path 가 서로 같은지를 비교

boolean isFile()// 디렉토리가 아니면 ture

boolean isDirectory()// 디렉토리인지 확인

Page 125: Java Korea lectures

long length()// 바이트 단위의 파일 크기를 리턴

lastModified()// 상대적인 시간을 리턴

boolean mkdir()// 디렉토리를 생성

getParent()// 상위 디렉토리 이름을 리턴

getPath()// 디렉토리 상대적 경로를 리턴

getAbsolutePath()// 디렉토리 절대 경로를 리턴

getName()// 파일의 이름을 리턴

Strings[] list()// 디렉토리내의 파일과 서브 디렉토리 이름들을 어레이로 리턴 (없으면 null 리턴)

주의: File 객체가 gc 되어도 실제 파일이 삭제되는 것은 아니다.

예)

다음은 한 파일에서 256 바이트를 읽어서 다른 파일에 쓰는 프로그램이다. 읽을 파일을 FileInputStream 으로

선언하였으며 복사하여 새로 만들 파일을 FileOutputStream 으로 만들었다. 파일명은 각각 프로그램 명령인자로

주었다 (args[0]과 args[1]).

import java.io.*;

public class A {

public static void main(String[] args) {

Page 126: Java Korea lectures

final int max = 256;

byte buf[] = new byte[max];

try {

FileInputStream in = new FileInputStream(args[0]);

in.read(buf, 0, max);

FileOutputStream out = new FileOutputStream(args[1]);

out.write(buf);

} catch (IOException e) {

System.out.println ("Error: " + e.toString());

}

}

}

read(), write() 만 쓸 수 있다.

(2) RandomAccessFile

Page 127: Java Korea lectures

파일의 내용을 편리하게 엑세스하기 위하여 자바는 RandomAccessFile 클래스를 제공하는데 RandomAccessFile 는

뒤에서 설명할 Stream, Reader, Writer 등의 클래스를 이용하는 경우와 달리 파일내의 임의의 위치를 찾아가거나

임의의 크기 단위로 읽기 등을 할 수 있다. 자바에는 기본(primitive) 데이터 타입을 바로 입출력할 수 있는

메소드들을 정의하는 인터페이스로 DataOutput 과 DataOutout 인터페이스가 있는데, RandomAccessFile 는 이

인터페이스들을 구현하고 있다.

주의할 것은 스트림 타입이 아니라는 것이다.

RandomAccessFile 의 생성자는 다음과 같다.

RandomAccessFile(String filename, String mode)

RandomAccessFile(File filename, String mode)

위에서 mode 는 'r', 'rw 두가지 중 하나만 가능하다(읽기전용, 읽기 및 쓰기). 생성자의 인자로 올 수 있는 것은

String 타입의 파일 이름 또는 File 타입의 경로 두가지 뿐이다.

RandomAccessFile 객체를 만드는 것도 File 객체를 만들 때와 마찬가지로 실제 파일을 생성하는 것과는

관계없으며 이 인스턴스가 gc 되어도 실제 파일이 삭제되는 것은 아니다.

파일 내의 위치를 찾기 위하여 다음과 같은 메소드를 지원한다. 자바에서는 c 언어와 달리 파일의 처음부터의

위치만을 지정할 수 있다. (모든 메소드는 IOException 을 발생한다.)

long getFilePointer();// 현재의 위치를 리턴(바이트 단위)

seek(long pos);// 포인터 위치를 지정 (0 이 처음위치)

Page 128: Java Korea lectures

long length();// 파일의 길이를 리턴 (바이트 단위)

이용예

seek(length());// 파일끝의 위치로 이동

데이터 읽기

int read()// 한 바이트를 읽는다. (int 의 하위 8 비트에 저장됨)

int read(byte buffer[])// buffer 에 채워질때까지 바이트를 읽는다.

int read(byte buffer[], int offset, int length)

위의 메소드는 모두 실제로 읽은 바이트수를 리턴하며 데이터가 없는 경우는 -1 을 리턴한다.

데이터 쓰기

void write(int b)// b 의 하위 8 비트를 쓴다.

void write(byte b[])// 버퍼 b 를 쓴다.

void write(int b[], int offset, int length)

RandomAccessFile 은 모든 종류의 단순 변수를 읽고 쓰는 것을 지원한다.

(guide 381)

Page 129: Java Korea lectures

readInt()

readShort()

readFloat()

String readLine()

String readUTF()

int readUnsignedByte()

다음은 특정 파일에 문자열을 추가하고 파일 전체를 화면에 출력하는 프로그램이다.

public class Test {

public static void main(String[] args) {

try {

RandomAccessFile f = new RandomAccessFile(args[0], "rw");

f.seek(f.length());

f.writeBytes("Hello !!! ");

f.seek(0);

while (f.getFilePointer() < f.length())

System.out.println(f.readLine());

Page 130: Java Korea lectures

} catch (IOException e) {

System.out.println ("Error: " + e.toString());

}

}

}

네트웍으로부터 데이터를 받는 예

ir = new InputStreamReader(System.in. "8859_1");// Latin-1 ASCII 코드

URL InputStream

java.net.URL imageSource;

try {

imageSource = new URL("http://. . . ") ;

} catch (MalformedURLException e) { }

images[0] = getImage(imageSource, "Duke.gif") ;

Page 131: Java Korea lectures

URL.openStrean()을 사용하여 URL 에 있는 파일을 읽을 수 있다.

try {

InputStream = (new URL(getDocumentBase(), "filename")).openStream() ;

} catch (Exception e) { }

try {

is.read(buffer, 0, buffer.length);

} catch(IOException e) { }

12.4 Serializable(직렬화)

어떤 객체를 객체의 상태와 함께 메모리에 저장하거나 통신채널로 전송하기 위하여는 Serializable 화 하여야

한다. 즉, 객체를 스트림으로 저장하기 위하여 필요하며 이를 "객체를 persistent 하게 만드는 것"이라고도 한다.

(persistent: 영구히 저장할 수 있는 포맷으로 만드는 것). 여기서 저장을 하는 경우는 자바의 객체를 파일에

저장하거나 네트웍을 통하여 전송하는 경우를 말한다. 객체를 외부에 저장할 때는 ObjrctOutPutStream 클래스가

제공하는 writeObject() 메소드를 사용하면 되는데 이때 스트림이 FileOutputStream 으로 연결되어 있으면 파일에

객체가 저장되고 Socket OutputStream 에 연결되어 있으면 네트웍으로 전송되는 것이다. 이렇게 저장된 객체의

정보는 ObjectInputStream 을 통하여 읽을 수 있다. 그런데 이와같은 정상적인 저장-읽기 작업에는 객체의 모든

상태정보가 저장되므로 자바 가상머신 외부하고 할 수 있는 파일 또는 네트웍에서 객체의 어떤 중요 정보가

유출되기를 원하지 않는다면 이 변수를 transient 로 선언하여 이것이 저장되는 대상에서 제외가 되도록 하는 것이

안전한다. 이것을 또한 메모리를 절약하기 위하여도 사용될 수 있다.

Page 132: Java Korea lectures

일반적인 write() 문에서 변수가 객체이면 그 객체의 데이터부분만 저장되며 메소드나 생성자는 저장되지 않는다.

한편 속성상 직렬화가 안되는 객체들도 있다(스트림, 쓰레드 등). 그리고 이런 객체를 포함하는 객체도 직렬화가

안된다. 이 때는 해당 객체를 transient 로 선언하면 전체 객체를 직렬화할 수 있다. (이 객체는 직렬화에서

제외한다.)

Input 스트림 및 Output 스트림과 같은 개념으로 Object Stream (객체스트림) 객체가 있다.

어떤 클래스가 Serializable 인터페이스를 구현하고 있으면 ObjectInputStream 또는 ObjectOutputStream 을 사용하여

그 클래스의 인스턴스 변수들을 직접 스트림으로 입출력할 수 있다. (단 public 또는 protected 인 변수들만) 이때

인스턴스 변수가 다른 객체를 가리키고 있으면 그 객체에 대한 정보도 같이 자동으로 입출력된다. 따라서 저장될

객체는 graph 형태를 이루게 된다. (객체안에 객체가 있으므로)

한편 Serializable 인터페이스에는 메소드는 없다. 즉, 이 인터페이스를 구현한다고 선언함으로써 객체의 직렬화가

가능하게 할 뿐이며 어떤 구현할 메소드를 가지고 있지는 않다.

쓰기 예)

FileOutputStream fos = new FileOutputStream("test.txt");

ObjectOutput oo = new ObjectOutputStream(fos);

oo.writeObject("Sample Ouputs");

FileInputStream fis = new FileInputStream("test.txt");

ObjectInput oi = new ObjectInputStream(fis);

Page 133: Java Korea lectures

String s = (String) oi.readObject();

transient

이 변경자는 변수에 대하여만 선언할 수 있는데 transient 로 선언된 변수들은 객체를 메모리등, 자바 가상머신

외부에 "저장"할 때 제외된다.

class MyClass implements Serializable {

private String myName;

private transient String myPassword:

한편 transient 변수는 final 또는 static 으로 동시에 선언될 수 없다.