1장 자바 프로그래밍 기초(2)

2023. 12. 13. 23:31프로그래밍 공부/Data Structure

1.4 제어 흐름

자바에서의 제어 흐름은 다른 고급 언어들의 제어 흐름과 유사하다.

이 절에서는 자바에서의 제어 흐름의 기본 구조와 구문을 검토한다.

여기서는 메소드 반환, if문, switch문, 반복문 및

제한된 형태의 "점프"(breakcontinue 문)를 포함한다.

 

1.4.1 if문과 switch문

자바에서 조건부는 다른 언어에서 작동하는 방식과 유사하게 작동한다.

그들은 결정을 내리고 그 결정의 결과에 따라

하나 이상의 서로 다른 진술 블록을 실행하는 방법을 제공한다.

 

if문

간단한 if문의 구문은 다음과 같다:

if (boolean_exp)

    true_statement

else

    false_statement


여기서 boolean_exp는 부울 식이고 

true_statement와 false_statement는

각각 하나의 문장이거나 괄호 안에 포함된 블록문이다("{" 과 "}").

주의할 점은 자바에서 if문으로 검사되는 값은 반드시 부울 식이어야 한다는 것이다.

특히 이는 정수 식이 아니다.

그럼에도 불구하고, 다른 비슷한 언어들과 마찬가지로 

자바 if문의 else 부분(및 연관된 문장)은 선택 사항이다.

다음과 같이 여러 부울 테스트를 그룹화하는 방법도 있다:
if (first_boolean_exp)

    true_statement
else if (second_boolean_exp)

    second_true_statement
else
    false_statement


첫 번째 부울식이 거짓이면 두 번째 부울식이 테스트되는 등의 과정을 거친다.

if문은 else if부분의 임의의 개수를 가질 수 있다.
예를 들어, 다음은 올바른 if 문이다.

if (snowLevel < 2) {

    goToClass();

    comeHome();

}

else if (snowLevel < 5) {

    goSledding();

    haveSnowballFight();

}

else

    stayAtHome();


switch문

Java는 switch문을 사용하여 다중값 제어 흐름을 제공하므로

enum 타입에 특히 유용하다.

다음은 (1.1.3절의 Day enum 타입 변수 d를 기준으로) 지시적인 예이다.

switch (d) { 
    case MON:
        System.out.println("This is tough.");
        break
    case TUE:
        System.out.println("This is getting better.");
        break
    case WED:
        System.out.println("Half way there.");
        break
    case THU:
        System.out.println("I can see the light.");
        break
    case FRI:
        System.out.println("Now we are talking.");
        break;
    default:
       System.out.println("Day off ! "); 

       break;
}


switch문은 정수 또는 enum 식을 평가하고

제어 흐름이 이 식의 값으로 라벨이 지정된 코드 위치로 점프하도록 한다.

일치하는 레이블이 없으면 제어 흐름이 "default"로 레이블이 지정된 위치로 점프한다.

그러나 이는 switch 문에 의해 수행되는 유일한 명시적 점프이므로

각 경우의 코드가 break문으로 끝나지 않으면

제어 흐름이 다른 경우로 "넘어간다"

(switch문 뒤에 제어 흐름이 다음 줄로 점프하도록 한다).

 

1.4.2 반복문

프로그래밍 언어의 또 다른 중요한 제어 흐름 메커니즘은 반복문(loop)이다.

자바는 세 가지 종류의 반복문을 제공한다.

 

while문

자바에서 가장 간단한 종류의 반복문은 while문이다.

while문: 어떤 조건이 만족되는지를 테스트하고 이 조건이 true라고 평가될 때마다 반복문 본문을 수행한다.

반복문 본체가 실행되기 전에 이러한 조건 테스트를 수행하는 구문은 다음과 같다:
while (boolean_exp)

    loop_statement


각 반복을 시작할 때 반복문은 식 부울 exp를 테스트한 다음 

이 부울식을 true로 평가하는 경우에만 반복문의 본문 loop_statement를 실행한다.

반복문의 본문은 블록문이 될 수도 있다.
예를 들어, 당근 조각에 있는 모든 당근에 물을 주려 하는 유령을 생각해 보자.

그의 당근 조각이 처음에는 비어있을 수도 있기 때문에,

이 작업을 수행하기 위한 코드를 다음과 같이 작성한다:
public void waterCarrots () {
    Carrot current = garden.findNextCarrot (); 

    while (!waterCan.isEmpty ()) {
          water (current, waterCan);
          current = garden.findNextCarrot ();
     }
}


Java에서 "!"는 "not" 연산자임을 기억하자.

 

for문

또 다른 종류의 반복문은 for문이다.

가장 간단한 형태로,

for문: 정수 인덱스를 기반으로 반복되는 코드를 제공한다.

자바에서 우리는 그것과 훨씬 더 많은 것을 할 수 있다.

for문의 기능은 훨씬 더 유연하다.

특히 for loop의 사용은 초기화, 조건, 증분, 그리고 본문의 네 부분으로 나뉜다.


for문 정의

반복문에 대한 자바 구문은 다음과 같다:
for (initialization; condition; increment)
    loop_statement
각 섹션의 초기화, 조건 및 증분은 비워 둘 수 있다.

 

초기화 섹션에서는 for문의 범위에만 존재하는 인덱스 변수를 선언할 수 있다.

예를 들어, 만약 우리가 카운터 위를 인덱스하는 반복문을 원하는데, 

만약 우리가 for문 밖에서 카운터 변수를 필요로 하지 않는다면, 

다음과 같은 것을 선언하면 

그 범위가 오직 반복문 본문인 변수 "카운터"로 선언될 것이다.

for (int counter = 0; condition; increment)

    loop_statement

조건 부분에서 우리는 반복문의 반복 조건을 지정한다.

이것은 부울 식이어야 한다.

for문 본문은 잠재적 반복의 시작에서 평가될 때 조건이 참일 때마다 실행된다.

조건이 거짓으로 평가되자마자 반복문 본문은 실행되지 않고,

대신 프로그램은 for문은 다음 문장을 실행한다.


증가 부분에서, 우리는 반복문에 대한 증가문을 선언한다.

증가문은 임의의 법적인 문장이 될 수 있으므로 코딩에 상당한 유연성을 가질 수 있다.

따라서, for문의 구문은 다음과 같다:
initialization;
while (condition) { 

    loop_statement;

    increment;
}


다만 자바에서 while문은 빈 부울 조건을 가질 수 없는 반면 

for문은 빈 부울 조건을 가질 수 있다.

다음 예제는 자바에서 반복문을 간단히 보여준다:

public void eatApples (Apples apples) { 

    numApples = apples.getNumApples ();

    for (int x = 0; × < numApples; ×++) {
        eatApple (apples.getApple (×));
        spitOutCore ();
    }
}


위의 자바 예제에서 반복문 변수 x는 int x = 0으로 선언되었다.

각 반복 전에 반복문 조건 x < numApples를 테스트하고

이것이 참인 경우에만 반복문 본문을 실행한다.

마지막으로 각 반복이 끝날 때마다 반복문 조건을 다시 테스트하기 전에

반복문 변수 x를 증가시키기 위해 x++ 문을 사용한다.
덧붙여서, 5.0 이후 자바에는 각각의 반복문도 포함되어 있으며,

우리는 섹션 6.3.2에서 논의한다

 

do-while문

자바에는 for문과 표준 while문 외에 또 다른 종류의 반복문인 do-while문이 있다.

이전 반복문은 반복문 본문을 반복하기 전에 조건을 테스트하고

do-while문은 반복문 본문 이후의 조건을 테스트한다.

do-while문의 구문은 다음과 같다:
do
    loop_statement 

while ( boolean_exp)


다시 말하지만, 

