본문 바로가기
Java Tutorials

#5 Lesson: Control Flow Statements

by xogns93 2024. 7. 18.

 

 

소스 파일 내부의 statements은 일반적으로 위에서 아래로, 순서대로 실행됩니다. 그러나 제어 흐름 statements은 결정 만들기, 반복, 분기를 사용하여 실행 흐름을 분할함으로써 프로그램이 조건부로 특정 코드 블록을 실행할 수 있도록 합니다. 이 섹션에서는 자바 프로그래밍 언어가 지원하는 결정-만들기 statement(if-then, if-then-else, switch), 반복 statement(for, while, do-while), 그리고 분기[branch] statement(break, continue, return)에 대해 설명합니다.

※ "if-then"이라는 용어는 if 조건문의 조건이 참(true)일 경우 실행될 코드 블록을 실행한다는 의미입니다. 자바에서는 이 구조를 중괄호 {}를 사용하여 구현합니다. 예를 들어, 자바에서는 다음과 같이 작성할 수 있습니다:

if (condition) {
    // condition이 참일 때 실행할 코드
    // ...
}

여기서 condition이 참일 경우, 중괄호 안의 코드가 실행됩니다.  then은 실제 코드에 나타나지 않지만, 설명할 때 if-then이라고 표현하여 조건이 참인 경우에 실행될 행동을 설명하는 데 사용합니다.

 

The if-then and if-then-else Statements

The if-then Statement

if-then statement은 모든 제어 흐름 statements 중에서 가장 기본적인 statement입니다. 이 statement은 특정 테스트가 참으로 평가될 때만 프로그램이 특정 코드 섹션을 실행하도록 지시합니다. 예를 들어, 자전거 클래스는 자전거가 이미 움직이고 있을 때만 속도를 줄일 수 있도록 브레이크를 작동시킬 수 있습니다. applyBrakes 메소드의 한 가지 구현 방법은 다음과 같습니다:

void applyBrakes() {
    // "if" 절: 자전거가 움직여야 함
    if (isMoving){ 
        // "then" 절: 현재 속도 감소
        int x = currentSpeed--;
        
        // 
        // currentSpeed = currentSpeed - 1;
    }
}


이 테스트가 거짓으로 평가되면 (즉, 자전거가 움직이지 않는다는 의미), 제어는 if-then statement의 끝으로 이동합니다.

또한, "then" 절에 statement가 하나만 포함되어 있는 경우, 여는 괄호와 닫는 괄호는 선택적입니다:

void applyBrakes() {
    // 위와 동일하지만 괄호 없음
    if (isMoving)
        currentSpeed--;
}


괄호를 생략할지 여부는 개인의 취향에 달려 있습니다. 괄호를 생략하면 코드가 더 취약해질 수 있습니다. 나중에 "then" 절에 두 번째 문장이 추가되면 흔히 발생하는 실수는 새로 필요한 괄호를 추가하는 것을 잊는 것입니다. 컴파일러는 이러한 종류의 오류를 잡아낼 수 없으며, 잘못된 결과를 얻게 됩니다.

 

The if-then-else Statement

if-then-else statement은 "if" 절이 false로 평가될 때 실행할 대체 경로를 제공합니다. 자전거가 움직이지 않을 때 브레이크가 적용되면 어떤 조치를 취하는 applyBrakes 메소드에서 if-then-else 문을 사용할 수 있습니다. 이 경우, 조치는 자전거가 이미 멈추었다는 오류 메시지를 출력하는 것입니다.

void applyBrakes() {
    if (isMoving) {
        currentSpeed--;
    } else {
        System.err.println("The bicycle has already stopped!");
    } 
}


다음 프로그램, IfElseDemo는 testscore의 값에 따라 등급을 부여합니다: 90% 이상이면 A, 80% 이상이면 B, 그리고 이하로 계속됩니다.

class IfElseDemo {
    public static void main(String[] args) {

        int testscore = 76;
        char grade;

        if (testscore >= 90) {
            grade = 'A';
        } else if (testscore >= 80) {
            grade = 'B';
        } else if (testscore >= 70) {
            grade = 'C';
        } else if (testscore >= 60) {
            grade = 'D';
        } else {
            grade = 'F';
        }
        System.out.println("Grade = " + grade);
    }
}


프로그램의 출력은 다음과 같습니다:

    Grade = C


여러분은 testscore 값이 복합 statements에서 하나 이상의 statement을 만족할 수 있다는 것을 알아차렸을 수 있습니다: 76 >= 70 그리고 76 >= 60. 그러나 조건이 만족되면, 해당 statement가 실행됩니다 (grade = 'C';) 그리고 나머지 조건들은 평가되지 않습니다.

 

