2013년 3월 3일 일요일

책소개 - 빅자바


http://books.google.co.kr/books?vid=ISBN8955507895

좋은 내용들이 많다.


인터페이스와 다형성

인터페이스에 대해 배운다.

■ 상위 타입 레퍼런스와 하위 타입 레퍼런스 사이를 변환한다.

■ 다형성의 개념을 이해한다.

■ 클래스 사이의 결합성을 줄이기 위해 인터페이스를 사용한다.

■ 내부 클래스로 헬퍼 클래스를 구현하는 방법을 배운다.

■ 내부 클래스가 어떻게 주위 범위의 변수를 액세스하는지 이해한다.

■ 타이머 이벤트에 대한 이벤트 리스너를 구현한다.

436 생각하는 Big Java

6장의 DataSet 클래스를 살펴보자. 이 클래스는 입력받은 값들의 평균과 최대값을 계산

한다. 하지만 이 클래스는 입력값으로 숫자만을 받았다. 만약 가장 잔금이 많이 남아 있는 계

좌를 찾기 위해 BankAccount를 사용하고 싶다고 가정해 보자. 그렇다면 이 클래스를 다음

처럼 고쳐야 할 것이다.

public class DataSet // BankAccount 객체를 위해 수정

{

...

public void add(BankAccount x)

{

sum = sum + x.getBalance( );

if (count == 0



maximum.getBalance( ) < x.getBalance( ))

maximum = x;

count++;

}

public BankAccount getMaximum( )

{

return maximum;

}

private double sum;

private BankAccount maximum;

private int count;

}

9.1

재사용 가능한 솔루션 개발하기

CHAPTER CONTENTS

9.1 재사용 가능한 솔루션 개발하기
436

Syntax 9.1 인터페이스 정의하기
441

Syntax 9.2 인터페이스 구현하기
442

Common Error 9.1 메소드를 public으로 구현해야 된

다는 것을 잊어버리기
442

9.2 타입 변환하기
443

Syntax 9.3 instanceof 연산자
445

Advanced Topic 9.1 인터페이스 안의 상수들
445

9.3 다형성
446

9.4 재사용성을 증가시키기 위해 전략 인터페이스 사용

하기
448

Syntax 9.4 내부 클래스
454

Advanced Topic 9.2 익명 내부 클래스
455

9.5 타이머 이벤트 처리하기
456

Common Error 9.1 메소드 구현에서 메소드 시그너처

변경하기
463

Random Fact 9.1 운영체제
463

Chapter 9 인터페이스와 다형성 437

Chapter 09

또는 동전들 중에서 가장 값비싼 동전을 찾고 싶다고 해보자. 이번에는 DataSet 클래스를

다음처럼 고쳐야 할 것이다.

public class DataSet // Coin 객체를 위해 수정

{

...

public void add(Coin x)

{

sum = sum + x.getValue( );

if (count == 0



maximum.getValue( ) < x.getValue( ))

maximum = x;

count++;

}

public Coin getMaximum( )

{

return maximum;

}

private double sum;

private Coin maximum;

private int count;

}

데이터를 분석하는 작업은 모두 동일하지만 값을 측정하는 방법이 모두 다르다.

여러 클래스들이 은행 계좌의 잔금이나 동전의 값어치 같은 자료 분석에 사용되는 값을 얻

기 위해, getMeasure라는 동일한 메소드를 사용해도 된다고 가정해 보자.

이런 경우 DataSet 클래스를 재활용이 가능하도록 작성할 수 있을 것이다. add 메소드는

다음처럼 될 것이다.

sum = sum + x.getMeasure( );

if (count == 0

maximum.getMeasure( ) < x.getMeasure( ))

maximum = x;

count++;

변수 x의 타입은 무엇인가? x는 getMeasure 메소드를 갖고 있는 클래스의 오브젝트일 것

이다. 자바에서는 인터페이스(interface) 타입이 이런 개념을 표현한다. 다음은 Measurable

타입에 대한 선언이다.

438 생각하는 Big Java

public interface Measurable

{

double getMeasure( );

}

인터페이스 선언에는 인터페이스가 요구하는 모든 메소드를 나열

한다. 위의 인터페이스는 한 개의 메소드를 요구하지만, 일반적으로

인터페이스는 여러 개의 메소드를 요구할 것이다.

인터페이스는 클래스와 비슷하다. 하지만 이들 사이에는 몇 가지

의 중요한 차이점이 있다.

■ 인터페이스 안의 모든 메소드는 추상(abstract) 메소드이다. 다시 말해 이 메소드들은 이름과

매개변수, 리턴 타입을 갖고 있지만 구현부는 없다.

■ 인터페이스의 모든 메소드는 자동적으로 public 권한이 된다.

■ 인터페이스는 인스턴스 변수를 가질 수 없다.

이제 변수 x와 maximum을 선언하기 위해 Measurable 타입을 사용할 수 있을 것이다.

public class DataSet

{

...

public void add(Measurable x)

{

sum = sum + x.getMeasure( );

if (count == 0



maximum.getMeasure( ) < x.getMeasure( ))

maximum = x;

count++;

}

public Measurable getMaximum( )

{

return maximum;

}

private double sum;

private Measurable maximum;

private int count;

}

DataSet 클래스는 Measurable 인터페이스를 구현한 클래스의 객체를 분석하기 위해 사

자바 인터페이스는 메소드와

메소드의 시그너처를 선언한

다. 클래스와는 다르게 인터페

이스는 구현부를 갖지 않는다.

Chapter 9 인터페이스와 다형성 439

Chapter 09

용될 수 있다. 만약 클래스가 implemets 절 안에서 인터페이스를 선

언하고 인터페이스가 요구하는 메소드들을 다 구현했다면 클래스가

인터페이스를 구현했다고 말한다.

class ClassName implements Measurable

{

public double getMeasure( )

{

구현

}

추가적인 메소드와 필드

}

클래스는 여러 개의 인터페이스를 구현할 수 있다. 물론 클래스는 구현하려는 모든 인터페

이스들이 요구하는 메소드를 전부 정의해야 한다.

BankAccount 클래스가 Measurable 인터페이스를 구현하도록 변경해 보자.

public class BankAccount implements Measurable

{

public double getMeasure( )

{

return balance;

}

...

}

인터페이스는 메소드에 public을 안 붙였지만 클래스는 메소드에 public을 붙였다는 것을

주목하기 바란다 - 인터페이스의 모든 메소드는 public이다.

이와 비슷하게, Coin 클래스도 Measurable 인터페이스를 구현하도록 고칠 수 있다.

public class Coin implements Measurable

{

public double getMeasure( )

{

return value;

}

...

}

인터페이스를 구현할 때, 클래

스는 반드시 인터페이스가 요

구하는 모든 메소드를 구현해

야 한다.

440 생각하는 Big Java

이제 DataSet 객체는 bank account나 coin 집합을 분석하기 위해 사용할 수 있게 되었

다. 다음은 이것을 보여주는 테스트 프로그램이다.

01 /**

02 이 프로그램은 DataSet 클래스를 테스트한다.

03 */

04 public class DataSetTest

05 {

06 public static void main(String [ ] args))

07 {

08

09 DataSet bankData =new DataSet( );

10

11 bankData.add(new BankAccount(0));

12 bankData.add(new BankAccount(10000));

13 bankData.add(new BankAccount(2000));

14

15 System.out.println("Average balance ="

16 + bankData.getAverage( ));

17 Measurable max =bankData.getMaximum( );

18 System.out.println("Highest balance ="

19 + max.getMeasure( ));

20

21 DataSet coinData =new DataSet( );

22

23 coinData.add(new Coin(0.25,"quarter"));

24 coinData.add(new Coin(0.1,"dime"));

25 coinData.add(new Coin(0.05,"nickel"));

26

27 System.out.println("Average coin value ="

28 + coinData.getAverage( ));

29 max =coinData.getMaximum( );

30 System.out.println("Highest coin value ="

31 + max.getMeasure( ));

32 }

33 }

[그림 1]은 클래스와 인터페이스 사이의 관계를 보여주고 있다.

UML 에서 인터페이스에는 stereotype(다중 타입) 지시자

<>를 붙인다. 삼각 머리가 있는 점선 화살표는 클래스와

파일 DataSetTest.java

인터페이스는 클래스 사이의

결합도를 줄일 수 있다.

Chapter 9 인터페이스와 다형성 441

Chapter 09