반복문 본문인 loop_statement는 단일문일 수도 있고 블록문일 수도 있으며, 

조건부 boolean_exp는 부울 식이어야 한다.

두 while문에서는 조건이 평가될 때마다 참인 한 반복문 본문을 반복한다.


예를 들어, 사용자에게 입력을 요청한 다음 해당 입력으로 유용한 작업을 수행하고자 한다.

(Java 입력 및 출력에 대해서는 1.6절에서 더 자세히 설명한다.)

이 경우 반복문을 종료하기 위한 가능한 조건은 사용자가 빈 문자열을 입력하는 것이다.

그러나 이 경우에도 처리하고 싶을 수 있다
입력하고 사용자에게 그만뒀음을 알려준다.

다음 예는 이 경우를 보여준다:
public void getUserInput() { 

    String input;
    do {
        input = getInputString();
        handleInput(input);
    } while (input.length()>0);
}
위 예제의 종료 조건에 주목하자.

특히 조건이 참이 아닐

(다른 언어에서 사용되는 반복할 때까지 구문과 달리)

do-while문이 종료되는 자바의 규칙과 일치하도록 작성되었다.

 

1.4.3 명시적 제어-흐름 문

또한 Java는 프로그램의 제어 흐름을 명시적으로 변경할 수 있는 문을 제공한다.

 

메소드에서 반환

Java 메소드가 반환 형식의 void로 선언된 경우 

메소드에서 코드의 마지막 줄에 도달하거나 인수 없이 return문을 만나면 제어 흐름이 반환된다.

그러나 메소드가 반환 형식으로 선언된 경우

메소드는 함수이며 함수의 값을 return문으로 반환하여 종료해야 한다.

다음(올바른) 예제에서는 함수에서 반환하는 예를 보여 준다:

   // Check for a specific birthday
public boolean checkBDay (int date) { 

  if (date == Birthdays.MIKES_BDAY) {
    return true;
  }
  return false;
}


따라서 return문은 함수에서 마지막으로 실행되는 문이어야 한다.

나머지 코드에는 절대 도달할 수 없기 때문이다.
메소드에서 실행되는 마지막 줄의 코드와

메소드 자체에서 실행되는 코드의 마지막 줄의 문 사이에는 상당한 차이가 있다.

위 예제에서 줄은 return true.

분명히 함수에 쓰여진 코드의 마지막 줄은 아니지만

(날짜와 관련된 조건이 true인 경우) 실행되는 마지막 줄일 수 있다.

이러한 문은 메소드에서 제어의 흐름을 명시적으로 차단한다.

반복문 및 switch문과 함께 사용되는 두 개의 다른 명시적인 제어 흐름 문이 있다.

 

break문

break문의 일반적인 사용은 다음과 같은 간단한 구문을 갖는다:

break;
이것은 가장 내부에 있는 switch문, for문, while문 또는 do-while문의 본문을 "break"하기 위해 사용된다.

이것이 실행될 때, break문은 제어 흐름이

반복문 뒤의 다음 행으로 점프하게 하거나 break를 포함하는 본문으로 switch하게 한다.

 

브레이크 문은 또한 외부 중첩 반복문 또는 switch문에서 점프하기 위해 라벨이 지정된 형태로 사용될 수 있다.

이 경우 구문을 갖는다
break label;
여기서 label은 반복문 또는 switch문에 레이블을 지정하는 데 사용되는 Java 식별자이다.

이러한 라벨은 반복문 선언의 시작 부분에만 나타날 수 있다.

Java에는 다른 종류의 "go to" 문이 없다.

 

다음과 같은 간단한 예제에서 break문이 있는 라벨의 사용을 설명한다:
public static boolean hasZeroEntry (int[][] a) { 

  boolean foundFlag = false;
zeroSearch:
  for (int i=0; i<a.length; i++) {
    for (int j=0; j<a[i].length; j++) {
      if (a[i][j] == 0) {

        foundFlag = true;

        break zeroSearch;
      }

    }
  }
  return foundFlag; 

}
위의 예에서는 3.1절에서 다루는 배열도 사용한다.

 

continue문

Java 프로그램에서 제어 흐름을 명시적으로 변경하기 위한

다른 문은 continue 문으로 다음 구문을 가지고 있다:
continue label;
여기서 label은 반복문에 라벨을 지정하는 데 사용되는 선택적인 Java 식별자이다.

위에서 언급한 것처럼 자바에는 명시적인 "go to" 문이 없다.

마찬가지로 continue 문도 반복문 내부에서만 사용할 수 있다.

continue 문은 현재 반복에서 반복문 본문의 나머지 단계를 건너뛰게 한다

(그러나 조건이 만족되면 반복문를 계속한다).

 

1.5  배열

일반적인 프로그래밍 작업은 관련된 객체의 번호가 매겨진 그룹을 계속 추적하는 것이다.

예를 들어, 우리는 게임의 상위 10개 점수를 추적하기 위해 비디오 게임을 원할 수도 있다.

이 작업을 위해 10개의 다른 변수를 사용하는 것보다,

우리는 그룹에 하나의 이름을 사용하고

그 그룹의 높은 점수를 나타내는 인덱스 번호를 사용하는 것을 선호한다.

마찬가지로, 우리는 의료 정보 시스템이

현재 특정 병원의 병상에 배정된 환자를 추적하기를 원할 수도 있다.

다시 말하지만, 우리는 병원에 200개의 병상이 있다고 해서

프로그램에 200개의 변수를 도입할 필요가 없다.


그런 경우에 우리는 배열을 사용함으로써 프로그래밍 노력을 절약할 수 있다.

배열의 각 변수, 즉 셀에는 해당 셀에 저장된 값을 고유하게 나타내는 인덱스가 있다.
배열 a의 셀에는 0, 1, 2 등으로 번호가 매겨진다.

우리는 그림 1.6에서 비디오 게임의 고득점 배열을 보여준다.
그림 1.6: 비디오 게임의 10개(int) 고득점 배열 예시.

High scores 940 880 830 790 750 660 650 590 510 440
indices 0 1 2 3 4 5 6 7 8 9

 

그런 체계는 우리가 몇 가지 흥미로운 계산을 할 수 있도록 해주기 때문에 꽤 유용하다.

예를 들어, 다음 방법은 정수 배열의 모든 숫자를 합한다:
  /** 정수 배열의 모든 숫자를 더한다.*/
public static int sum(int[] a) {
  int total = 0;
  for (int i=0; i < a.length; i==) // note the use of the length variable
    total += a[i];

  return total; 

}


이 예제는 배열이 저장하는 셀의 수,

즉 셀의 길이를 찾을 수 있게 해주는 자바의 좋은 기능을 이용한 것이다.

자바에서 배열 a는 특별한 종류의 객체이며,

a의 길이는 인스턴스 변수인 길이에 저장된다.

즉, 우리는 자바에서 배열의 길이를 추측할 필요가 없으므로,

배열의 길이는 다음과 같이 접근할 수 있다:
array_name.length
array_name: 배열의 이름아다.

따라서 배열 a의 셀들은 0, 1, 2 등으로 번호가 매겨진다.

배열 a의 길이: a.length - 1. 

 

배열 요소 및 용량

배열의 요소(element): 배열에 저장된 각각의 객체

0번 요소: a[0], 1번 요소: a[1], 2번 요소: a[2]

배열의 길이가 저장할 수 있는 최대 개수를 결정하기 때문에 배열의 길이가 결정된다

배열의 용량(capacity) = 배열의 길이

다음 코드 조각에서 배열의 또 다른 간단한 사용을 보여준다.

이 코드 조각은 특정 숫자가 배열에 나타나는 횟수를 세는 것이다:
  /** 정수가 배열에 나타나는 횟수를 카운트한다*/