The switch Statement

if-then과 if-then-else 문과 달리 switch 문은 여러 가능한 실행 경로를 가질 수 있습니다. switch는 byte, short, char, int 기본[primitive] 데이터 타입과 함께 작동합니다. 또한 열거 타입(Enum 타입에서 논의됨), String 클래스, 그리고 특정 기본 타입을 감싸[wrapping]는 몇 가지 특별한 클래스인 Character, Byte, Short, Integer(Number와 String에서 논의됨)와 함께 작동합니다.

다음 코드 예제인 SwitchDemo는 특정 월을 나타내는 값인 int 타입의 month 변수를 선언합니다. 코드는 switch 문을 사용하여 month의 값에 따라 월 이름을 표시합니다.

public class SwitchDemo {
    public static void main(String[] args) {
        int month = 8;
        String monthString;
        switch (month) {
            case 1:  monthString = "January"; break;
            case 2:  monthString = "February"; break;
            case 3:  monthString = "March"; break;
            case 4:  monthString = "April"; break;
            case 5:  monthString = "May"; break;
            case 6:  monthString = "June"; break;
            case 7:  monthString = "July"; break;
            case 8:  monthString = "August"; break;
            case 9:  monthString = "September"; break;
            case 10: monthString = "October"; break;
            case 11: monthString = "November"; break;
            case 12: monthString = "December"; break;
            default: monthString = "Invalid month"; break;
        }
        System.out.println(monthString);
    }
}


이 경우, 표준 출력으로 "August"가 출력됩니다.

switch statement의 본문은 switch 블록으로 알려져 있습니다. switch 블록의 statements은 하나 이상의 case 또는 default 레이블로 표시될 수 있습니다. switch statement은 expression을 평가한 다음 일치하는 case 레이블을 따르는 모든 statements을 실행합니다.

해당 월 이름을 if-then-else statement으로도 콘솔에 출력할 수 있습니다:

int month = 8;
if (month == 1) {
    System.out.println("January");
} else if (month == 2) {
    System.out.println("February");
}
// 그리고 이어서...


if-then-else statement과 switch statement을 사용하는 것은 가독성과 문장이 테스트하는 expression에 따라 다릅니다. if-then-else statement은 값 또는 조건의 범위를 기반으로 expression을 테스트할 수 있지만, switch statement은 단일 정수, Enum 값 또는 String 객체를 기반으로 표현식을 테스트합니다.

또 다른 관심사는 break 문입니다. 각 break statement은 포함된 switch statement을 종료합니다. 제어 흐름은 switch 블록을 따르는 첫 번째 statement으로 계속됩니다. break statement이 없으면 switch 블록의 statements가 통과됩니다: swicth 표현식과 일치하는 case 레이블 이후의 모든 statements이 연속적으로 실행됩니다, 후속 case 레이블의 표현과 관계없이 break statement을 만날 때까지입니다. SwitchDemoFallThrough 프로그램은 통과하는 switch 블록의 statement을 보여줍니다. 이 프로그램은 정수 월과 그 해의 이어지는 월을 표시합니다:

public class SwitchDemoFallThrough {
    public static void main(String[] args) {
        java.util.ArrayList<String> futureMonths = new java.util.ArrayList<String>();
        int month = 8;

        switch (month) {
            case 1:  futureMonths.add("January");
            case 2:  futureMonths.add("February");
            case 3:  futureMonths.add("March");
            case 4:  futureMonths.add("April");
            case 5:  futureMonths.add("May");
            case 6:  futureMonths.add("June");
            case 7:  futureMonths.add("July");
            case 8:  futureMonths.add("August");
            case 9:  futureMonths.add("September");
            case 10: futureMonths.add("October");
            case 11: futureMonths.add("November");
            case 12: futureMonths.add("December"); break;
            default: break;
        }

        if (futureMonths.isEmpty()) {
            System.out.println("Invalid month number");
        } else {
            for (String monthName : futureMonths) {
               System.out.println(monthName);
            }
        }
    }
}


이 코드에서 출력은 다음과 같습니다:

August
September
October
November
December


기술적으로는 마지막 break가 필요하지 않습니다. 왜냐하면 흐름이 switch statement을 벗어나기 때문입니다. 코드를 수정하기 쉽고 오류 가능성을 줄이기 위해 break 사용을 권장합니다. default 섹션은 case 섹션 중 하나에서 명시적으로 처리되지 않는 모든 값을 처리합니다.

