은폐(private) 메소드
클래스를 구현할 때 public 데이터는 위험하므로 모든 데이터 필드는 은폐형으로 만든다. 메
소드의 경우에는 어떠한가?, 대개 public 으로 되어 있지만 private 메소드는 자주 쓰인다.
이 메소드는 클래스의 다른 연산으로부터만 호출된다. 이유는 간단하다. 연산을 구현하기 위
해 코드를 여러 개의 함수로 쪼개기를 원할 수 있다. 이런 함수의 대부분은 공용으로 쓰기에는
유용하지 않다.(예를 들면, 이것들은 현재 구현에 너무 밀접하게 접근해 있고, 특별한 프로토
콜이나 호출 순서를 필요로 할 수도 있다.) 이러한 메소드는 private 연산으로 구현되는 것
이 제일 좋다.
중복(Overloading)
자바의 Date 클래스와 우리의 Day 클래스는 하나 이상의 생성자를 가졌다는 사실을 상기해
보자. 우리는 다음을 사용할 수 있다.
Date today = new Date();
또는
Date preMillenium = new Date(1999, 12, 31);
이런 기능을 중복(overloading)이라 한다. 중복은 다수의 메소드가 동일한 이름(이 경우
Date 생성자 메소드)가지고 있으나 다른 인자를 가질 경우 발생한다. 자바 인터프리터는 어
느 메소드를 호출할지 결정해야한다.(이것을 보통 중복 해결(overloading resolution)
이라고 한다.)
———————————————————————-
노트 : 자바는 생성자 메소드를 포함한 어떤 메소드라도 중복을 허락한다.
———————————————————————-
모든 생성자에 대하여 필드가 동일한 상수값으로 설정되는 경우, 이런 편리한 구문을 사용하
도록 추천한다.
다음과 같은 변형된 Customer class 를 고려해 보자.
class Customer
{ public Customer (String n)
{ name = n;
}
. . .
private String name;
private int accountNumber = Account.getNewNumber ( );
}
———————————————————————-
주의: 이것은 단지 생성자가 없는 클래스의 경우에만 적용된다. 클래스가 생성자를 가지고
있으면, 다음의 호출을 통하여 인스턴스를 생성하고자 할 때 디폴트 생성자를 제공하기를 강
요한다.
new ClassName();
예를 들면, Customer 클래스가 매개변수를 갖지 않는 생성자를 정의하지 않으면, 다음의 호
출은 틀린 것이 된다.
c = new Customer(); // ERROR—no default constructor
여러분이 new Customer(“James Bond”)을 호출하면, Customer(String) 생성자는
Customer(String, int) 생성자를 호출한다.
이것은 생성자 사이에 공통된 코드를 공유할 수 있는 유용한 방법이다.
요약하면, 자바에서의 생성자는 약간 복잡하다. 생성자가 호출되기 전에 모든 인스턴스 필드
는 클래스에서 지정한 값이나 디폴트 값(숫자는 0, 객체는 null)으로 초기화된다. 생성자의
첫번째 줄에서 다른 생성자를 호출할 수 있다.
여러분이 new Customer(“James Bond”)을 호출하면, Customer(String) 생성자는
Customer(String, int) 생성자를 호출한다.
이것은 생성자 사이에 공통된 코드를 공유할 수 있는 유용한 방법이다.
요약하면, 자바에서의 생성자는 약간 복잡하다. 생성자가 호출되기 전에 모든 인스턴스 필드
는 클래스에서 지정한 값이나 디폴트 값(숫자는 0, 객체는 null)으로 초기화된다. 생성자의
첫번째 줄에서 다른 생성자를 호출할 수 있다.
자바는 어떤 클래스에라도 finalize() 메소드를 추가할 수 있다. finalize() 메소드는
쓰레기 수집기가 객체를 청소하기 전에, 호출될 것이다. 실제로는 자원을 재생하는 것을
finalize() 메소드에 의존하지 말아라. 이 메소드가 언제 호출될지 알 수 없다.
노트: 정적 메소드인 System.runFinalizersOnExit (true)를 호출하는 것은 자바 종료
전에 finalizer 메소드들을 호출하는 확실한 방법이다.
———————————————————————-
자원을 사용하자마자 닫고자 한다면, 손수 이것을 다루어야 할 필요가 있다. dispose 메소
드를 사용하여 말끔하게 처리하여라. 클래스에 dispose 메소드가 있다면, 이것을 호출하여
재생하는 것이 중요한 작업일 것이다. 특히, 클래스가 dispose 메소드를 가진 인스턴스 필드
를 가지고 있으면, 필드의 dispose 를 호출할 dispose 메소드를 제공하여라.
정적(Static) 메소드
이 장에서 다루는 마지막 메소드 수식어는 static 수식어이다. 여러분은 이미 3 장에서 클래
스 상수를 생성하기 위해 사용된 static 수식어를 보았다. 클래스는 정적 변수와 정적 메소
드를 둘 다 가질 수 있다. 정적 필드는 모든 클래스의 인스턴스에서 변하지 않으므로, 클래스
에 속해 있다고 생각할 수 있다. 정적 메소드도 이와 비슷하여, 클래스에 속하며 클래스의 인
스턴스에서는 동작되지 않는다. 이것은 클래스의 인스턴스를 생성하지 않고도 이용할 수 있음
을 의미한다. 예를 들면, Console 과 Math 클래스의 모든 메소드는 정적 메소드이다. 이렇
기 때문에 다음의 코드는 의미가 있는 것이다.
double x = Console.readDouble();
double x = Math.pow(3, 0.1);
또한 정적 데이터 필드를 다음과 같이 사용할 수 있다.
Math.PI
System.out
클래스로부터 정적 메소드를 사용하기 위한 일반적인 구문은 아래와 같다.
ClassName.staticMethod(parameters)
———————————————————————-
노트 : 정적 메소드는 클래스의 인스턴스와 함께 동작하지 않으므로, 단지 정적 필드만을 접
근할 수 있다. 특히, 메소드가 객체의 정적이 아닌 인스턴스 필드를 필요로 한다면, 이것은
정적 메소드가 될 수 없다.
———————————————————————-
또한 초기값을 제공하거나 정적 초기화 블록을 통해 초기화가 가능하다.
public static final DM_PER_INCH = 2.54;
두 번째 방법은 static 이라는 키워드를 이용해 초기화 코드를 블록과 태그 사이에 넣는 것이
다.
static double [] buffer = new double[BUFFER_SIZE];
static
{ int I;
for (I = 0; I<buffer.length; I++)
buffer[I] = java.lang.math.random();
}
정적 초기화는 클래스가 첫번째 실행될 때 일어난다. 우선 모든 정적 변수들이 선언순서에 따
라 초기화 된다. 다음으로 초기화 블록들이 그들이 있는 클래스의 선언의 순서에 따라 초기화
된다.
———————————————————————-
C++ 노트 : 자바에서의 정적 변수와 메소드는 C++에서의 정적 데이터 멤버와 멤버 함수와 동
일하다. C++에서처럼 “정적”이라는 용어는 무의미하다. C/C++에서 static 을 사용하는 원
래 목적은 지역 범위가 종료하고 나서도 그대로 남아있는 지역 변수를 가리키기 위함이었다.
이런 상황에서 보면, “정적”이라는 용어는 변수의 값이 여전히 남아있어서 블럭을 다시 들어
가도 사용할 수 있음을 나타낸다. C/C++에서의 static 에 대한 두 번째 의미는 다른 파일에
서 접근할 수 없는 파일 범위의 함수와 전역 변수를 나타낸다. 마지막으로, C++에서는 이 키
워드를 다른 목적으로 재사용하였다. 이것은 클래스의 특정 객체에 속하지 않고 클래스에 속
하는 변수나 함수를 나타내기 위함이다. 이것이 자바에서의 키워드와 같은 의미이다.
———————————————————————-
패키지(Packages)
자바에서는 패키지(package)를 이용하여 클래스의 집합을 그룹화할 수 있다. 패키지는 다른
사람들이 제공하는 코드 라이브러리로부터 여러분의 작업을 구별해서 구성하는데 편리하다.
예를 들면, 우리는 corejava 라는 패키지 안에 유용한 클래스를 제공하였다. java.lang,
java.util, java.net 등을 포함하는 많은 패키지가 표준화된 자바 라이브러리로 제공된다.
표준화된 자바 라이브러리는 계층화되어 있는 패키지의 전형적인 예이다. 하드 디스크에 중첩
된 하위 디렉토리를 가지는 것과 마찬가지로, 중첩 수준을 이용하여 패키지를 구성할 수 있다.
모든 표준화된 자바 패키지는 java 패키지 계층 안에 존재한다.
패키지를 중첩시키는 이유의 하나는 패키지 이름의 유일성을 보장하기 위함이다. 만일 우리가
더 많은 유틸리티 클래스를 제공 받았다면 우리는 그 패키지를 corejava 라는 패키지 안에
둘 것이다. 원하는 만큼의 수준에 따라 중첩은 가능하다. 실제로는 유일한 패키지 이름을 완
전히 보장하기 위해서 Sun 에서는 패키지 접두사로서 여러분 회사의 인터넷 도메인 이름(아마
도 유일할 것임)을 반대 순서로 쓸 것을 추천하고 있다.
패키지의 이용
패키지 안의 클래스를 이용하는 방식에는 두 가지가 있다. 첫번째는 패키지의 완전한 이름을
써 주는 방법이다. 예를 들면,
int i = corejava.Console.readInteger();
java.util.GregorianCalendar today = new java.util.GregorianCalendar
();
위의 방법은 매우 귀찮다. 더 간단하고 일반적인 방법은 import 키워드를 사용하는 것이다.
클래스의 이름을 전부 다 쓰지 않고도 패키지의 클래스를 참조할 수 있다. 여러분은 특정 클래
스나 전체 패키지를 수입할(import) 수 있다. import 문장은 사용할 클래스의 소스 코드
앞부분에 써 주어야 한다. 예를 들면,
import corejava.*; // imports all the classes in the
// corejava package
import java.util.*;
그러면 우리는 다음과 같이 사용 가능하다.
int i = Console.readInteger();
GregorianCalendar today = new GregorianCalendar();
또한 패키지 안의 특정 클래스를 수입할 수 있다. 이 경우, 다음과 같이 import 문장을 써야
한다.
import corejava.Console; // imports only the Console class
패키지 안의 모든 클래스를 수입하는 편이 더 간단하며 정상적인 방식이다. 이것은 컴파일 시
간이나 코드 크기에 부정적인 영향을 주지 않으므로, 이렇게 하는 것이 일반적이다.
하지만, 두개의 패키지가 같은 이름의 클래스를 가지고 있으면 둘 다 수입할 수 없다.
마지막으로 언급할 점은, *은 단지 하나의 패키지를 수입하기 위해서만 사용할 수 있다는 것
이다. java 접두사를 갖는 모든 패키지를 수입하기 위해 import java.*을 사용할 수 없다.
마지막으로 컴파일러는 항상 java.lang 패키지를 찾는다. 여러분은 이것을 지정하거나 수입
할 필요가 없다.
패키지를 만들 때, 여러분이 직접 오브젝트 파일을 적당한 하위 디렉토리에 위치시켜야 한다.
예를 들면, 다음의 라인으로 시작하는 파일을 컴파일하면,
package acme.util
여러분은 컴파일된 클래스 파일을 acmeutil 하위 디렉토리에 위치시켜야 한다. 컴파일러가
자동으로 이렇게 해 주지는 않는다.
패키지 범위
우리는 이미 접근 수식어 public 와 private 에 대하여 살펴 보았다. public 이라고 선언
된 특성은 아무 클래스에서나 사용될 수 있다. private 으로 선언된 특성은 이것을 정의한
클래스에서만 이용될 수 있다. public 이나 private 로 지정하지 않으면, 그 특성(다시 말
하면, 클래스, 메소드, 또는 변수)은 같은 패키지 안의 모든 메소드에서 이용가능 하다.
———————————————————————-
노트 : 모든 소스 파일은 기껏해야 파일 이름과 같은 하나의 공용 클래스를 포함할 수 있다.
———————————————————————-
클래스 설계 힌트
너무 어렵거나 지루하지 않게, 여러분의 클래스를 OOP 에 적합하도록 만드는 힌트를 제시하면
서 이 장을 마치고자 한다.
1. 데이터를 항상 은폐형으로 유지하라.
캡슐화를 깨뜨리지 말아야 한다는 것이 가장 먼저 지켜야 할 규칙이다. 때로는 접근자나 변경
자 메소드를 작성해야 할 필요가 있지만, 인스턴스 필드는 은폐형으로 유지하여야 한다. 일반
적으로, 데이터를 표현하는 방식은 바뀔 수 있지만, 데이터를 이용하는 방식은 훨씬 덜 자주
바뀐다. 데이터를 은폐형으로 유지하면 데이터 표현의 변화는 이 클래스의 사용자에게 영향을
끼치지 않으며, 버그를 찾기도 수월하다.
2. 항상 데이터를 초기화하여라.
자바는 지역 변수를 초기화하지 않지만, 객체의 인스턴스 변수는 초기화한다. 디폴트 값에 의
존하지 말고, 디폴트를 제공하거나 모든 생성자에서 디폴트를 설정함으로써 변수를 의도적으
로 초기화하여라.
3. 하나의 클래스에서 너무 많은 기본 타입을 사용하지 말아라.
서로 연관된 다수의 기본 타입을 다른 클래스로 대치하라는 것이 요점이다. 이것은 여러분의
클래스를 이해하기 쉽고 변경하기 쉽게 유지한다. 예를 들면, Customer 클래스의 다음 인스
턴스 필드를
private String street;
private String city;
private String state;
private int zip;
Address 라는 새로운 클래스로 대치하여라. 이렇게 하면, 주소의 변화에 쉽게 대처할 수 있
다.
4. 모든 필드가 각각의 필드 접근자와 변경자를 필요로 하지는 않는다.
어느 사람의 급여를 접근하고 변경할 필요가 있을 수 있다. 하지만, 객체가 생성된 후, 고용
한 날짜를 바꿀 필요는 없다. 그리고 아주 종종, 객체는 카드판의 카드 배열처럼 다른 사람들
이 접근하거나 설정하기를 원하지 않는 인스턴스 변수를 가질 수 있다.
5. 클래스를 정의할 때 표준화된 형태를 사용하여라.
우리는 항상 다음의 순서로 클래스의 내용을 코딩한다.
공용(public) 특성
패키지 범위 특성
은폐(Private) 특성
각 절에서 우리는 다음 순서로 설명한다.
상수
생성자
메소드
정적 메소드
인스턴스 변수
정적 변수
결국, 여러분의 클래스를 사용할 사람은 은폐형의 자세한 구현보다는 공용 인터페이스에 더
많은 관심을 가지고 있을 것이다. 그리고 데이터보다는 메소드에 더 관심이 있을 것이다.
6. 아주 많은 책임을 갖도록 클래스들을 나누어라.
물론, 이 힌트는 애매하다. 특히 “아주 많은”이라는 말은 제 3 자의 눈으로 보면 확실히 애매
하다. 하지만, 하나의 복잡한 클래스를 개념적으로 간단한 클래스로 나누는 확실한 방법이 있
다면 무조건 그렇게 하여라.(반면에, 너무 과하게는 하지 말아라. 하나의 메소드만을 갖는
10 개의 클래스가 있다면, 좀 무리한 것이다.)
다음은 좋지 않은 설계의 예이다. 우리의 카드 게임을 살펴보자. 카드 한 벌이 두개의 배열을
저장하고 있다면, Card 클래스가 없어도 된다. 배열의 하나는 무늬이고 다른 하나는 숫자이
다. 이렇게 하면 카드를 리턴하는데 어렵게 되므로, 카드 한 벌의 제일 위 카드의 특성들을
볼 수 있는 함수를 날조해서 만들어야 한다.
class CardDeck // bad design
{ public void CardDeck() { … }
public void shuffle() { … }
public int getTopValue() { … }
public int getTopSuit() { … }
public int topRank() { … }
public void draw() { … }
private int[] value;
private int[] suit;
private int cards;
}
보는 바와 같이 이렇게 구현할 수는 있지만, 좀 어색하다. 이 상황에서는 카드가 의미 있는
객체이므로, Card 클래스를 생성한 것이다.
7. 여러분의 클래스와 메소드가 이들의 책임에 맞는 이름을 가지도록 하라.
변수가 이들이 하는 일이 반영되는 이름을 갖도록 해야 하는 것과 같이 클래스도 마찬가지이
다.(표준화된 라이브러리는 Date 클래스가 시간을 표시한다든지 getDay 메소드가 날이 아니
라 평일을 리턴한다든지 하는 약간의 불명료한 예제를 확실히 포함하고 있다.)
클래스 이름은 명사(Order)이거나 형용사 또는 동명사(“ing” 단어, BillingAddress)
뒤의 명사(RushOrder)로 하는 것이 좋은 습관이다. 메소드는 접근자 메소드를 소문자
get(getDay), 변경자를 소문자 set(setSalary)으로 시작하는 습관이 좋다.