public static int findCount(int[] a, int k) { 

  int count = 0;
  for (int e: a) { // note the use of the "foreach" loop
    if (e == k) // check if the current element equals k

      count++;
  }
  return count;

}

 

범위를 벗어남 오류

0에서 a.length-1까지의 범위를 벗어난 숫자를 사용하여

배열에 인덱스를 입력하려고 시도하는 것은 위험한 실수이다.

이러한 참조는 경계를 벗어났다(out of bound)고 한다.

자바가 아닌 다른 언어로 작성된 컴퓨터 시스템의 보안을 손상시키기 위해

버퍼 오버플로 공격(buffer overflow attack)이라는 방법을 사용하여

해커들은 경계를 벗어난 참조를 여러 번 이용했다.

안전 기능으로 자바에서 배열 인덱스가 경계를 벗어난 적이 있는지 항상 확인한다.

배열 인덱스가 경계를 벗어난 경우 런타임 자바 환경에서 오류 조건을 표시한다.

이 조건의 이름은 ArrayIndexOutOfBoundsException이다.

이 확인을 통해 자바는 다른 언어에서 처리해야 하는

여러 가지 보안 문제(버퍼 오버플로 공격 포함)를 피할 수 있다.


0과 a.length 사이의 정수 값을 사용하여 항상 배열 a로 인덱싱함으로써 경계를 벗어난 오류를 피할 수 있다.

이 작업을 수행하는 한 가지 방법은 자바에서 부울 연산의 조기 종결 기능을 주의 깊게 사용하는 것이다.

예를 들어, 다음과 같은 문장은 경계를 벗어난 인덱스 오류를 생성하지 않다:
if ((i > = 0) && (i < a.length) & (a[i] > 2)

  × = a[i];
비교를 위해 "a[i] > 0.5"는 처음 두 비교가 성공한 경우에만 수행된다.

 

1.5.1 배열 선언

배열을 선언하고 초기화하는 한 가지 방법은 다음과 같다:
element_type[] array_name = {init_val_0,init_val_1,...,init_val_N-1};

element_type: 임의의 자바 기본 형식 또는 클래스 이름 

array_name: 임의의 값 자바 식별자

 

초기 값은 배열과 동일한 형식이어야 한다.

예를 들어 첫 번째 10개의 소수를 포함하도록 초기화된 배열의 다음 선언을 생각해 보자:
int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
배열 변수를 초기화하지 않고도

배열을 만들고 모든 초기값을 정의할 수 있을 뿐만 아니라

배열 변수를 초기화할 수 있다.

이 선언의 형식은 다음과 같다:
element_type[] array_name;

 

이런 식으로 만들어진 배열은 배열 타입이 숫자 타입일 경우 모든 0을 사용하여 초기화된다.

객체의 배열은 모든 null 참조로 초기화된다.

이런 식으로 배열을 선언하면

나중에 다음 구문을 사용하여 배열을 위한 셀 모음을 만들 수 있다:
new element_type[length]

 

여기서 길이는 생성된 배열의 길이를 나타내는 양의 정수이다.

일반적으로 이 식을 할당 연산자의 왼쪽에 배열 이름이 있는 할당문에 나타낸다.

예를 들어, 다음 문장은 a라는 이름의 배열 변수를 정의하고 

나중에 각각 double 타입의 10개 셀로 구성된 배열을 할당한 다음 초기화한다:
double[] a;
//... various steps ...
a = new double[10];
for (int k=0; k < a.length; k++) {
    a[k] = 1.0; 

}

이 새 배열의 셀 "a"는 정수 집합 {0,1,2,...,9}

(자바의 배열는 항상 0에서 인덱싱을 시작함을 상기)를 사용하여 인덱싱되며, 

자바의 모든 배열와 마찬가지로 이 배열의 모든 셀은 동일한 double 타입의 셀이다.

 

1.5.2 배열은 객체

자바의 배열 = 특별한 종류의 객체

사실 이것이 우리가 new 연산자를 사용하여 배열의 새로운 인스턴스를 만들 수 있는 이유이다.

배열은 자바의 일반적인 객체들처럼 사용할 수 있지만,

우리는 그 구성원들을 가리키는

특별한 구문(대괄호, "[" 및 "]")을 사용)을 가지고 있다.

자바의 배열은 일반적인 객체가 할 수 있는 모든 것을 할 수 있다.

그러나 배열은 객체이기 때문에

자바에서 배열의 이름은 실제로 배열이 저장된 메모리 내의 위치를 가리키는 말이다.

따라서 배열의 길이를 "a.length"와 같이 도트 연산자와 인스턴스 변수인 길이를 사용하는 것은 그리 특별한 일이 없다.

이 경우 a라는 이름은 기본 배열 객체를 가리키는 참조나 포인터에 불과하다.

자바의 배열이 객체라는 사실은 할당문에서 배열 이름을 사용하는 데 중요한 의미를 갖는다.

우리가 b = a 같은 것을 쓸 때

자바 프로그램에서 우리는 정말로 b와 a가 모두 같은 배열을 가리킨다는 것을 의미한다.

그래서 만약 우리가 b[3] = 5 같은 것을 쓴다면, 우리는 숫자 a도 [3]을 5로 설정할 것이다.

우리는 그림 1.7에 이 중요한 점을 설명한다.

 

그림 1.7: 배열 객체들의 할당 그림이다.

우리는 이전에 "b = a;"로 설정한 후에 "b[3] = 5;"로 설정한 결과를 보여준다.

a 940 880 830 790 750 660 650 590 510 440
b 0 1 2 3 4 5 6 7 8 9

 

 b[3] = 5 할당으로부터 변화

a 940 880 830 5 750 660 650 590 510 440
b 0 1 2 3 4 5 6 7 8 9

 

 

배열 복사

대신에 배열의 정확한 복사본 a를 만들고 배열 변수 b에 그 배열을 할당하려고 했는데,

이것은 a의 모든 셀을 새 배열로 복사하고

그 새 배열을 가리키도록 b를 할당하는 b = a.clone ()를 작성해야 한다.

사실 복제 방법은 모든 자바 객체의 정확한 복사본을 만드는 내장된 방법이다.

이 경우에 만약 우리가 b[3] = 5를 작성한다면,

새로운 (복사된) 배열은 인덱스 3에 있는 셀을 가지고 5 값을 할당되지만,

a[3]는 변하지 않는다. 우리는 이 점을 그림 1.8에 이 점을 설명한다.

 

그림 1.8: 배열 객체를 복제하는 예를 보여준다. 

이전에 "b = a.clone ();"로 설정한 후 

"b[3] = 5;"로 설정한 결과를 보여준다.

a  940 880 830 790 750 660 650 590 510 440
  0 1 2 3 4 5 6 7 8 9

 

b  940 880 830 790 750 660 650 590 510 440
  0 1 2 3 4 5 6 7 8 9

 

 

  b[3] = 5 할당으로부터 변화

a  940 880 830 790 750 660 650 590 510 440
  0 1 2 3 4 5 6 7 8 9

 

b  940 880 830 5 750 660 650 590 510 440
  0 1 2 3 4 5 6 7 8 9

 

 

우리는 배열의 셀들을 복제할 때 복사된다는 점을 강조해야 한다. 

만약 셀들이 int와 같은 기본 타입이라면, 그들의 값은 복사된다. 

그러나 셀들이 객체 참조라면, 그 참조들은 복사된다. 

이것은 이제 그러한 객체를 참조하는 두 가지 방법이 있다는 것을 의미한다. 

우리는 연습문제 R-1.1에서 이 사실의 결과를 탐구한다.

 

1.6 단순 입력 및 출력

자바에서는 프로그램 내에서 입력 및 출력을 수행하기 위한 풍부한 클래스와 방법을 제공한다.

자바에는 팝업 창과 풀다운 메뉴로 완성된 그래픽 사용자 인터페이스 설계를 수행하는 클래스와