다음 코드 예제인 SwitchDemo2는 하나의 statement에 여러 case 레이블을 가질 수 있는 방법을 보여줍니다. 이 코드 예제는 특정 월의 일 수를 계산합니다:

class SwitchDemo2 {
    public static void main(String[] args) {
        int month = 2;
        int year = 2000;
        int numDays = 0;

        switch (month) {
            case 1: case 3: case 5:
            case 7: case 8: case 10:
            case 12:
                numDays = 31;
                break;
            case 4: case 6:
            case 9: case 11:
                numDays = 30;
                break;
            case 2:
                if (((year % 4 == 0) && !(year % 100 == 0))
                     || (year % 400 == 0))
                    numDays = 29;
                else
                    numDays = 28;
                break;
            default:
                System.out.println("Invalid month.");
                break;
        }
        System.out.println("Number of Days = " + numDays);
    }
}


이 코드에서 출력된 결과는 다음과 같습니다:

Number of Days = 29

 

 

Using Strings in switch Statements

Java SE 7 이상에서는 switch statement의 expression에서 String 객체를 사용할 수 있습니다. 다음 코드 예제인 StringSwitchDemo는 String 변수인 month의 값에 기반하여 월의 숫자를 표시합니다:

 

다음은 Java SE 7 이상에서 String 객체를 switch statement의 expression으로 사용하는 코드 예제인 StringSwitchDemo입니다. 이 예제는 String으로 명명된 month 변수의 값에 따라 월의 숫자를 표시합니다.

public class StringSwitchDemo {

    public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

    public static void main(String[] args) {

        String month = "August";

        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);

        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
}


이 코드에서 출력된 결과는 8입니다.

switch expression에서 사용된 String은 마치 String.equals 메소드를 사용하는 것처럼 각 case 레이블과 관련된 expression과 비교됩니다. StringSwitchDemo 예제가 대소문자에 상관없이 모든 월을 받아들일 수 있도록 month는 소문자로 변환됩니다(toLowerCase 메소드 사용), 그리고 모든 case 레이블과 관련된 문자열들도 소문자입니다.

참고: 이 예제는 switch statement의 expression이 null인지 확인합니다. switch statement에서 expression이 null이 아닌지 확인하여 NullPointerException이 발생하지 않도록 해야 합니다.

 

The while and do-while Statements

 while statement는 특정 조건이 참인 동안 블록의 statements를 계속해서 실행합니다. 그 syntax은 다음과 같이 표현될 수 있습니다:

while (expression) {
     statement(s)
}


while statement은 expression을 평가하며, 이 expression은 반드시 boolean 값을 반환해야 합니다. 만약 statement가 참으로 평가되면, while statement은 while 블록 안의 statement(s)을 실행합니다. while statement은 expression를 계속해서 테스트하고 블록을 실행하는 것을 expression이 거짓으로 평가될 때까지 반복합니다. while 문을 사용하여 1부터 10까지의 값을 출력하는 것은 다음의 WhileDemo 프로그램에서와 같이 할 수 있습니다:

class WhileDemo {
    public static void main(String[] args){
        int count = 1;
        while (count < 11) {
            System.out.println("Count is: " + count);
            count++;
        }
    }
}


다음과 같이 while statement를 사용하여 무한 루프를 구현할 수 있습니다:

while (true){
    // 여기에 코드 작성
}


자바 프로그래밍 언어는 또한 do-while statement를 제공하며, 이는 다음과 같이 표현될 수 있습니다:

do {
     statement(s)
} while (expression);


do-while statement과 while statement의 차이점은 do-while statement은 루프의 상단이 아닌 하단에서 expression을 평가한다는 것입니다. 따라서 do 블록 내의 statement들은 최소한 한 번은 실행됩니다. 이는 다음의 DoWhileDemo 프로그램에서와 같이 보여집니다:

class DoWhileDemo {
    public static void main(String[] args){
        int count = 1;
        do {
            System.out.println("Count is: " + count);
            count++;
        } while (count < 11);
    }
}
 

 

The for Statement

for statement은 값의 범위를 반복하여 반복 작업을 수행하는 간편한 방법을 제공합니다. 프로그래머들은 특정 조건이 만족될 때까지 반복되는 방식 때문에 이를 "for 루프"라고 부릅니다. for 문의 일반적인 형태는 다음과 같이 표현될 수 있습니다:

for (initialization; termination; increment) {
    statement(s)
}