인터페이스 사이의 구현 관계를 나타낸다. 화살표 머리를 주의해서 보기 바란다 - 열린 v자

모양의 화살표는 의존성을 나타낸다.

􄧔 그림 1 Measurable 인터페이스를 구현하고 있는 클래스들과 DataSet 클래스의 UML 도표

위의 도표는 DataSet 클래스가 오직 Measurable 인터페이스만을 의존하고 있다는 것을

보여준다. 이런 결합도 감소는 DataSet 클래스를 재활용할 수 있도록 만들어준다.

Measurable 인터페이스를 구현한 클래스는 DataSet 클래스와 함께 사용될 수 있을 것이다.

Syntax 9.1 : 인터페이스 정의하기

public interface 인터페이스이름

{

메소드 시그너처

}



public interface Measurable

{

double getMeasure( );

}

목적

인터페이스와 인터페이스 메소드의 시그너처(signature)를 정의한다.

메소드들은 자동적으로 public이 된다.

442 생각하는 Big Java

Syntax 9.2 : 인터페이스 구현하기

public class 클래스이름

implements 인터페이스이름, 인터페이스이름, ...

{

메소드

인스턴스 변수

}



public class bankAccount

implements Measurable

{

// 다른 BankAccount 메소드

public double getMeasure( )

{

// 메소드 구현

}

}

목적

인터페이스의 메소드를 구현한 새로운 클래스를 정의한다.

메소드를 public으로 구현해야 된다는 것을 잊어버리기

인터페이스의 메소드는 public으로 선언되지 않는다. 왜냐하면 인터페이스의 메소드는

디폴트로 public 상태가 되기 때문이다. 하지만 클래스의 메소드들에 대한 디폴트 권한은

public이 아니다 - 이들의 디폴트 권한 수준은 package이다(package에 대해서는 11장

에서 나올 것이다). 인터페이스로부터 받은 메소드를 정의할 때 public 키워드를 잊는 것

은 자주 있는 실수이다.

public class BankAccount implements Measurable

{

double getMeasure( ) // 아차, public이어야 함

{

return balance;

}

...

}

Common Error 9.1

Chapter 9 인터페이스와 다형성 443

Chapter 09

위와 같이 한다면 컴파일러는 메소드의 접근 권한이 약하다고, 즉 public이 아니라

package라고 불평을 늘어놓을 것이다. 이에 대한 조치는 메소드를 public으로 선언하는

것이다.

앞의 절의 테스트 프로그램에 있는 다음의 호출 구문을 살펴보아라.

bankData.add(new BankAccount(10000));

위의 구문은 DataSet 클래스의 add 메소드로 BankAccount 타

입의 객체를 전달한다. 하지만 이 메소드는 Measurable 타입의 매

개변수를 받는다! BankAccount 타입을 Measurable 타입으로 변

환시키는 것은 합법적인가?

자바에서 이런 변환은 합법적이다. 어떤 클래스 타입을 이 클래스가 구현한 인터페이스 타

입으로 변환하는 것은 가능하다. 예를 들어 다음은 합법적이다.

BankAccount account = new BankAccount(10000);

Measurable x = account; // OK

한편 x는 Measurable 인터페이스를 현실화한 Coin 객체도 가리킬 수 있다.

Coin dime = new Coin(0.1, "dime");

x = dime; // 이것도 OK

그러므로 Measurable 타입의 객체 변수를 갖고 있다면, x가 정확히 무슨 타입의 객체를

가리키고 있는지를 알 수 없을 것이다. 단지 알고 있는 것은 이 객체가 getMeasure 메소드

를 갖고 있다는 사실이다.

하지만 서로 상관없는 객체 사이를 변환시킬 수는 없다.

x = new Rectangle(5, 10, 20, 30); // 에러

위의 할당은 에러를 발생시킨다. 왜냐하면 Rectabgle 클래스는 Measurable 인터페이스

를 현실화하지 않았기 때문이다.

9.2

타입 변환하기

클래스 타입을 클래스가 구현

한 인터페이스 타입으로 변환

시킬 수 있다.

444 생각하는 Big Java

경우에 따라 객체를 인터페이스 레퍼런스로 바꿨다가 다시 객체로 바꿔야할 수도 있다.

DataSet 클래스의 getMaximum 클래스에서도 이를 필요로 한다. DataSet은 가장 큰 자

료값을 갖고 있는 객체를 Measurable 레퍼런스 변수로 저장한다.

DataSet coinData = new DataSet( );

coinData.add(new Coin(0.25, "quarter"));

coinData.add(new Coin(0.1, "dime"));

coinData.add(new Coin(0.05, "nickel"));

Measurable max = coinData.getMaximum( );

max 레퍼런스로 무엇을 할 수 있을까? 독자들은 이 레퍼런스가 Coin 객체를 가리키고 있

다는 것을 알고 있을 테지만, 컴파일러는 그렇지 않다. 따라서 이를 사용해서는 getName 메

소드를 호출할 수가 없다.

String name = max.getName( ); // 에러

위의 호출은 에러를 야기한다. 왜냐하면 Measurable 타입은 getName 메소드를 갖고 있

지 않기 때문이다.

하지만 max가 Coin 객체를 정말로 가리키고 있다는 확신이 있다

면, 이것을 다시 Coin 타입으로 변환시킬 수 있을 것이다.

Coin maxCoin = (Coin)max;

String name = maxCoin.getName( );

만약 문제가 발생했다면, 이 객체가 Coin 클래스의 객체가 아니기 때문일 것이다. 이 때

프로그램은 예외를 던지면서 종료하게 된다.

객체에 대한 형 변환 방법은 3장에서 보았던 숫자 타입 사이를 변환하는 것과 동일하다.

예를 들어 x가 실수 타입의 숫자라면 (int)x는 정수값이 된다. 둘 사이의 취지는 비슷해 보인

다 - 어떤 타입으로부터 다른 타입으로 변환시킨다. 하지만 숫자 타입의 형 변환과 클래스 타

입의 형 변환 사이에는 엄청난 차이가 있다. 숫자 타입을 형 변환할 때 정보를 잃어버릴 수도

있고, 정보를 잃어버리는 것이 괜찮을 때 이것을 사용한다. 한편 객체 타입을 형 변환할 때에

는 예외가 발생할 위험이 있고, 예외가 발생할 수도 있다는 것을 인정

하면서 형 변환을 하게 된다.

그래픽 프로그램에서 형 변환을 사용하는 예제를 보았을 것이다.

그때는 Graphics 객체를 Graphics2D 객체로 형 변환을 했어야 했

다. 이런 형 변환은 훌륭한 프로그래머의 징표가 절대 아니다. 라이브러리 개발자들은 호환

성 문제에 대한 응급 조치로 형 변환을 사용했다. 하여튼 형 변환을 해야 할 때가 생긴다면,

instanceof 연산자는 객체가

해당되는 타입에 속하는지 아

닌지를 검사한다.

인터페이스 타입에서 클래스

타입으로 변환할 때에는 형 변

환을 해야 한다.

Chapter 9 인터페이스와 다형성 445

Chapter 09

형 변환을 하기 전에 형 변환이 성공적으로 수행될 수 있는지를 테스트해야 한다. 이를 위해

instanceof 연산자를 사용할 수 있다. 이것은 객체가 해당 타입에 소속되어 있는지를 검사한

다. 예를 들어 다음의 구문은 만약 max의 타입이 Coin이라면 true를 리턴하고 아니라면

false를 리턴한다.

max instnaceof Coin

따라서 안전한 형 변환은 다음처럼 작성될 수 있다.

if (max instance of Coin)

{

Coin maxCoin = (Coin)max;

...

}

Syntax 9.3 : instanceof 연산자

객체 instanceof 클래스이름



if (x instanceof Coin)

{

Coin c = (Coin)x;

...

]

목적

만약 object가 ClassName(혹은 이 클래스의 자식클래스)의 인스턴스라면 true를 리

턴하고, 그렇지 않다면 false를 리턴할 것이다.

인터페이스 안의 상수들

인터페이스는 변수를 가질 수 없지만, 인터페이스를 구현하는 모든 클래스로 상속될 수

있는 상수(constant)를 정의할 수 있다.

예를 들어 SwingConstants 인터페이스는 SwingConstants.NORTH, Swing

Constants.EAST처럼 여러 개의 상수를 정의하고 있다. JLabel이 SwingConstants인

터페이스를 구현했기 때문에, JLabel 객체와 함께 이 상수들을 사용할 때, JLabel.