텍스트 및 숫자를 표시하고 입력하는 방법이 있다.

자바에서는 그래픽 객체, 이미지, 사운드, 웹 페이지 및

마우스 이벤트(클릭, 마우스 오버, 드래그 등)를 처리하는 방법도 제공한다.

또한 이러한 입력 및 출력 방법의 대부분은 독립 실행형 프로그램이나 애플릿에서 사용할 수 있다.

 

안타깝게도 이 모든 메소드가 정교한 그래픽 사용자 인터페이스를 구성하는 데 

어떻게 작동하는지에 대한 자세한 내용은 이 책의 범위를 벗어난 것이다.

여전히 완성도를 위해 이 섹션에서는 Java에서 간단한 입력 및 출력을 수행할 수 있는 방법을 설명한다.

 

Java에서 간단한 입력 및 출력은 Java 콘솔 창 내에서 발생한다.

사용하는 Java 환경에 따라 이 창은 텍스트를 표시하고 입력하는 데 사용할 수 있는 특수 팝업 창이거나

운영 체제에 명령을 내리는 데 사용되는 창(이러한 창을 셸 창, DOS 창 또는 터미널 창이라고 함)이다.

 

단순 출력 메소드

System.out: Java에서 제공하는 "표준 출력" 장치에 출력을 수행하는 내장 정적 객체

대부분의 운영 체제 셸에서는 사용자가 표준 출력을 파일로 리디렉션하거나

다른 프로그램의 입력으로도 리디렉션할 수 있지만

기본 출력은 Java 콘솔 창에 있다.

System.out 객체는 java.io.PrintStream class의 인스턴스이다.

이 클래스는 버퍼 출력 스트림에 대한 메소드를 정의한다.

버퍼(buffer): 일시적인 위치에 문자를 넣고 콘솔 창이 문자를 인쇄할 준비가 되면 비워지는 것

 

즉, java.io .PrintStream 클래스는 다음과 같은 단순 출력을 수행하는 메소드를 제공한다

(여기서는 가능한 기본 유형을 참조하는 데 base_type을 사용한다):

 

print(Object o): 객체 o를 toString 메소드를 사용하여 인쇄한다.

print(String s): 문자열 s를 인쇄한다.

print(base_type b): 기본 타입 값 b를 인쇄한다.

println(String s): 문자열 s를 인쇄한 다음 새 줄 문자를 인쇄한다.

 

출력 예시

예를 들어 다음 코드 조각을 고려하자.

System.out.print("Java values: ");
System.out.print(3.1415);
System.out.print(',');
System.out.print(15);
System.out.println(" (double,char,int) .");

이 조각을 실행하면 Java 콘솔 창에 다음과 같은 값이 출력된다.

Java values: 3.1415,15 (double,char,int)

 

java.util.Scanner 클래스를 사용한 간단한 입력

System.in: 자바 콘솔 창에서 입력을 수행하는 특별한 객체

기술적으로 입력은 실제로 "표준 입력" 장치에서 오는 것이며,

기본적으로 컴퓨터 키보드가 자바 콘솔에서 해당 문자를 반향하는 것이다.

System.in 객체: 표준 입력 장치와 연결된 객체이다.

이 객체를 사용하여 입력을 읽는 간단한 방법은

new Scanner(System.in )

라는 표현을 사용하여 Scanner 객체를 만드는 것이다.

 

Scanner 클래스에는 주어진 입력 스트림에서 읽는 여러 가지 편리한 포함 메소드가 있다.

예를 들어 다음 프로그램에서는 Scanner 객체를 사용하여 입력을 처리한다.

import java.io.*;
import java.util.Scanner; 
public class InputExample {
  public static void main(String args[]) throws IOException {
    Scanner s = new Scanner(System.in);
    System.out.print("Enter your height in centimeters: ");
    float height = s.nextFloat();
    System.out.print("Enter your weight in kilograms: ");
    float weight = s.nextFloat();
    float bmi = weight/(height*height)*10000;
    System.out.println("Your body mass index is " + bmi + ".");
  } 
}

 

실행 시 이 프로그램은 Java 콘솔에서 다음을 생성할 수 있다.

Enter your height in centimeters:180
Enter your weight in kilograms: 80.5
Your body mass index is 24.84568.

 

java.util.Scanner 메소드

Scanner 클래스: 입력 스트림을 읽고 토큰으로 분할한다.

토큰(token): 구분자로 구분된 문자의 연속 문자열

구분자(delimiter): 특수 구분 문자

기본 구분 기호는 공백으로,

즉 토큰은 기본적으로 공백, 탭 및 새 줄의 문자열로 구분된다.

토큰을 문자열로 즉시 읽거나 토큰이 올바른 구문이면

Scanner 객체가 토큰을 기본 유형으로 변환할 수 있다.

구체적으로, Scanner 클래스에는 토큰을 처리하는 다음 방법이 포함되어 있다.

 

hasNext():

입력 스트림에 다른 토큰이 있는 경우에만 true를 반환한다.

 

next ():

입력 스트림의 다음 토큰 문자열을 반환하고

토큰이 더 이상 남아 있지 않으면 오류를 생성한다.

 

hasNextType():

입력 스트림에 다른 토큰이 있고

해당하는 기본 타입인 Type으로 해석될 수 있는 경우에만 true를 반환한다. 

여기서 Type은 Boolean, Byte, Double, Float, Int, Long 또는 Short이다.

 

nextType():

입력 스트림에서 Type에 해당하는 기본 타입으로 반환되는 다음 토큰을 반환한다. 

토큰이 더 이상 남아 있지 않거나 

다음 토큰을 Type에 해당하는 기본 유형으로 해석할 수 없는 경우 오류를 발생시킨다.

 

또한 Scanner 객체는 입력 행을 처리하면서 구분 기호를 무시하고 행 내에서 패턴을 찾을 수도 있다.

이러한 방식으로 입력을 처리하는 방법은 다음을 포함한다.

 

hasNextLine():

입력 스트림에 텍스트 행이 있는 경우에만 true를 반환한다.

 

NextLine():

입력을 현재 행 끝자리를 지나 앞으로 진행하고 건너뛴 입력을 반환한다.

 

findInLine(String s):

현재 행의 (정규 표현식) 패턴과 일치하는 문자열을 찾으려고 시도한다. 
패턴이 발견되면 반환되고 이 일치 후 스캐너는 첫 번째 문자로 진행된다. 

패턴이 발견되지 않으면 스캐너는 null을 반환하고 진행하지 않는다.

 

이러한 방법은 위의 방법과 함께 다음과 같이 사용할 수도 있다.

Scanner input = new Scanner(System.in); 
System.out.print("Please enter an integer: "); 
while (!input.hasNextInt()) {
   input. nextLine();
   System.out.print("That' s not an integer; please enter an integer: ");
}
int i = input.nextInt();

 

1.7 예제 프로그램

이 절에서는 위에서 정의한 많은 구성 요소를 보여주는 간단한 예제 자바 프로그램을 설명한다.

우리의 예제는 두 가지 클래스로 구성된다.

CreditCard 클래스 - 신용카드 객체를 정의한다.

Test 클래스 - CreditCard 클래스의 기능을 테스트한다.

CreditCard 클래스에 의해 정의된 신용카드 객체는 기존의 신용카드의 단순화된 버전이다.

이들은 번호를 식별하고, 소유자와 발급 은행에 대한 정보를 가지고 있으며,

현재 잔액과 신용 한도에 대한 정보를 가지고 있다.

그러나 이들은 이자나 연체료는 부과하지 않지만,

카드 잔액이 지출 한도를 초과할 수 있는 요금은 제한한다.

 

CreditCard 클래스

코드 조각 1.5에서 CreditCard 클래스를 보여준다.