이 형태의 for statement을 사용할 때 다음 사항을 기억해야 합니다:

  초기화 expression은 루프를 초기화합니다. 이는 루프가 시작될 때 한 번 실행됩니다.
  종료 조건이 거짓으로 평가되면 루프가 종료됩니다.
  증감 expression은 루프의 각 반복이 끝날 때마다 호출됩니다. 이 expression이 값을 증가시키거나 감소시키는 것은 전혀 문제가 되지 않습니다.

다음 프로그램 ForDemo는 for statement의 일반적인 형태를 사용하여 1부터 10까지의 숫자를 표준 출력에 출력합니다:

class ForDemo {
    public static void main(String[] args){
         for(int i=1; i<11; i++){
              System.out.println("Count is: " + i);
         }
    }
}


이 프로그램의 출력은 다음과 같습니다:

Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10


코드가 초기화 expression 내에서 변수를 선언하는 방식을 주목하세요. 이 변수의 범위는 선언된 지점부터 for statement가 지배하는 블록의 끝까지 확장되므로, 종료 조건 및 증감 표현식에서도 사용할 수 있습니다. 만약 for 문을 제어하는 변수가 루프 외부에서 필요하지 않다면, 초기화 표현식 내에서 변수를 선언하는 것이 가장 좋습니다. i, j, k와 같은 이름은 for 루프를 제어하는 데 자주 사용되며, 초기화 expression 내에서 선언하면 그 life cycle이 제한되어 오류가 줄어듭니다.

for 루프의 세 가지 expression은 선택 사항이며, 다음과 같이 무한 루프를 생성할 수 있습니다:

// 무한 루프
for ( ; ; ) {
    // 여기에 코드 작성
}


for statement은 또한 컬렉션과 배열을 반복하기 위해 설계된 다른 형태를 가지고 있습니다. 이 형태는 때때로 향상된 for statement라고 하며, 루프를 더 간결하고 읽기 쉽게 만들 수 있습니다. 다음은 1부터 10까지의 숫자를 포함하는 배열을 예로 들어 보겠습니다:

int[] numbers = {1,2,3,4,5,6,7,8,9,10};


다음 프로그램 EnhancedForDemo는 향상된 for statement를 사용하여 배열을 반복합니다:

class EnhancedForDemo {
    public static void main(String[] args){
         int[] numbers = {1,2,3,4,5,6,7,8,9,10};
         for (int item : numbers) {
             System.out.println("Count is: " + item);
         }
    }
}


이 예제에서 변수 item은 numbers 배열의 현재 값을 가집니다. 이 프로그램의 출력은 이전과 동일합니다:

Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10


가능한 경우 항상 일반 형태보다 이 형태의 향상된 for 문을 사용할 것을 권장합니다.

 

Branching Statements

The break Statement

break statement 에는 두 가지 형태가 있습니다: 라벨이 있는 형태와 라벨이 없는 형태입니다. 이전에 switch statement에 대해 논의할 때 라벨이 없는 형태를 보았습니다. 또한 라벨이 없는 break statement을 사용하여 for, while 또는 do-while 루프를 종료할 수 있습니다. 다음의 BreakDemo 프로그램에서 이를 확인할 수 있습니다:

class BreakDemo {
    public static void main(String[] args) {

        int[] arrayOfInts = 
            { 32, 87, 3, 589,
              12, 1076, 2000,
              8, 622, 127 };
        int searchfor = 12;

        int i;
        boolean foundIt = false;

        for (i = 0; i < arrayOfInts.length; i++) {
            if (arrayOfInts[i] == searchfor) {
                foundIt = true;
                break;
            }
        }

        if (foundIt) {
            System.out.println("Found " + searchfor + " at index " + i);
        } else {
            System.out.println(searchfor + " not in the array");
        }
    }
}


이 프로그램은 배열에서 숫자 12를 검색합니다. 볼드체로 표시된 break statement는 해당 값을 찾았을 때 for 루프를 종료합니다. 그 후 제어 흐름은 for 루프 다음의 statement으로 이동합니다. 이 프로그램의 출력은 다음과 같습니다:

Found 12 at index 4


라벨이 없는 break statement은 가장 안쪽의 switch, for, while 또는 do-while statement을 종료하지만, 라벨이 있는 break 문은 외부 statement을 종료합니다. 다음 프로그램, BreakWithLabelDemo는 이전 프로그램과 유사하지만, 2차원 배열에서 값을 검색하기 위해 중첩된 for 루프를 사용합니다. 값을 찾으면 라벨이 있는 break statement이 외부 for (i = 0; i < arrayOfInts.length; i++) 루프를 종료합니다 (라벨이 "search"로 지정됨):