Advanced Topic 9.1

446 생각하는 Big Java

NORTH, JLable.EAST 등으로 이 상수들을 참조할 수 있다.

인터페이스 안의 상수를 정의할 때, public static final 키워드를 사용하지 말아야 한다.

왜냐하면 인터페이스 안의 모든 변수들은 자동적으로 public static final이 되기 때문이

다. 다음은 이에 대한 예이다.

public interface SwingConstants

{

int NORTH = 1;

int NORTHEAST = 2;

int EAST = 3;

...

}

다시 한번 말하지만 다음처럼 인스턴스 타입의 변수를 만드는 것은 가능하다 - 그리고 매

우 흔한 일이다.

Measurable x;

x가 가리키는 객체는 Measurable 타입이 아니라는 것을 기억하기 바란다. 사실 어떤 객

체도 Measurable 타입이 될 수 없다. 대신에 객체의 타입은 BankAccount나 Coin 같은

Measurable 인터페이스를 구현한 클래스가 될 것이다.

x는 변수가 존재하는 동안 다른 타입의 객체들을 가리킬 수도 있다는 사실에 주목하라. 다

음에서 변수 x는 처음엔 BankAccount 객체를 가리키다가, 이후에는 Coin 객체를 가리킨다.

x = new BankAccount(10000); // OK

x = new Coin(0.1, "dime"); // OK

하지만 절대 인터페이스를 생성시킬 수는 없을 것이다.

x = new Measurable( ); // 에러

인터페이스는 클래스가 아니다. 그래서 인터페이스 객체를 생성시

킬 수 없다.

9.3

다형성

다형성은 행동이 객체의 실제

타입에 따라 달라질 수 있다는

원리를 나타낸다.

Chapter 9 인터페이스와 다형성 447

Chapter 09

인터페이스 변수가 가리키는 객체가 클래스에 속해 있는지 모르는 상황에서, 인터페이스

변수를 가지고 무엇을 할 수 있을까? 인터페이스의 메소드를 호출할 수가 있을 것이다.

double m = x.getMeasure( );

DataSet 클래스는 어느 종류의 객체가 추가(add)되었는 지를 상관하지 않고, 추가된 객

체의 측정값을 계산할 수 있다.

이제 getMeasure 메소드를 호출하는 것에 대해 더 깊게 생각해 보도록 하자. 어떤 get

Measure 메소드가 호출될까? BankAccoun 클래스와 Coin 클래스가 따로 이 메소드를 구

현해 놓았다. x가 소속된 클래스가 무엇인지 모르는데 어떻게 올바르게 메소드를 호출할 수

있을까.

자바 가상 머신은 객체의 클래스에 소속된 메소드를 찾기 위해 특별한 노력을 기울인다. 다

시 말해, 만약 x가 BankAccount 객체를 가리키고 있다면, BankAccount.getMeasure 메소

드가 호출되고, x가 Coin 객체를 가리키고 있다면, Coin.getMeasure 메소드가 호출된다.

이 말은 다음의 호출이 x가 현재 무슨 내용을 담고 있느냐에 따라 각기 다른 메소드를 호

출하게 된다는 것이다.

double m = x.getMeasure( );

객체의 실제 타입이 호출될 메소드를 결정한다는 원리를 다형성(polymorphism)이라고

부른다. Polymorphism(다형성)이라는 단어는 그리스어 many shapes(형태가 많음)에서

따왔다. 이는 동일한 계산이 여러 모양의 객체에 대해 이뤄지고, 또 객체의 내용에 적응한다

는 것을 뜻한다. 자바에서 모든 인스턴스 메소드는 다형적이다.

x.getMeasure( )처럼 다형적인 메소드가 호출된다면, 호출될 수 있는 getMeasure는 여

러 종류이다. 동일한 메소드 이름으로 다른 메소드들을 호출하는 것을 본 적이 있을 것이다 -

특히 이름이 오버로딩(overloading)되었을 때, 다시 말해 하나의 클래스가 동일한 이름을 갖

지만 매개변수 타입이 다른 여러 개의 메소드를 갖고 있을 때이다. 예를 들어 BankAccount

클래스는 두 개의 생성자 BankAccount( )와 BankAccount(double)를 가질 수 있다. 컴파

일러는 프로그램을 컴파일할 때 매개변수의 타입을 보고 적절한 메소드를 선택하게 된다.

account = new BankAccount( );

// 컴파일러는 BankAccount( )를 선택한다.

account = new BankAccount(10000);

// 컴파일러는 BankAccount(double)을 선택한다.

다형성과 오버로딩 사이에는 중요한 차이점이 있다. 컴파일러는 오버로딩된 메소드 중 어

448 생각하는 Big Java

떤 메소드를 호출할지 프로그램이 실행되기 전 컴파일 시에 선택한

다. 이런 메소드 선택법을 초기 바인딩(early binding)이라고 부른

다. 하지만 x.getMeasure( )에 대해서 컴파일러는 어떤 getMeasure

메소드를 연결해야 할 지를 알 수가 없다. 그래서 컴파일러가 아닌 가

상 머신이 적절한 메소드를 선택한다. 이런 메소드 선택법을 후기 바

인딩(late binding)이라고 부른다.

지금까지 살펴본 인터페이스 개념은 재사용이 가능한 클래스를 작성하는데 유용하게 쓰일

수 있다. 하지만 현실적인 문제로서 Measurable 인터페이스를 사용하기에는 상당히 심각한

제약들이 있다.

■ 오직 직접 다루고 있는 클래스에만 Measurable 인터페이스를 추가시킬 수 있다. 만약

Rectangle 객체 집합을 처리하고 싶다 해도, Rectangle 클래스가 다른 인터페이스를 구현하

도록 만들 수 없다 - 이것은 고칠 수 없는 시스템 클래스다.

■ 오직 한 가지 방법으로만 객체를 측정(measure)할 수 있다. 만약 은행 잔고와 이자율로 예금

계좌를 분석하고 싶다면, 어떻게 해야 할지 모를 것이다.

그러므로 DataSet 클래스에 대해 다시 생각해 보도록 하자. 자료 집합(data set)은 추가

될 객체를 측정할 수 있어야 한다. 객체가 Measurable 타입이 될 필요가 있다는 말은, 측정

에 대한 책임이 추가되는 객체 자신에게 있다는 뜻이다. 이것이 우리가 언급했던 제약에 대

한 이유이다. 만약 다른 객체가 측정을 수행할 수 있게 되면 좋을 것이다. 그러므로 측정 메

소드를 다른 인터페이스로 옮기도록 하자.

public interface Measurer

{

double measure(Object anObject);

}

measure 메소드는 객체를 측정하고 측정값을 리턴한다. 이는 모든 객체는 Object 타입으

로 변환될 수 있다는 점을 이용한다. Object 객체는 모든 자바 클래스의 최하위 공통 분모이

기 때문에 모든 객체들은 Object 타입이 될 수 있다. 11장에서 Object 타입에 대해 더 자세

하게 논의해 보도록 하겠다.

향상된 DataSet 클래스는 Measurer 객체(Measurer 인터페이스를 구현한 클래스의 객

만약 컴파일러가 실행될 메소

드를 선택한다면 메소드가 초

기 바인딩됐다고 하고, 만약 메

소드가 프로그램이 실행될 때

선택된다면 후기 바인딩됐다고

한다.

9.4

재사용성을 증가시키기 위해 전략 인터페이스 사용하기

Chapter 9 인터페이스와 다형성 449

Chapter 09

체)와 함께 생성된다. 이 객체는 아래처럼 measurer 인스턴스 변수 안에 저장되어 측정을

수행하기 위해 사용될 것이다.

public void add(Object x)

{

sum = sum + measurer.measure(x);

if (count == 0



measurer.measure(maximum) < measurer.measure(x))

maximum = x;

count++;

}

이 절의 끝에서 DataSet 클래스에 대한 전체 소스 코드를 볼 수 있을 것이다.

이제는 어떤 종류의 측정이든 가능한 측정기를 정의할 수 있게 되었다. 예를 들어 다음에

rectangles(사각형)의 넓이(area)를 측정할 수 있는 방법이 나와 있다. 클래스를 다음처럼

정의한다.

class RectangleMeasurer implements Measurer

{

public double measure(Object anObject)

{

Rectangle aRectangle = (Rectangle)anObject;

double area = aRectangle.getWidth( )

* aRectangle.getHeight( );

return area;

}

}