CreditCard 클래스는 모두 클래스에 고유한 5개의 인스턴스 변수를 정의하고,

이러한 인스턴스 변수를 초기화하는 간단한 생성자를 제공한다.

 

또한 이러한 인스턴스 변수의 5개의 접근자 메소드를 정의한다.

접근자 메소드(accessor method): 현재 값에 대한 접근을 제공한다.

물론 인스턴스 변수를 공개(public)로 정의할 수도 있으며,

이를 통해 접근자 메소드가 의미없어질 수도 있다.

그러나 이 직접적인 접근 방식의 단점은 사용자가 직접 객체의 인스턴스 변수를 수정할 수 있다.

반면 이 때 업데이트 메소드를 많이 사용한다.

업데이트 메소드(update method) : 인스턴스 변수의 수정을 제한한다.

코드 조각 1.5에

chargeIt과 makePayment,

이러한 두 가지 업데이트 메소드가 포함되어 있다.

 

또한 액션 메소드를 포함하는 것이 편리한 경우가 많다.

액션 메소드(action method): 해당 객체의 동작에 대한 특정 동작을 정의한다.

예를 들어, 그러한 액션 메소드인 PrintCard 메소드를 static 메소드로 정의했으며,

이 역시 코드 조각 1.5에 포함되어 있다.

 

Test  클래스

Test 클래스에서 CreditCard 클래스를 테스트한다.

여기에 CreditCard 객체의 array, wallet 사용과

반복을 사용하여 요금과 결제를 수행하는 방법에 주목하자.

저희는 코드 조각 1.6에 Test 클래스에 대한 전체 코드를 보여준다.

단순화를 위해 Test 클래스는

화려한 그래픽 출력을 하지 않고 출력을 Java 콘솔로 전송한다.

이 출력은 코드 조각 1.7에 나와 있다.

static이 아닌 chargeIt 및 make-Payment 메소드와

static인 printCard 메소드의 차이에 주목하라.

 

코드 조각 1.5: CreditCard 클래스

public class CreditCard{
  // Instance variables:
  private String number;
  private String name;
  private String bank;
  private double balance;
  private int limit;
  // Constructor;
  CreditCard(String no, String nm, String bk, double bal, int lim) {
    number = no;
    name = nm;
    bank = bk;
    balance = bal;
    limit = lim;
  }
  // Accessor methods:
  public String getNumber() { return number; }
  public String getName() { return name; }
  public String getBank() { return bank; }
  public double getBalance() { return limit;}
  // Action methods:
  public boolean chargelt(double price){ // Make a charge
    if (price + balance > (double) limit)
      return false; // There is not enough money left to charge it
    balance += price;
    return true; // The charge goes through in this case
  }
  public void makePayment(double payment) { // Make a payment
    balance -= payment;
  }
  public static void printCard(CreditCard c) { // Print a card's information
    System.out.println("Number = " + c.getNumber());
    System.out.println("Name = " + c.getName());
    System.out.println("Bank = " + c.getBank());
    System.out.println("Balance = " + c.getBalance()); //Implicit cast
    System.out.println("Limit = " + c.getLimit()); //Implicit cast
  }
}

 

코드 조각 1.6: Test 클래스

public class Test {
  public static void main(String[] args) {
    CreditCard wallet[] = new CreditCard[10];
    wallet[0] = new CreditCard("5391 0375 9387 5309",
                        "John Bowman", "California Savings", 0.0, 2500);
    wallet[1] = new CreditCard("3485 0399 3395 1954",
                        "John Bowman", "California Federal", 0.0, 3500);
    wallet[2] = new CreditCard("6011 4902 3294 2994",
                        "John Bowman", "California Finance", 0.0, 5000);
    for (int i=1; i<=16; i++) {
      wallet[0].chargelt((double)i);
      wallet[1].chargelt(2.0);          //implicit cast
      wallet[2].chargelt((double)3*i);  //explicit cast
    }
    for (int i=0; i<3; i++) {
      CreditCard.printCard(wallet[i]);
      while(wallet[i].getBalance() > 100.0) {
        wallet[i].makePayment(100.0);
        System.out.println("New balance = " + wallet[i].getBalance());
      }
    }
  }
}

 

코드 조각 1.7: Test 클래스의 출력

Number = 5391 0375 9387 5309
Name = John Bowman
Bank = California Savings
Balance = 136.0
Limit = 2500
New balance = 36.0

Number = 3485 0399 3395 1954
Name = John Bowman
Bank = California Federal
Balance = 272.0
Limit = 3500
New balance = 72.0

Number = 6011 4902 3294 2994
Name = John Bowman
Bank = California Finance
Balance = 408.0
Limit = 5000
New balance = 308.0

New balance = 208.0

New balance = 108.0

New balance = 8.0

 

1.8 중첩 클래스 및 패키지

자바 언어는 클래스를 프로그램으로 구성하는 데 일반적이고 유용한 접근 방식을 취한다.

자바에서 정의된 모든 독립형 public 클래스는 별도의 파일로 제공되어야 한다.

파일 이름은 .java 확장자가 있는 클래스 이름이다.

따라서 public class SmartBoard는 SmartBoard.java 파일에 정의되어 있다.

이 절에서는 Java가 여러 클래스를 의미 있는 방법으로 구성할 수 있는 두 가지 방법을 설명한다.

 

중첩 클래스

자바는 클래스 정의를 다른 클래스의 정의 내부에, 즉 내부에 중첩(nested)할 수 있도록 한다.

이것은 유용한 구성 요소이며, 우리는 데이터 구조의 구현에서 이 책에서 여러 번 활용할 것이다.

이러한 중첩 클래스의 주된 용도는 다른 클래스와 강하게 연관되어 있는 클래스를 정의하는 것이다.

예를 들어 텍스트 편집기 클래스는 관련된 커서 클래스를 정의하려고 할 수 있다.

커서 클래스를 텍스트 편집기 클래스의 정의 내부에 중첩 클래스로 정의하면

관련성이 높은 이 두 클래스가 한 파일에 함께 저장된다.

또한 이들 각각이 다른 클래스의 public되지 않는 메소드에 액세스할 수도 있다.

중첩 클래스와 관련하여 한 가지 기술적인 점은

중첩 클래스는 static으로 선언되어야 한다는 것이다.

이 선언은 중첩 클래스가 외부 클래스의 인스턴스,

즉 특정 객체와 연관되지 않는다는 것을 의미한다.

 

패키지

공통 서브 디렉토리에 정의된 모든 클래스의 집합은 Java package가 될 수 있다.

패키지의 모든 파일은

package package_name

행으로 시작한다.

 

패키지를 포함하는 서브 디렉토리는 패키지와 동일한 이름으로 명명되어야 한다.

여러 클래스 정의를 포함하는 단일 파일에서 패키지를 정의할 수도 있지만,

컴파일되면 모든 클래스는 동일한 서브 디렉토리의 개별 파일로 컴파일된다.

 

Java에서는 클래스 이름 앞에 다른 패키지의 디렉토리 구조에 해당하는 점(즉, '.' 문자를 사용)을 붙여

다른 패키지에 정의된 클래스를 사용할 수 있다.

public boolean Temperature(TA.Measures.Thermometer thermometer,int temperature) {
    //...
}

 

 

함수 Temperature는 클래스 Thermometer를 매개 변수로 사용한다.

온도계는 TA 패키지에 Measures라는 서브 패키지로 정의되어 있다.

TA.Measurements.Thermometer의 점은 TA 패키지의 디렉토리 구조에 직접 대응한다.

현재 패키지 외부의 클래스를 참조하는 데 필요한 추가 입력은 모두 피곤할 수 있다.

Java에서는 import 키워드를 사용하여 외부 클래스 또는 전체 패키지를 현재 파일에 포함할 수 있다.