class BreakWithLabelDemo {
    public static void main(String[] args) {

        int[][] arrayOfInts = { 
            { 32, 87, 3, 589 },
            { 12, 1076, 2000, 8 },
            { 622, 127, 77, 955 }
        };
        int searchfor = 12;

        int i;
        int j = 0;
        boolean foundIt = false;

    search:
        for (i = 0; i < arrayOfInts.length; i++) {
            for (j = 0; j < arrayOfInts[i].length;
                 j++) {
                if (arrayOfInts[i][j] == searchfor) {
                    foundIt = true;
                    break search;
                }
            }
        }

        if (foundIt) {
            System.out.println("Found " + searchfor + " at " + i + ", " + j);
        } else {
            System.out.println(searchfor + " not in the array");
        }
    }
}


이 프로그램의 출력은 다음과 같습니다:

Found 12 at 1, 0


break statement은 라벨이 지정된 statement을 종료합니다; 제어 흐름을 라벨로 이동시키는 것이 아닙니다. 제어 흐름은 라벨이 지정된 (종료된) statement 바로 다음의 statement으로 이동합니다.

 

The continue Statement

continue statement은 for, while, 또는 do-while 루프의 현재 반복을 건너뜁니다. 라벨이 없는 형태는 가장 안쪽 루프의 본문의 끝으로 이동한 후, 루프를 제어하는 불리언 expression을 평가합니다. 다음 프로그램인 ContinueDemo는 문자열을 순회하면서 'p' 문자의 발생 횟수를 셉니다. 현재 문자가 'p'가 아니면 continue statement는 루프의 나머지 부분을 건너뛰고 다음 문자로 진행합니다. 만약 'p'라면, 프로그램은 문자 수를 증가시킵니다.

class ContinueDemo {
    public static void main(String[] args) {

        String searchMe = "peter piper picked a " + "peck of pickled peppers";
        int max = searchMe.length();
        int numPs = 0;

        for (int i = 0; i < max; i++) {
            // 'p' 문자에만 관심이 있습니다.
            if (searchMe.charAt(i) != 'p')
                continue;

            // 'p' 문자 처리
            numPs++;
        }
        System.out.println("Found " + numPs + " p's in the string.");
    }
}


이 프로그램의 출력은 다음과 같습니다:

Found 9 p's in the string.


이 효과를 더 명확히 보기 위해, continue statement을 제거하고 다시 컴파일해 보세요. 프로그램을 다시 실행하면, 9 대신 35개의 'p'를 찾았다고 잘못된 결과가 나옵니다.

라벨이 있는 continue statement은 주어진 라벨로 표시된 외부 루프의 현재 반복을 건너뜁니다. 다음 예제 프로그램인 ContinueWithLabelDemo는 중첩된 루프를 사용하여 한 문자열 내에서 부분 문자열을 검색합니다. 부분 문자열을 반복하는 루프와 검색되는 문자열을 반복하는 루프, 두 개의 중첩된 루프가 필요합니다. 다음 프로그램인 ContinueWithLabelDemo는 라벨이 있는 continue 형태를 사용하여 외부 루프의 반복을 건너뜁니다.

class ContinueWithLabelDemo {
    public static void main(String[] args) {

        String searchMe = "Look for a substring in me";
        String substring = "sub";
        boolean foundIt = false;

        int max = searchMe.length() - 
                  substring.length();

    test:
        for (int i = 0; i <= max; i++) {
            int n = substring.length();
            int j = i;
            int k = 0;
            while (n-- != 0) {
                if (searchMe.charAt(j++) != substring.charAt(k++)) {
                    continue test;
                }
            }
            foundIt = true;
            break test;
        }
        System.out.println(foundIt ? "Found it" : "Didn't find it");
    }
}


이 프로그램의 출력은 다음과 같습니다:

Found it

 


The return Statement

branch statement 중 마지막은 return statement입니다. return statement은 현재 메서드에서 빠져나가고, 제어 흐름은 메서드가 호출된 곳으로 돌아갑니다. return statement에는 값을 반환하는 형태와 값을 반환하지 않는 형태가 있습니다. 값을 반환하려면 return 키워드 뒤에 값을 (또는 값을 계산하는 표현식)을 넣습니다.

return ++count;


반환된 값의 데이터 타입은 메서드의 선언된 반환 값 타입과 일치해야 합니다. 메서드가 void로 선언된 경우, 값을 반환하지 않는 형태의 return을 사용합니다.

return;


클래스와 객체에 대한 수업에서는 메서드 작성에 필요한 모든 것을 다룰 것입니다.