지금 문제가 되는 측정기가 오직 사각형을 측정한다 할지라도, measure 메소드가 Object

타입의 매개변수를 받아야 한다는 것이다. 메소드 용법은 반드시 Measurer 인터페이스의

measure 메소드 용법와 일치해야 한다. 그러므로 Object 매개변수는 Rectangle 타입으로

캐스팅된다.

Rectangle aRectangle = (Rectangle)anObject;

이제 RectangleMeasurer 클래스의 객체를 생성한 뒤 DataSet 생성자로 전달하자.

Measurer m = new RectangleMeasurer( );

DataSet data = new DataSet(m);

450 생각하는 Big Java

다음으로 rectangle을 data set에 추가시키자. RectangleMeasurer 객체는 넓이를 측정

할 것이다.

data.add(new Rectangle(5, 10, 20, 30));

data.add(new Rectangle(10, 20, 30, 40));

...

이 data set에 다른 타입의 객체를 추가하면 어떻게 될까? add 메소드는 불평을 하지 않

는다 - 이것은 어떤 객체든 다 받을 것이다. 하지만 측정기가 Coin 레퍼런스를 Rectangle

레퍼런스로 변환하려할 때 예외가 발생하고 프로그램이 종료될 것이다.

DataSet 클래스의 measurer 객체 같은 객체를 전략 객체(strategy object)라고 부른다.

왜냐하면 이 객체가 계산에 대해 특별한 전략을 적용시키기 때문이다. 다른 전략에 대해서

간단히 다른 전략 객체를 사용하라. 예를 들어 사각형(rectangle)의 둘레를 측정하기 위해

다른 전략을 정의하는 것은 간단할 것이다.

[그림 2]에는 이 솔루션에 대한 클래스와 인터페이스의 UML 도표가 나와 있다. [그림 1]

에서처럼 DataSet 클래스는 이 클래스가 처리하는 객체인 Rectangle 클래스와 결합되어 있

다. 하지만 [그림 1]과는 다르게 Rectangle 클래스는 다른 클래스와 결합되어 있지 않다. 대

신에 사각형을 처리하기 위해, 헬퍼(helper) 클래스 RectangleMeasurer를 만들어야 할 것

이다.

􄧔 그림 2 DataSet 클래스와 Measurer 인터페이스의 UML 도표

RectangleMeasurer 클래스는 매우 작은 클래스이다. 이것의 객체는 어떤 상태도 저장하

지 않는다. 단지 DataSet 클래스가 Measurer 인터페이스를 구현한 클래스의 객체를 필요

로하기 때문에 이것을 필요로 할 뿐이다. 이것처럼 상당히 전략적인 목적을 위한 클래스를

작성하게 될 때, 이 클래스를 필요로 하는 메소드 안에 선언할 수 있다.

Chapter 9 인터페이스와 다형성 451

Chapter 09

public static void main(String[ ] args)

{

class RectangleMeasurer implements Measurer

{

...

}

Measurer m = new Rectanglemeasurer( );

DataSet data = new DataSet(m);

...

}

이런 클래스는 내부 클래스(inner class)라고 불린다. 내부 클래스

는 다른 클래스 안에서 정의된 클래스를 말한다. 이런 배열은 프로그

램을 보는 사람에게 RectangleMeasurer 클래스가 이 메소드 범위

밖에서는 의미를 갖지 않는다는 것을 가르쳐준다. 메소드 안에 있는

내부 클래스는 외부에서 접근할 수 있는 클래스가 아니기 때문에 이

것을 자세하게 문서화할 필요는 없다.

이 프로그램에 대한 소스 파일을 컴파일할 때, 디스크에 저장되는 클래스 파일들을 살펴보

기 바란다 - 내부 클래스들이 DataSetTest$1$RectangleMeasurer.class 같은 이상한 이름

의 파일로 저장되는 것을 볼 수 있다. 정확한 이름은 중요하지 않다. 요점은 컴파일러는 내부

클래스를 일반적인 클래스 파일로 만든다는 사실이다.

01 /**

02 데이터 값들의 평균을 계산한다.

03 */

04 public class DataSet