특정 패키지에서 개별 클래스를 가져오려면 파일의 맨 앞에 다음과 같이 입력한다.

import packageName.className;

 

예를 들어, Project package의 맨 앞에 

package Project;

import TA.Measurements.Thermometer;

import TA.Measurements.Scale;

을 입력하여

TA.Measurements.Thermometer 및 TA.Measurements.Scale.Java이라는

이름의 클래스를 가져올 것임을 나타낼 수 있다.

이제 Java 런타임 환경에서 이러한 클래스를 검색하여 

식별자를 우리 프로그램에서 사용하는 클래스, 메서드 및 인스턴스 변수와 일치시킨다.

 

다음 구문을 사용하여 전체 패키지를 가져올 수도 있다:

import <packageName>.*;

 

예를 들어,

package student;

import TA.Measurement.*;

public Boolean temperature(Thermometer thermometer, int temperatur) {

    //...

}

 

두 패키지에 같은 이름의 클래스가 있는 경우에는 클래스가 포함된 패키지를 반드시 구체적으로 참조해야 한다.

예를 들어, Gnomes 패키지와 Cooking 패키지 모두 Mushroom이라는 클래스가 있다고 가정한다.

만약 두 패키지에 대해 가져오기 설명을 제공한다면,

다음과 같이 어떤 클래스를 의미하는지 지정해야 한다.

Gnomes.Mushroom shroom = new Gnomes.Mushroom ("purple");

Cooking.Mushroom topping = new Cooking.Mushroom ();

패키지를 지정하지 않으면(즉, 이전 예제에서는 Mushroom이라는 변수만 사용한다),

컴파일러는 " 모호한 클래스" 오류를 발생시킨다.

자바 프로그램의 구조를 요약하면,

클래스 안에는 인스턴스 변수와 메소드가 있고 패키지 안에는 클래스가 있다.

 

1.9 자바 프로그램 쓰기

자바 프로그램을 작성하는 과정은 다음 세 가지 기본 단계를 포함한다.

  1. 디자인
  2. 코딩
  3. 테스트 및 디버깅

이 섹션에서 이러한 각 단계에 대해 간략히 설명한다.

 

1.9.1 디자인

디자인 단계는 아마도 프로그램을 작성하는 과정에서 가장 중요한 단계일 것이다.

프로그램의 작업을 클래스로 나누는 방법을 결정하는 설계 단계에서

이 클래스들이 상호 작용하는 방식,

각 클래스가 저장하는 데이터 및 각 작업을 수행하는 방법을 결정한다.

실제로 초기 자바 프로그래머가 직면하는 주요 문제 중 하나는

프로그램 작업을 수행할 클래스를 정의하는 것이다.

일반적인 처방을 구하기 어렵지만

클래스를 정의하는 방법을 결정할 때 적용할 수 있는 일반적인 경험 규칙이 있다:

 

책임: 작업을 서로 다른 책임을 가진 다른 액터(actor)로 나누자.

액터들은 액션 동사를 사용하여 작업을 설명한다.

액터들은 프로그램의 클래스를 구성할 것이다.

 

독립성: 각 클래스의 작업을 다른 클래스로부터 가능한 독립적으로 정의한다.

각 클래스가 프로그램의 일부 측면에 대해 자율성을 갖도록 클래스 간 책임을 세분화한다.

이 데이터에 접근해야 하는 작업을 관할하는 클래스에 데이터(예시 변수)를 제공한다.

 

행동: 클래스가 수행하는 각 동작의 결과를 상호 작용하는 다른 클래스가 잘 이해할 수 있도록

각 클래스에 대한 행동을 신중하고 정확하게 정의한다.

이러한 행동은 클래스가 수행하는 방법을 정의한다.

프로토콜(protocol): 클래스에 대한 행동 집합

이는 클래스에 대한 행동이 응집력 있는 단위로 함께 유지되기를 기대하기 때문이다.

 

클래스를 인스턴스 변수 및 방법과 함께 정의하는 것이 자바 프로그램의 설계를 결정한다.

좋은 프로그래머는 시간이 지남에 따라 이러한 작업을 수행하는 데 있어

이전에 본 패턴과 일치하는 패턴을 찾는 것을 경험으로 인해 자연스럽게 더 큰 기술을 개발할 수 있다.

 

1.9.2 의사 코드

의사 코드(Psedo-Code): 실제 코드를 작성하기 전에 알고리즘을 사람의 눈에만 맞게 설명하는 것

의사 코드는 컴퓨터 프로그램은 아니지만 일반적인 산문보다 더 구조적이다.

의사 코드는 자연어와 고급 프로그래밍 구조의 혼합물로,

데이터 구조나 알고리즘의 일반적인 구현 뒤에 숨겨진 주요 아이디어를 설명한다.

그러나 의사 코드 언어에 대한 정확한 정의는 자연어에 대한 의존도 때문에 실제로 없다.

동시에 명확성을 달성하기 위해 의사 코드는 자연어를 표준 프로그래밍 언어 구조와 혼합한다.

우리가 선택한 프로그래밍 언어 구조는 C, C++ 및 Java와 같은 현대 고급 언어와 일치하는 구성이다.

이러한 구조는 다음을 포함한다.

 

표현식: 숫자 및 부울식을 표현하기 위해 표준 수학 기호를 사용한다.

왼쪽 화살표 기호()를 할당 연산자(Java에서 = 연산자에 동등함)로 사용하고

부울식에서 등호(==)를 등호(Java에서 "==" 관계에 동등함)로 사용한다.

 

메소드 선언: 알고리즘 이름(param1, param2, ...)은 새로운 메소드의 "이름"과 그 매개 변수를 선언한다.

 

결정 구조: 만약(if) 조건이 된다면(then) 참-행위[다른(else) 거짓-행위]가 된다.

저희는 true-actions와 false-actions에 포함되어야 하는 액션을 나타내기 위해 들여쓰기를 사용한다.

 

while문: 조건이 액션을 수행하는(do) 동안(while)

액션에 포함되어야 하는 액션을 나타내기 위해 들여쓰기를 사용한다.

 

반복문: 조건이 될 때까지(until) 액션을 반복한다(repeat).

우리는 반복문 액션에 포함되어야 하는 액션을 나타내기 위해 들여쓰기를 사용한다.

 

for문: 변수-증분-정의의 경우(for) 작업을 수행한다(do).

우리는 반복문 액션 중에 어떤 액션을 포함해야 하는지 나타내기 위해 들여쓰기를 사용한다.

 

배열 인덱싱: A[i]는 배열 A의 i번째 셀을 나타낸다.

n-셀 배열 A의 셀은 A[0]에서 A[n - 1](자바와 일치함)로 인덱싱된다.

 

메소드 호출: object.method(args) (객체가 이해된 경우 객체는 선택 사항이다.)

 

메소드 반환: 값을 반환한다(return).

이 작업은 지정된 값을 이 메소드를 호출한 메소드로 반환한다.

 

코멘트: {코멘트는 여기에 있다. }.

코멘트를 괄호 안에 동봉한다.

 

의사 코드를 작성할 때 우리는 컴퓨터가 아니라 인간 독자를 위해 작성하고 있음을 염두에 두어야 한다.

따라서 우리는 낮은 수준의 구현 세부 정보가 아니라 높은 수준의 아이디어를 전달하기 위해 노력해야 한다.

동시에 중요한 단계를 얼버무려서는 안 된다.

많은 형태의 인간 커뮤니케이션처럼 올바른 균형을 찾는 것은 연습을 통해 다듬어지는 중요한 기술이다.

 

1.9.3 코딩

앞서 언급한 바와 같이 객체 지향 프로그램을 코드화하는 핵심 단계 중 하나는 