05 {

06 /**

07 주어진 mesurer를 이용해 빈 데이터 집합을 생성

08 @param aMeasurer 데이터값을 측정하기 위해 사용

09 */

10 public DataSet(Measurer aMeasurer)

11 {

12 sum = 0;

13 count = 0;

14 maximum = null;

15 measurer = aMeasurer;

16 }

파일 DataSet.java

내부 클래스는 다른 클래스 안

에서 선언된다. 내부 클래스는

보통 프로그램의 다른 곳에서

보여서는 안 되는 전략적인 클

래스를 만들기 위해 사용된다.

452 생각하는 Big Java

17

18 /**

19 데이터 집합에 데이터 값을 더한다.

20 @param x 데이터 값

21 */

22 public void add(Object x)

23 {

24 sum = sum + measurer.measure(x);

25 if (count == 0

26

measurer.measure(maximum)
27 maximum = x;

28 count++;

29 }

30

31 /**

32 평균을 구한다.

33 @return 평균, 더해진 데이터가 없다면 0

34 */

35 public double getAverage( )

36 {

37 if (count ==0)return 0;

38 else return sum /count;

39 }

40

41 /**

42 더해진 데이터 중 가장 큰 값을 얻는다.

33 @return 최대값, 더해진 데이터가 없다면 0

44 */

45 public Object getMaximum( )

46 {

47 return maximum;

48 }

49

50 private double sum;

51 private Object maximum;

52 private int count;

53 private Measurer measurer;

54 }

01 import java.awt.Rectangle;

02

파일 DataSetTest.java

Chapter 9 인터페이스와 다형성 453

Chapter 09

03 /**

04 이 프로그램은 Measurer의 사용법을 보여준다.

05 */

06 public class DataSetTest

07 {

08 public static void main(String [ ] args)

09 {

10 class RectangleMeasurer implements Measurer

11 {

12 public double measure(Object anObject)

13 {

14 Rectangle aRectangle = (Rectangle)anObject;

15 double area = aRectangle.getWidth( )

16 * aRectangle.getHeight( );

17 return area;

18 }

19 }

20

21 Measurer m = new RectangleMeasurer( );

22

23 DataSet data = new DataSet(m);

24

25 data.add(new Rectangle(5,10,20,30));

26 data.add(new Rectangle(10,20,30,40));

27 data.add(new Rectangle(20,30,5,10));

28

29 System.out.println("Average area ="

30 + data.getAverage( ));

31 Rectangle max = (Rectangle)data.getMaximum( );

32 System.out.println("Maximum area =" + max);

33 }

34 }

01 /**

02 다른 객체를 측정할 수 있는 객체를 만드는 클래스를 기술

03 */

04 public interface Measurer

05 {

06 /**

07 객체를 측정한다.

08 @param anObject 측정될 객체

파일 Measurer.java

454 생각하는 Big Java

09 @return the measure

10 */

11 double measure(Object anObject);

12 }

Syntax 9.4 : 내부 클래스

메소드 내부에서 선언됨

class OuterClassName

{

method signature

{

...

class InnerClassName

{

methods

fields

}

...

}

...

}

클래스 내부에서 선언됨

class OuterClassName

{

methods

fields

accessSpecifier class InnerClassName

{

methods

fields

}

...

}



public class Test

{

public static void main(String[ ] args)

Chapter 9 인터페이스와 다형성 455

Chapter 09

{

class RectangleMeasurer implements Measurer

{

...

}

...

}

}

목적

외부 클래스의 메소드인 것처럼 클래스의 변수와 메소드가 접근되는 내부 클래스 정의

하기

익명 내부 클래스

만약 어떤 것이 이름이 없다면 이것을 익명(anonymous)라고 부른다. 프로그램에서 한

번만 사용되는 것들은 이름이 필요 없을 것이다.

Coin a Coin = new Coin(0.1, "dime");

data.add(aCoin);

예를 들어 만약 Coin이 한 번 사용된 뒤에 동일한 메소드 안의 어느 곳에서도 사용되지

않는다면 위의 구문을 다음처럼 고칠 수 있을 것이다.

data.add(new Coin(0.1, dime));

객체 new Coin(0.1,“ dime”)은 익명 객체이다. 이것은 이름에 대한 문제를 피할 수 있

게 하기 때문에 자주 사용되고 있다. 만약 coin을 c로 부를지 dime으로 부를지 aCoin으

로 부를 지를 결정하는 데에 애를 먹었다면, 앞에서의 기분을 이해할 수 있을 것이다.

자바에서는 내부 클래스에도 이와 유사한 상황이 발생한다. RectangleMeasurer 객체

가 한 개 생성되지만, 이 클래스를 두 번 다시 사용하지 않고 클래스의 객체가 한 번만 사

용된다면 익명 클래스를 정의할 수 있다.

public static void main(String[ ] args)

{

Measurer m = new Measurer( )

// 익명 클래스의 객체를 생성함

Advanced Topic 9.2

456 생각하는 Big Java

// 클래스 정의가 밑에 나온다.

{

public double measure(Object anObject)

{

Rectangle aRectangle = (Rectangle)anObject;

double area =

aRectangle.getWidth( )

* aRectangle.getHeight( );

return area;

}

};

DataSet data = new DataSet(m);

...

}

위의 코드는 measurer 메소드를 정의함으로써 Measurer 인터페이스를 구현해 놓은

클래스의 객체를 생성한다.

몇몇 프로그래머들은 이런 스타일을 좋아하지만, 대다수의 신참들은 위의 코드를 당황스

럽게 생각할 것이다. 이 책에서는 이에 대해 더 이상 설명하지는 않을 것이다. [참고문헌 1]

에서 더 많은 정보를 얻기 바란다.

이번 절에서는 타이머 이벤트(timer event)에 대해 배워볼 것이다. 이벤트 핸들링(event

handling)은 GUI 프로그램의 버튼이나 메뉴에서처럼 동일한 방법으로 인터페이스를 사용

하기 때문이다. 하지만 타이머가 GUI 프로그램보다 더 간단하기 때문에, 더 본질적인 것에

집중을 할 수 있을 것이다.

타이머는 애니메이션을 프로그래밍하는 데에 있어서도 유용하게 사용된다([문제 P9.13]

참조).

javax.swing 패키지의 Timer 클래스는 일정 시간 간격마다 연속적으로 이벤트(event)를 발

생시킨다. 정기적인 간격으로 객체를 갱신하고 싶다면 이것은 매우 유용하게 사용될 수 있다.

예를 들어 애니메이션을 하려 한다면, 이미지를 움직이는 것처럼 보이도록 하기 위해 1초에

10번 화면을 갱신하도록 하고 싶을 것이다.

9.5

타이머 이벤트 처리하기

Chapter 9 인터페이스와 다형성 457

Chapter 09

타이머 이벤트가 발생했을 때 타이머는 이벤트 리스너(event

listener)라고 불리는 어떤 객체에게 통보할 필요가 있다. Timer 클래

스의 개발자는 프로그래머들이 Timer를 어떻게 사용할 지를 상관하

지 않고 각자 리스너 객체 타입을 지정해서 원하는 메소드를 호출할

수 있도록 만들었다. 개발자들은 이런 목적을 위해 ActionListener

인터페이스를 선택했다.

public interface ActionListener

{

void actionPerformed(ActionEvent event);

}

타이머를 사용할 때, ActionListener 인터페이스를 구현한 클래스를 정의할 필요가 있다.

actionPerformed 메소드 안에 발생시키고 싶은 행동(action)을 집어 넣어라. 클래스의 객체

를 생성시키고, 이것을 Timer 생성자로 전달하라. 마지막으로, 타이머를 시작시켜라.

class MyListener implements ActionListener

{

public void actionPerformed(ActionEvent event)

{

// 다음의 작업은 타이머 이벤트가 발생했을 때마다 실행된다.

리스너가 할 작업

}

}

MyListener listener = new MyListener( );

Timer t = new Timer(interval, listener);

t.start( );

후에 타이머는 매 interval 밀리세컨드(millisecond)마다 리스너 객체의 actionPerformed

메소드를 호출한다. actionPerformed 메소드의 event 매개변수는 타이머 이벤트에 대한 더 세

부적인 정보를 포함할 것이다. 하지만 실제로는 대부분의 리스너들이 이 매개변수를 무시한다.

다음은 조금 우스운 예제 프로그램이다 - 타이머는 0까지 숫자를 세어 내려간다.

10

9

...

2

1

0

Liftoff!

타이머는 일정한 시간 간격마다

타이머 이벤트를 발생시킨다.

특정한 이벤트가 발생했을 때,

이벤트 리스너가 통보를 받는다.

458 생각하는 Big Java

하지만, 즉시 모든 내용들을 출력하는 for 루프와는 다르게, 위는 1초씩 간격을 두고 각 줄

을 출력한다.

프로그램이 타이머를 설정한 후에도 살아있도록 만들기 위해, 프로그램은 메시지 다이얼

로그를 화면에 뿌린다. 프로그램을 종료시키기 위해 OK버튼을 클릭하기 바란다.

01 import java.awt.event.ActionEvent;

02 import java.awt.event.ActionListener;

03 import javax.swing.JOptionPane;

04 import javax.swing.Timer;

05

06 /**

07 이 프로그램은 타이머 클래스를 테스트한다.

08 */

09 public class TimerTest

10 {

11 public static void main(String[ ] args)

12 {

13 class CountDown implements ActionListener

14 {

15 public CountDown(int initialCount)

16 {

17 count = initialCount;

18 }

19

20 public void actionPerformed(ActionEvent event)

21 {

22 if (count >= 0)

23 System.out.println(count);

24 if (count == 0)

25 System.out.println("Liftoff!");

26 count--;

27 }

28

29 private int count;

30 }

31

32 CountDown listener = new CountDown(10);

33

34 final int DELAY = 1000;// 타이머가 1초 간격으로 작동하도록 밀리세컨드값 1000을 준다.

35 Timer t = new Timer(DELAY,listener);

파일 TimerTest.java

Chapter 9 인터페이스와 다형성 459

Chapter 09

36 t.start( );

37

38 JOptionPane.showMessageDialog(null,"Quit?");

39 System.exit(0);

40 }

41 }

앞의 예제는 너무 간단한게 아닌가 하는 생각이 들기도 할 것이다. 대개, 이벤트 리스너는

actionPerformed 메소드 안의 다른 객체를 변경하길 원한다. 이벤트가 발생했을 때, 조작되

길 바라는 여러 객체들을 액세스하기 위한 인스턴스 변수들을 저장할 필요가 있기 때문에 위

의 사항은 리스너 클래스의 구조를 더 복잡하게 만들 수도 있을 것이다. 다행히도, 리스너에

대해서 내부 클래스를 사용한다면, 자바 컴파일러는 자동적으로 이런 변수들의 일부를 생성

한다. 다음은 예제이다.

투자금이 늘어나는 것을 모의 실험하기 위해, 1초마다 한 번씩 은행 계좌에 이자를 추가

시키고 싶다고 하자. 프로그램은 아래처럼 1초에 한 번씩 잔금을 출력할 것이다.

Balance = 1050.0

Balance = 1102.5

Balance = 1157.625

Balance = 1215.50625

...

다음은 BankAccoutn 객체이다.

public class TimerTest

{

public static void main(String[ ] args)

{

final BankAccount account = new BankAccount(1000);

...

}

private static final double RATE = 5;

}

account 변수가 final로 선언된 이유는 곧 알게 될 것이다. 리스너 클래스의 action

Performed 메소드는 계좌에 이자를 추가하고 새로운 잔액을 출력한다.

class InterestAdder implements ActionListener

{

460 생각하는 Big Java

public void actionPerformed(ActionEvent event)

{

double interest = account.getBalance( )

* RATE / 100;

account.deposit(interest);

System.out.println(Balance =

+ account.getBalance( ));

}

}

보다시피, actionPerformed 메소드는 BankAccount 객체를 접근

할 필요가 있다. 물론, BankAccount 인스턴스 변수를 추가해서, 이

것을 InterestAdder 생성자 안에 집어넣을 수도 있을 것이다. 하지만

내부 클래스에 대해서는 다른 편리한 방법이 있다. 내부 클래스는 클

래스 정의가 있는 곳에서 이미 정의되어 있는 변수들을 접근할 수 있다. InterestAdder 정의

가 main 메소드 안의 account 변수의 정의 아래에 있기 때문에, 이 클래스는 main 메소드의

지역 변수를 접근시킬 수 있다!

public static void main(String[ ] args)

{

final BankAccount account = new BankAccount(1000);

class InterestAdder implements ActionListener

{

public void actionPerformed(ActionEvent event)

{

double interest = account.getBalance( )

* RATE / 100;

// account 접근 가능

...

}

}

...

}

컴파일러는 InterestAdder 클래스를 컴파일할 때, 이 클래스의 메소드 중 하나가 클래스

를 감싸고 있는 범위 안에 있는 변수를 액세스하려는 것에 주목하게 된다. 컴파일러는 클래

스 내부에 인스턴스 변수를 생성하고 이것을 외부 변수의 값으로 초기화한다. 이것은 프로그

래머에게 있어서 상당히 편리한 기능이다. 이를 사용해서 전략적 헬퍼 클래스를 제공하는데

큰 짐을 덜 수 있을 것이다. 사실 컴파일러가 내부 클래스를 빌드할 때 무슨 작업을 수행하는

내부 클래스의 메소드는 주위

범위의 변수를 액세스할 수

있다.

Chapter 9 인터페이스와 다형성 461

Chapter 09

지에 대해 알 필요는 없다. 간단하게 감싸고 있는 범위 안의 어떤 변

수든 액세스하기만 하면, 컴파일러가 나머지를 처리해 줄 것이다.

이번엔 또 다른 기술적인 조언을 해보도록 하겠다. 내부 클래스는

아무런 제한 없이 감싸고 있는 범위의 필드를 모두 액세스할 수 있다.

하지만 내부 클래스에 의해 액세스되는 지역 변수는 final로 선언되어야 한다. 따라서 내부

클래스의 메소드가 사용하는 지역 변수의 값은 모호하지 않을 것이다. 이것은 제한처럼 느껴

질 수있지만, 실제로 프로그래밍을 할 때에는 제한으로 느껴지지 않을 것이다. 객체 변수가

항상 동일한 객체를 참조한다면, 객체 변수를 final로 하면 된다는 것을 기억하기 바란다. 객

체의 상태는 변경될 수 있지만, 변수가 다른 객체를 가리킬 수는 없다. 예를 들어 우리의 프

로그램에서는 절대 account 변수가 여러 개의 bank account 객체를 가리키도록 하지 않는

다. 따라서 이것을 final로 선언해도 아무런 문제가 없을 것이다.

[그림 3]에 클래스와 인터페이스의 UML 도표가 나와 있다. Timer 클래스와 Bank

Account 클래스가 어떻게 서로 분리되어 있는 지를 주목하기 바란다. 두 클래스 모두 다른

클래스의 존재를 모르고 있다. InterestAdder 헬퍼 클래스가 이 두 클래스 사이에서 가교 역

할을 하고 있다.

다음은 프로그램에 대한 소스 코드이다.

01 import java.awt.event.ActionEvent;

02 import java.awt.event.ActionListener;

03 import javax.swing.JOptionPane;

04 import javax.swing.Timer;

05

06 /**

07 이 프로그램은 타이머를 이용하여 1초마다 은행 계좌에

08 이자를 더한다.

09 */

10 public class TimerTest

11 {

12 public static void main(String [ ] args)

13 {

14 final BankAccount account = new BankAccount(1000);

15

16 class InterestAdder implements ActionListener

17 {

18 public void actionPerformed(ActionEvent event)

19 {

20 double interest = account.getBalance( )

파일 TimerTest.java

내부 클래스에 의해 액세스되

는 지역 변수는 반드시 final로

선언되어야 한다.

462 생각하는 Big Java

21 * RATE /100;

22 account.deposit(interest);

23 System.out.println("Balance = "

24 + account.getBalance( ));

25 }

26 }

27

28 InterestAdder listener = new InterestAdder( );

29

30 final int DELAY =1000;// 타이머가 1초 간격으로 작동하도록 밀리세컨드값 1000을 준다.

31 Timer t = new Timer(DELAY,listener);

32 t.start( );

33

34 JOptionPane.showMessageDialog(null,"Quit?");

35 System.exit(0);

36 }

37

38 private static final double RATE = 5;

39 }

􄧔 그림 3 Timer 이벤트를 처리하기 위한 클래스와 인터페이스

이제 이벤트 리스너를 어떻게 작성할 수 있는 지를 알 수 있을 것이다. GUI 프로그램을

작성할 때 이것은 상당히 자주 해야 하는 작업이 될 것이다. 버튼이나 슬라이더, 체크박스,

마우스, 타이머 등의 소스(source)는 모두 이벤트를 발생시킨다. 처리하고 싶은 이벤트 소스

마다 이벤트 리스너를 부착할 필요가 있다. 예를 들어, 버튼이 클릭됐을 때 무엇인가를 처리

해야 할 프로그램을 작성하게 된다면, 버튼에 이벤트 리스너를 부착해야 한다. 다음 장에서

이에 대한 처리과정을 더 자세하게 알아보도록 하겠다.

Chapter 9 인터페이스와 다형성 463

Chapter 09

메소드 구현에서 메소드 시그너처 변경하기

(역주 : 시그너처(signature)는 메소드의 리턴 타입과 매개변수 정의를 말함. 다른 언어에서

는 프로토타입(prototype)이라고도 표현함)

인터페이스를 구현할 때, 클래스는 인터페이스에서 정의되어 있는 메소드들을 똑같은

용법으로 구현해야 한다. 매개변수나 리턴 타입을 실수로 바꾸게 되는 것은 자주 있는 실

수다. 다음은 아주 고전적인 예이다.

class MyAction implements ActionListener

{

public void actionPerformed( )

// 아차...ActionEvent 매개변수를 잊어버렸다.

{

...

}

}

컴파일러가 지적하는 것에 따르면, 이 클래스는 두 개의 메소드를 갖게 된다.

public void actionPerformed(ActionEvent event)

public void actionPerformed( )

첫 번째 메소드는 정의되어 있지 않다. 컴파일러는 이 메소드가 사라졌다고 경고를 할 것

이다. 에러 메시지를 조심스럽게 읽고 매개변수와 리턴 타입에 주의를 기울여야 할 것이다.

운영체제

운영체제가 없다면 컴퓨터는 무용지물이라고 할 수 있다. 적어도 파일의 위치를 찾고 프

로그램을 시작시키기 위해서라도 운영체제가 필요하다. 독자들이 실행시키는 프로그램들은

다른 프로그램과 상호작용을 하고 장치들을 접근하기 위해 운영체제의 서비스를 필요로 한

다. 거대한 컴퓨터에서의 운영체제는 퍼스널 컴퓨터에 들어있는 운영체제가 서비스하는 것

보다 훨씬 더 많은 내용을 서비스해야 할 필요가 있다. 다음은 일반적인 서비스 내용들이다.

■ 프로그램 적재(program loading) : 모든 운영체제는 응용 프로그램을 실행시키기 위한

방법을 제공한다. 사용자들은 프로그램의 이름을 타이핑하거나, 아이콘을 클릭하는 것에 의

해 무슨 프로그램이 실행될 지를 지정할 수 있다. 운영체제는 프로그램 코드의 위치를 찾은

Common Error 9.1

Random Fact 9.1

464 생각하는 Big Java

후, 메모리에 적재한 뒤 프로그램을 실행시킨다.

■ 파일 관리(managing files) : 하드 디스크 같은 저장 장치는 전기적으로 상당한 양의 0, 1

순열을 저장할 수 있다. 저장소 구획에 복사하고, 이것을 파일이나 폴더 등으로 구성하는 것

은 운영체제의 몫이다. 운영체제는 또한 전력이 나갔을 때 하드 디스크 안에 들어있는 것들

을 보호하기 위해 파일 시스템 안에 여분 공간이나 보안 설정을 부과한다. 몇몇 운영체제들

은 위에 나온 것보다 더 많은 작업을 수행하기도 한다.

■ 가상 메모리(virtual memory) : 램(RAM)은 값이 비싸서, 몇몇 컴퓨터만이 사용자가 동

시에 실행되길 바라는 모든 프로그램과 이에 필요한 데이터를 충분히 저장시킬 수 있는 램

을 보유하고 있다. 대부분의 운영체제는 하드 디스크 안에 데이터를 저장시킴으로써 사용가

능한 메모리를 확장시킨다. 응용 프로그램은 필요한 데이터가 메모리에 있는지 가상 메모리

디스크 저장소에 있는 지를 알아차리지 못한다. 프로그램이 현재 램에 있지 않은 데이터를

액세스하려고 하면, 프로세서는 이것을 감지하고 운영체제에 통보한다. 운영체제는 하드 디

스크에 있는 필요한 데이터를 램에 집어넣고, 동시에 오랫동안 액세스되지 않은 동일한 크

기의 메모리 블록을 하드 디스크로 뺀다.

■ 다중 사용자 처리하기(handling multiple users) : 거대하고 강력한 컴퓨터의 운영체제는

동시에 여러 명의 사용자가 사용할 수 있도록 지원해 준다. 각각의 사용자는 서로 떨어진 터

미널을 통해서 컴퓨터에 접속한다. 운영체제는 올바른 계정과 비밀번호를 입력했는 지를 검

사함으로써 사용자를 확인한다. 운영체제는 각각의 사용자에게 번갈아가며 짧은 시간의

CPU 사용 시간을 할당한다.

■ 멀티태스킹(multitasking) : 만약 독자가 컴퓨터의 유일한 사용자라 할 지라도, 여러 개의

응용 프로그램을 동시에 실행되도록 하고 싶을 경우가 있을 것이다 - 예를 들어, 한 창에서

이메일을 보면서 다른 창에서 자바 컴파일러를 실행시키고 싶을 수 있다. 운영체제는 실행

시키고 있는 응용 프로그램들이 자신의 처리를 수행할 수 있도록 CPU 타임을 나눠줄 책임

이 있다.

■ 프린팅(printing) : 운영체제는 여러 응용 프로그램에서 전달된 프린트 요청을 큐에 집어

넣는다. 이것은 서로 다른 프로그램 사이에서 동시에 전달된 단어들이 섞이지 않도록 하기

위해서 필요하다.

■ 윈도우(windows) : 다수의 운영체제들은 여러 개의 윈도우로 만들어진 바탕화면

(desktop)을 사용자에게 보여준다. 운영체제는 윈도우의 외형과 위치를 관리한다. 내부 치

장은 응용 프로그램이 해야 한다.

■ 폰트(fonts) : 스크린이나 프린터에 문장을 출력하기 위해, 문자의 모양이 반드시 정의되어

야 한다. 이것은 여러 종류의 스타일과 크기를 출력해야 하는 프로그램에 있어서는 상당히

중요하다. 요즘의 운영체제는 폰트 저장소를 포함하고 있다.

Chapter 9 인터페이스와 다형성 465

Chapter 09

■ 프로그램 사이의 통신(communicating between programs) : 운영체제는 프로그램 사

이에 정보를 전송할 수 있도록 해준다. 이런 전송은 잘라 붙이기(cut and paste)나 프로세

스 간의 통신(interprocess communication)으로 이루어진다. 잘라 붙이기는 사용자가

한 응용 프로그램에서 운영체제가 관리하는(클립보드(clipboard)라고 부르는) 전송 버퍼로

데이터를 복사하고 다른 응용 프로그램으로 버퍼의 내용을 삽입하는 방법으로 이루어진다.

반면 프로세스 간의 통신은 응용 프로그램이 사용자와 관련 없이 직접 데이터를 전송하는

방법으로 이루어진다.

■ 네트워킹(networking) : 운영체제는 응용 프로그램이 네트워크에 연결된 다른 컴퓨터의

정보에 접근할 수 있도록 프로토콜과 서비스를 제공해 준다.

􄧔 그림 4 Linux 운영체제 상의 그래픽(Graphical) 소프트웨어 환경

오늘날 가장 많이 사용되는 운영체제는([그림 4]에 나온 Linux 등으로 파생된) UNIX

와 Windows와 MacOS다.

● 단원 요약 ●

1. 자바 인터페이스(Interface)는 메소드와 메소드의 용법(signature)을 선언한다. 클래스와는

다르게, 인터페이스는 메소드를 구현하지 않는다.

2. 인터페이스를 구현할 때, 클래스는 반드시 인터페이스가 요구하는 모든 메소드들을 구현해야

한다.

466 생각하는 Big Java

3. 인터페이스는 클래스 사이의 결합성을 줄여준다.

4. 클래스가 인터페이스를 구현했다면, 클래스 타입을 인터페이스 타입으로 변환시킬 수 있다.

5. 인터페이스 타입을 클래스 타입으로 변환하기 위해서는 형 변환(cast)이 필요하다.

6. instanceof 연산자는 객체가 지정된 타입에 속하는지 아닌지를 검사한다.

7. 다형성(polymorphism)의 의미는 객체의 실제 타입에 따라 행동이 달라질 수 있다는 것이다.

8. 만약 컴파일러가 사용 가능한 메소드 중 하나를 선택한다면, 이 메소드가 초기 바인딩(early

binding)이 된다고 말하고, 만약 프로그램이 실행되는 동안에 메소드가 선택되면, 이 메소드

가 후기 바인딩(late binding)이 된다고 말한다.

9. 내부 클래스(inner class)는 다른 클래스 안에서 선언된다. 보통 프로그램의 다른 곳에서 사용

할 수 없는 전략적인 클래스를 선언할 때, 이 클래스를 내부 클래스로서 선언한다.

10. 타이머는 일정한 간격으로 타이머 이벤트를 발생시킨다.

11. 이벤트 리스너(event listener)는 지정된 이벤트가 발생했을 때 반응을 하게 된다.

12. 내부 클래스의 메소드는 클래스의 주위를 감싸고 있는 범위의 모든 변수들을 액세스할 수

있다.

13. 내부 클래스의 메소드에 의해 액세스되는 지역 변수들은 반드시 final로 선언되어야 한다.

참고문헌

1. Cay S. Horstmann and Gary Cornell, Core Java Fundamentals, 5th Edition,

Prentice-Hall, 2001.

● 이 장에서 소개된 클래스·객체·메소드 ●

java.awt.event.ActionEvent

java.awt.event.ActionListener

actionPerformed

javax.swing.Timer

start

● 복습 문제 ●

C가 인터페이스 I와 J를 구현한 클래스라고 가정해 보자. 다음의 할당 중

무엇이 형 변환을 필요로 할까?

C c = ...;

문제 R9.1

Chapter 9 인터페이스와 다형성 467

Chapter 09

I i = ...;

J j = ...;

c = i; // 1

j = c; // 2

i = j; // 3

C가 인터페이스 I와 J를 구현한 클래스라고 가정해 보자. 다음의 할당 중

예외를 던지는 것들은 무엇일까?

C c = new C( );

I i = c; // 1

J j = (J)i; // 2

C d = (C)i; // 3

Sandwich 클래스가 Edible 인터페이스를 구현했다고 가정해 보자. 다음

의 할당 중 어느 것이 합법적(legal)일까?

Sandwich sub = new Sandwich( );

Edible e = sub; // 1

Rectangle cerealBox = new Rectangle(5, 10, 20, 30);

Edible f = cerealBox; // 2

f = (Edible)cerealBox; // 3

sub = e; // 4

sub = (Sandwich)e; // 5

sub = (Sandwich)cerealBox; // 6

(BankAccount)x와 같은 형 변환은 (int)x 등의 숫자 형 변환과 어떻게

다른가?

클래스 Rectangle2D.Double과 Ellipse2D.Double, Line2D.Double은

Shape 인터페이스를 구현해 놓았다. Graphics2D 클래스는 Shape 인터페이스에는 의존

하고 있지만, Rectangle이나 Ellipse나 Line 클래스에는 의존하고 있지 않다. 이들의 관

계를 UML 도표로 나타내 보아라.

r이 new Rectangle(5, 10, 20, 30)을 가리키고 있다고 가정해 보자. 다

음의 조건식 중 어느 것이 true일까(Hint : Rectangle 클래스가 구현하고 있는 인터페이

스에 대해 API 도큐먼트를 참조하라)?

■ r instanceof Rectangle

■ r instanceof Shape

■ r instanceof Point

문제 R9.6

문제 R9.5

문제 R9.4

문제 R9.3

문제 R9.2

468 생각하는 Big Java

■ r instanceof Object

■ r instanceof ActionListener

■ r instanceof Serializable

Rectangle2D.Double이나 Ellipse2D.Double이나 Line2D.Double 등의

클래스들은 Shape 인터페이스를 구현해 놓았다. Shape 인터페이스는 모양을 완전히 감

싸는 사각형을 리턴하는 메소드를 갖고 있다.

Rectangle getBounds( )

다음의 메소드 호출을 살펴보자.

Shape s = ...;

Rectangle r = s.getBounds( );

왜 이것이 다형성의 예가 될 수 있는지를 설명해 보아라.

자바에서 x.f( ) 같은 메소드 호출은 후기 바인딩을 사용한다 - x가 가리

키고 있는 객체의 타입에 따라 호출되는 메소드가 결정된다. 자바에서 초기 바인딩이 사용

되는 두 종류의 메소드 호출 방법을 제시해 보아라.

평균 월급과 최고액의 월급을 찾기 위해 고용인 배열을 처리할 필요가 있

다고 가정해 보자. Measureable 객체를 처리하는 맨 처음 구현한 형식의 DataSet 클래

스를 사용하기 위해 무엇이 필요한지 논의해 보아라. 다시 구현한 형식의 DataSet 클래스

를 사용하기 위해서는 무엇이 필요한가? 어느 쪽이 더 쉬운가?

처음으로 구현한 DataSet 클래스에 String 객체를 추가하면 어떻게 될

까? RectangleMeasurer 클래스를 사용해서 다시 구현한 DataSet에, String 객체를 추

가하면 어떻게 되는가?

최상위 클래스로(다시 말해, 내부 클래스가 아닌 외부 클래스로) Rectangle

Measurer를 구현할 필요가 있다면, RectangleMeasurer 클래스를 사용하는 테스트 프로그

램을 어떻게 고쳐야할까?

최상위 클래스로 InterestAdder를 구현할 필요가 있다면, InterestAdder

클래스를 사용하는 테스트 프로그램을 어떻게 고쳐야할까?

전략적 객체(strategy object)란 무엇인가? DataSet 클래스에 대해 사용

할 만한 다른 전략적 객체를 떠올려 보아라(Hint : 문제 P9.8).

문제R9.14 이벤트와이벤트리스너의차이점은무엇인가?

문제 R9.13

문제 R9.12

문제 R9.11

문제 R9.10

문제 R9.9

문제 R9.8

문제 R9.7

Chapter 9 인터페이스와 다형성 469

Chapter 09

Timer 객체가 여러 개의 이벤트 리스너에게 이벤트를 통보할 수 있을까?

만약 그렇다면, 어떻게 할 수 있을까?(Hint : API 도큐먼트를 참조하라)

최상위 클래스와 내부 클래스에 대해 생각해 보아라. 어떤 변수들이 메소

드 f를 액세스할 수 있을까?

public class T

{

public void m(final int x, int y)

{

int a;

final int b;

class C implements I

{

public void f( )

{

...

}

}

final int c;

...

}

private int t;

}

내부 클래스가 final이 아닌 지역 변수를 액세스하려 한다면 무슨 일이 발

생할까? 시도해보고 이유를 설명해 보아라.

● 프로그래밍 연습문제 ●

6장의 Die 클래스는 Measurable 인터페이스를 갖고 있는가?

Measurable 인터페이스를 구현시켜서 Quiz 클래스를 정의해 보아라.

quiz(시험)는 score(점수)와(B+등의), letter grade(성적)을 갖고 있다. 첫 번째 형식의

DataSet 클래스를 사용해서 quiz 집합을 처리해 보아라. 평균 점수를 출력하고 가장 점수

가 높은 답안지의 점수와 성적을 모두 출력하라.

문제 P9.2

문제 P9.1

문제 R9.17

문제 R9.16

문제 R9.15

470 생각하는 Big Java

Person 클래스를 정의하라. person(사람)은 name(이름)과 센티미터 단

위의 height(키)를 갖고 있다. 다시 구현한 DataSet 클래스를 사용해서 Person 객체의

집합을 처리해 보아라. 평균 키와 가장 키가 큰 사람의 이름을 출력하라.

Measureable 객체를 처리하는 첫 번째 형식의 DataSet 클래스를 최소

데이터 값도 처리하도록 변경해 보아라.

Measurer 객체를 사용하는, 다시 구현한 DataSet 클래스를 최소 데이터

값도 처리하도록 변경해 보아라.

여러 개의 Measurer 객체를 사용해서, Rectangle 객체 집합에 대해 가장

긴 둘레를 갖는 사각형을 찾도록 해 보아라.

Measurer 객체를 사용하는 DataSet 클래스를 Measurable 객체도 처리

할 수 있도록 보강해 보아라. Hint : Measurer 객체를 처리할 수 있는 Measurer를 구현

한 디폴트 생성자를 만들어라.

Filter 인터페이스는 다음처럼 정의되어 있다.

public interface Filter

{

boolean accept(Object x);

}

다시 구현한 DataSet 클래스를 Measurer와 Filter 객체를 둘 다 사용할 수 있도록 변경해

보아라. 반드시 filter가 받는 객체만 처리되어야 한다. 변경된 클래스를 bank account 객

체 집합에 대해, 1,000달러보다 적은 잔금을 갖고 있는 계좌들을 제거(filter out)하면서

평균과 최대값을 찾도록 테스트해 보아라.

다음의 인터페이스를 사용하라.

public interface Drawable

{

void draw(Graphics2D g2);

}

이 인터페이스를 구현하는 Car와 House 클래스를 구현해 보아라. 4.7절에서 했던 것처

럼, Car와 House 클래스의 생성자는 car와 house의 위치를 받아야 한다.

그런 후, 랜덤하게 Drawable 레퍼런스를 생성하는 randomDrawable 메소드를 작성하

라. 랜덤하게 car와 house 중 하나를 선택한 뒤, 임의의 위치에 놓는다. 이 메소드를 10

번 호출하며, 이에 대한 그림을 그리도록 하라.

문제 P9.9

문제 P9.8

문제 P9.7

문제 P9.6

문제 P9.5

문제 P9.4

문제 P9.3

Chapter 9 인터페이스와 다형성 471

Chapter 09

Shape 인터페이스를 구현한 객체를 랜덤하게 생성하는 randomShape라

는 메소드를 작성하라(이 메소드는 임의의 위치에 사각형과 타원과 직선 몇 개를 마구잡

이로 그릴 것이다). 이 메소드를 10번 호출하고, 이에 대한 그림을 그리도록 하라.

매 초마다 현재 시간을 출력하도록 타이머를 사용해서 프로그램을 작성해

보아라. Hint : 다음의 코드는 현재 시간을 출력한다.

Date now = new Date( );

System.out.println(now);

Date 클래스는 java.util 패키지 안에 있다.

타이머를 사용하여 2초마다 계좌의 잔액을 증가시키는 프로그램을 만들

어 보아라.

타이머를 사용해서 움직이는 자동차를 그리는 애플릿을 작성하라. 1초에

10번씩 타이머 리스너의 actionPerformed 메소드는 다음과 같다.

■ 자동차를 오른쪽으로 1픽셀 이동시킨다.

■ 애플릿의 repaint 메소드를 호출한다.

다음의 인터페이스를 사용한다.

public interface InputReader

{

String readLine(String prompt) throws IOException;

}

이 인터페이스를 구현하는 두 개의 클래스 OptionPaneReader와 ConsoleReader를 정

의하라. OptionPaneReader는 입력 다이얼로그를 출력해서 입력값을 받고, Console

Reader는 System.in에 부착된 buffered reader로부터 입력값을 받는다.

그리고 나서, 다음의 스테틱 메소드를 정의하라.

public Coin readCoin(InputReader reader)

이 메소드는 사용자로부터 coin의 이름과 값을 받는다(nickel, dime, quarter 뿐만이 아

니라 외국의 동전에 대한 정보를 받을 수도 있을 것이다). 이 메소드가 다이얼로그 박스나

콘솔 윈도우 모두로부터 사용자의 입력을 받을 수 있다는 것을 예시해 보아라.

문제 P9.14

문제 P9.13

문제 P9.12

문제 P9.11

문제 P9.10

472 생각하는 Big Java

API 도큐먼트에서 표준 Comparable 인터페이스의 정의를 살펴보아라.

DataSet 클래스를 Comparable 객체를 받을 수 있도록 변경하라. 평균을 계산할 필요는 없

고, DataSet 클래스가 최소값과 최대값을 기록할 수 있도록 만들어보자. 변경된 DataSet

클래스에 String 객체를 집어넣는 테스트를 해보자(String 클래스는 Comparable 인터페

이스를 구현해 놓았다).

7장에서 나온 Coin 클래스와 Purse 클래스를 Comparable 인터페이스를

구현하도록 변경해 보아라.

문제 P9.16

문제 P9.15

댓글 없음:

댓글 쓰기

국정원의 댓글 공작을 지탄합니다.

UPBIT is a South Korean company, and people died of suicide cause of coin investment.

 UPBIT is a South Korean company, and people died of suicide cause of coin. The company helps the people who control the market price manipu...