클래스에 대한 설명과 각각의 데이터 및 방법을 코드화하는 것이다.

이 기술 개발을 가속화하기 위해

이 텍스트 전반에 걸쳐 다양한 지점에서

객체 지향 프로그램을 설계하기 위한 다양한 설계 패턴에 대해 논의한다(섹션 2.1.3 참조).

설계 패턴(design pattern): 클래스를 정의하고 클래스 간의 상호 작용을 정의하기 위한 템플릿을 제공한다.

 

많은 프로그래머가 초기 코딩을 컴퓨터가 아닌 CRC 카드를 사용한다.

CRC(Component-responsibility-collaborator) 카드: 프로그램에 필요한 작업을 세분화하는 간단한 인덱스 카드

이 도구의 주요 아이디어는 각 카드가 최종적으로 프로그램의 클래스가 되는 컴포넌트를 나타내도록 하는 것이다.

인덱스 카드의 맨 위에 각 컴포넌트의 이름을 적는다.

카드의 왼쪽에는 이 컴포넌트에 대한 책임을 작성하기 시작한다.

오른쪽에는 이 컴포넌트,

즉 이 컴포넌트가 임무를 수행하기 위해 상호 작용해야 할 다른 컴포넌트의 협력자를 나열한다.

설계 프로세스는 액션/액터 사이클을 통해 반복되며,

먼저 액션(즉, 책임)을 식별한 다음

그 액션을 수행하는 데 가장 적합한 액터(즉, 컴포넌트)를 결정한다.

모든 액션을 액터에게 할당하면 설계가 완료된다.

 

그런데 인덱스 카드를 사용하여 코딩을 시작할 때

각 컴포넌트에는 작은 책임 집합과 협력자가 있을 것으로 가정하고 있다.

이 가정은 우연이 아니다.

 

CRC 카드의 대안은 프로그램의 구성을 표현하기 위해

UML(Unified Modeling Language) 다이어그램을 사용하고 알고리즘을 설명하기 위해 의사 코드를 사용하는 것이다.

UML 다이어그램: 객체 지향 소프트웨어 디자인을 표현하기 위한 표준 시각적 표기법이다.

UML 다이어그램을 구축하기 위해 여러 컴퓨터 지원 도구를 사용할 수 있다.

반면 의사 코드로 알고리즘을 설명하는 것은 이 책 전체에서 사용하는 기술이다.

 

프로그램의 클래스와 담당 프로그램을 결정한 후, 컴퓨터에서 실제 코딩을 시작할 준비가 되었다.

우리는 독립적인 텍스트 편집기(emacs, WordPad, vi 등) 또는 Eclipse 또는 Borland JBuilder와 같은

통합 개발 환경(IDE)에 내장된 편집기를 사용하여 프로그램의 클래스에 대한 실제 코드를 만든다.

 

클래스(또는 패키지)에 대한 코딩이 완료되면 컴파일러를 호출하여 이 파일을 작동 코드로 컴파일한다.

IDE를 사용하지 않는 경우에는 파일의 javac과 같은 프로그램을 호출하여 프로그램을 컴파일한다.

IDE를 사용하는 경우에는 적절한 컴파일 버튼을 클릭하여 프로그램을 컴파일한다.

다행히 프로그램에 구문 오류가 없으면 이 컴파일 과정에서 확장자가 ".class"인 파일이 생성된다.

 

프로그램에 구문 오류가 있는 경우

이러한 오류가 식별되므로 코드의 잘못된 행을 수정하려면 편집기로 다시 이동해야 한다.

모든 구문 오류를 제거하고 적절한 컴파일 코드를 만든 후에는 "java"(IDE outside)와 같은 명령을 호출하거나

(IDE 내에서) 적절한 "실행" 버튼을 클릭하여 프로그램을 실행할 수 있다.

이러한 방식으로 Java 프로그램을 실행하는 경우 런타임 환경은

특수 운영 체제 환경 변수에 따라 이름이 지정된 클래스와

이 클래스에서 참조되는 다른 클래스를 포함하는 디렉터리를 찾다.

이 변수의 이름은 "CLASSPATH"이며

검색할 디렉터리의 순서는 디렉터리 리스트로 제공되며,

이 리스트는 Unix/Linux의 콜론 또는 DOS/Windows의 세미콜론으로 구분된다.

 

DOS/Windows 운영 체제의 CLASSPATH 할당 예로는 다음이 있다.

SET CLASSPATH=  .  ;C:\Java;C:\Program Files\Java\

 

반면에 Unix/Linux 운영 체제의 CLASSPATH 할당 예로는

SETenv CLASSPATH

".:/usr/local/Java/lib:/usr/netscape/classes"

가 있다.

 

두 경우 모두 점(".")은 런타임 환경이 호출되는 현재 디렉터리를 나타낸다.

 

Javadoc

코멘트 블록의 유용한 사용과 문서의 자동 생성을 장려하기 위해

Java 프로그래밍 환경에는 javadoc이라는 문서 제작 프로그램이 제공된다.

이 프로그램은 태그라는 특정 키워드를 사용하여 코멘트를 작성한 Java 소스 파일 모음을 가져오며,

이러한 파일에 포함된 클래스, 메소드, 변수 및 상수를 설명하는 일련의 HTML 문서를 생성한다.

공간상의 이유로 이 책에 포함된 모든 예제 프로그램에 javadoc 스타일의 코멘트를 사용하지는 않았지만

코드 프래그먼트 1.8의 javadoc 예제와 이 책과 함께 제공되는 웹 사이트의 다른 예제를 포함한다.

 

각 javadoc 코멘트는 "/**"로 시작하여 "*/"로 끝나는 블록 코멘트이며,

이 두 줄 사이의 각 줄은 이들 사이의 한 줄로 시작할 수 있다.

코멘트 블록은 설명문으로 시작하고 빈 줄이 뒤따르는 것으로 가정하고

자바독 태그로 시작하는 특수 줄이 뒤따른다.

javadoc은 클래스 정의, 인스턴스 변수 선언 또는 메소드 정의 직전에 오는 코멘트 블록을

해당 클래스, 변수 또는 메소드에 대한 코멘트로 처리한다.

 

Code Fragment 1.8: javadoc 스타일의 코멘트를 사용한 클래스 정의 예제이다.

이 클래스에는 두 개의 인스턴스 변수, 하나의 생성자, 두 개의 접근자 메소드 포함된다.

/**
 * This class defines an immutable (x,y) point in the plane.
 *
 * @author Michael Goodrich
 */
public class XYPoint {
  private double x,y; //private instance variables for cordinates
  
  /**
   * Construct an (x,y) pint at a specified location
   *
   * @param xCoor The x-coordinate of the point
   * @param yCoor The y-coordinate of the point
   */
  public XYPoint(double xCoor, double yCoor) {
    x = xCoor;
    y = yCoor;
  }
  
  /**
   * Return x-coordinate value.
   *
   * @return x-coordinate
   */
  public double getX() { return x; }
  
  /**
   * Return y-coordinate value.
   *
   * @return y-coordinate
   */
  public double getY() { return y; }
}

 

주요 자바독 태그는 다음과 같다.

• @author text: 클래스에 대한 각 저자(한 줄당 하나씩)를 식별한다.

• @exception exception-name description: 이 메소드에서 신호를 보내는 오류 조건을 식별한다(섹션 2.3 참조).

• @param parameter-name description: 이 메소드에서 허용하는 매개 변수를 식별한다.

• @return description: 메소드의 반환 유형과 값 범위를 설명한다.

다른 태그도 있다.

관심 있는 독자는 추가 논의를 위해 자바독에 대한 온라인 설명서를 참조하자.

 

가독성 및 스타일

프로그램은 읽고 이해하기 쉽게 만들어야 한다.

따라서 훌륭한 프로그래머는 자신의 코딩 스타일을 염두에 두고

사람과 컴퓨터 모두를 위해 프로그램 설계의 중요한 측면을 전달하는 스타일을 개발해야 한다.

좋은 코딩 스타일에 대해 많은 내용이 작성되었으며 주요 원칙 중 일부는 다음과 같다.

 

• 식별자에 의미 있는 이름을 사용한다.

큰 소리로 읽을 수 있는 이름을 선택하고 각 식별자가 명명하는 작업, 책임 또는 데이터를 반영하는 이름을 선택한다.

대부분의 자바 원에서 전통은 변수 또는 메소드에 대한 식별자의 첫 번째 단어를 제외하고

식별자의 각 단어의 첫 글자를 대문자로 사용하는 것이다.

따라서 이 전통에서는 "Date", "Vector", "Device Manager"가 클래스를 식별하고

"isfull()", "InsertItem()", "studentName" 및 "student Height"가 각각 메소드와 변수를 식별한다.

 

• 리터럴 대신 명명된 상수 또는 열거형을 사용한다.

클래스 정의에 명명된 상수의 일련의 정의를 포함하면 가독성, 견고성 및 수정성이 향상된다.

그런 다음 이 클래스와 다른 항목에서 이 클래스에 대한 특별한 값을 참조하는 데 사용할 수 있다.

자바의 전통은 아래와 같이 이러한 상수를 완전히 활용하는 것이다.

public class Student {

  public static final int MINCREDITS = 12; // min. credits

  int MAXCREDITS = 24; // min. credits

  int MAXCREDITS = 24; / min. credits int for term

  public static final int Final int FREENM= 1; // 신입생 

  public static final int = 2; // 주니어

  public static final int JUNIOR = 3; // 주니어

  public static final int SENIOR = 4; // 시니어

  // 인스턴스 변수, 생성자 및 메소드 정의는 여기에 있다.

}

 

일반적으로 프로그래머는 각 블록문을 4칸씩 들여보낸다.

그러나 이 책에서는 코드가 책의 여백을 초과하지 않도록 일반적으로 2칸을 사용한다.

 

각 클래스를 다음 순서로 구성한다.

  1. 상수
  2. 인스턴스 변수
  3. 생성자
  4. 메소드

일부 Java 프로그래머가 인스턴스 변수 정의를 마지막으로 두는 것을 선호한다.

우리는 각 클래스를 순차적으로 읽고 각 방법이 작동하는 데이터를 이해할 수 있도록 더 일찍 배치한다.

 

• 프로그램에 의미를 부여하는 코멘트를 사용하고, 모호하거나 혼란스러운 구성개념을 설명한다.

인라인 코멘트: 빠른 설명을 위해 유용하며, 문장일 필요는 없다.

블록 코멘트: 방법의 목적과 복잡한 코드 섹션을 설명하는 데 유용하다.

 

1.9.4 테스팅 및 디버깅

디버깅: 프로그램의 실행을 추적하여 오류를 발견하는 과정

테스팅: 프로그램의 정확성을 실험적으로 확인하는 과정

테스팅 및 디버깅은 프로그램 개발 시 종종 가장 많은 시간이 소요되는 작업이다.

 

테스팅

신중한 테스트 계획을 테스트하는 것은 프로그램을 작성하는 데 있어 필수적인 부분이다.

가능한 모든 입력에 대해 프로그램의 정확성을 확인하는 것은 일반적으로 실행 불가능하지만,

우리는 대표적인 입력 집합에서 프로그램을 실행하는 것을 목표로 해야 한다.

최소한 우리는 프로그램의 모든 메소드를 한 번 이상 테스트해야 한다(메소드 커버리지).

이보다 더 좋은 점은 프로그램의 각 코드 문은 적어도 한 번은 실행되어야 한다는 것이다.

 

프로그램은 입력의 특별한 경우(special case)에 종종 실패한다.

이러한 경우는 신중하게 식별하고 테스트해야 한다.

예를 들어, 정수 배열을 정렬하는(즉, 순서대로 입력하는) 메소드를 테스트할 때

다음 입력을 고려해야 한다:

• 배열의 길이가 0이다

• 배열이 한 개의 요소이다

• 배열의 모든 요소가 동일하다

• 배열의 모든 요소가 이미 정렬되었다

• 배열이 역 정렬된다.

 

프로그램에 대한 특수한 입력 이외에도

프로그램이 사용하는 구조에 대한 특수한 조건도 고려해야 한다.

예를 들어, 우리가 배열을 사용하여 데이터를 저장하는 경우,

우리는 데이터를 저장하는 서브 배열의 시작 또는 끝에

삽입/제거와 같은 경계 사례가 적절하게 처리되는지 확인해야 한다.

 

수작업으로 테스트 스위트를 사용하는 것이 필수적이지만,

무작위로 생성된 많은 입력 집합에서 프로그램을 실행하는 것도 유리하다.

java.util 패키지의 Random 클래스는 난수를 생성하는 여러 가지 방법을 제공한다.

 

호출자-피호출자(caller-callee) 관계에 의해

유도된 프로그램의 클래스와 메소드 사이에는 계층 구조가 있다.

즉, 메소드 A는 A가 B를 호출하면 계층 구조에서 메소드 B 위에 있다.

하향식 및 상향식 테스트 전략의 두 가지 주요한 순서가 있으며,

메소드가 테스트되는 순서는 다르다.

 

상향식 테스트(Bottom-up): 하위 수준 메소드에서 상위 수준 메소드로 진행된다.

즉, 다른 메소드를 호출하지 않는 하위 수준 메소드를 먼저 테스트한 다음

하위 수준 메소드만 호출하는 메소드를 테스트한다.

이 전략은 메소드에서 발견된 오류가

메소드 내에 중첩된 하위 수준 메소드로 인해 발생하지 않도록 보장한다.

 

하향식 테스트(Top-down): 메소드 계층 구조의 위에서 아래로 진행된다.

일반적으로 스터빙과 함께 사용된다.

스터빙(stubbing): 하위 수준 메소드를 스터브로 대체하는 부트스크래핑 기법

스터브(stub): 원래의 메소드 출력을 시뮬레이션하는 메소드의 대체물

예를 들어 메소드 A가 83을 호출하는 경우 메소드 A가 83을 호출한다

방법 B를 사용하여 파일의 첫 줄을 얻는 방법 A를 테스트할 때

B를 고정 문자열을 반환하는 스터브로 바꿀 수 있다.

 

디버깅

가장 간단한 디버깅 기법은 프로그램 실행 중 변수 값을 추적하기 위해 인쇄문을 사용하는 것이다.

인쇄문: System.out.println(string) 메소드를 사용한다.

이 접근법의 문제점은 소프트웨어가 최종적으로 출시되기 전에

인쇄문을 제거하거나 주석을 달아야 한다는 것이다.

 

더 나은 접근법은 디버거 내에서 프로그램을 실행하는 것이다.

디버거(debugger): 프로그램의 실행을 제어하고 모니터링하기 위한 전문 환경

디버거에서 제공하는 기본 기능은 코드 내에 중단점(breakpoint)을 삽입하는 것이다.

디버거 내에서 프로그램이 실행되면 각 중단점에서 프로그램이 중지된다.

프로그램이 중지되는 동안 변수의 현재 값을 검사할 수 있다.

고정된 중단점 외에도 고급 디버거를 사용하면 조건부 중단점을 지정할 수 있다.

조건부 중단점(conditional breakpoint): 주어진 식을 만족하는 경우에만 트리거된다.

 

표준 자바 도구에는 jdb가 포함되어 있다.

jdb: 커맨드 라인 방향인 기본 디버거

자바 프로그래밍용 IDE들은 그래픽 사용자 인터페이스를 갖춘 고급 디버깅 환경을 제공한다.