Technology/Java

Java의 제어문을 정복해보자!

ikjo 2022. 8. 6. 22:28

제어문이란?

제어문은 프로그램의 흐름(flow)을 바꾸는 역할을 한다. 이러한 제어문에는 '조건문''반복문'이 있는데, 조건문은 조건에 따라 다른 문장이 수행되도록 하고, 반복문은 특정 문장들을 반복해서 수행한다.

 

조건문

조건문은 조건식문장을 포함하는 블럭 { }으로 구성되어 있으며, 조건식의 연산결과에 따라 실행할 문장이 달라져 프로그램의 실행 흐름을 변경할 수 있다. 이러한 조건문에는 if문switch문 두가지가 있다.

 

if - else if - else문

if문의 조건식은 일반적으로 비교연산자논리연산자로 구성되며, else if문을 통해 여러가지 경우의 수에 대해 흐름을 제어할 수 있으며, else문을 통해 그 밖의 다른 모든 경우(반드시 실행)에 대해 흐름을 제어할 수 있다.

 

    private long calculate(long number1, long number2, String operator) {
        if (operator.equals("*")) {
            return number1 * number2;
        } else if (operator.equals("+")) {
            return number1 + number2;
        } else {
            return number1 - number2;
        }
    }

 

위 예시 코드는 두 개의 수와 연산자를 매개변수로 받았을 때, 각각의 연산자별로 두 수에 대한 처리를 반환하는 메서드이다. 매개변수 operator가 *일 경우와 +일 경우에 대해 각각의 연산을 처리해주도록 했고, 그 외의 경우에는 모두 뺄셈 연산 처리를 해주도록 했다.

 

switch 문과 switch 표현식

switch 표현식은 Java 12 이전까지 switch문이었다. switch문은 단 하나의 조건식으로 많은 경우의 수를 처리할 수 있어 다수의 조건 식이 존재하는 경우 if문 보다 성능상(조건식을 1번만 계산하면 되므로)이나 가독성면에서 유리할 수 있었다.

 

아래 코드는 여러가지 경우의 수를 if문으로 처리한 경우에 해당된다.

 

        char grade = 'A';
        if (grade == 'A' || grade == 'S') {
            System.out.println("Excellent!!");
        } else if (grade == 'B') {
            System.out.println("Good!!");
        } else if (grade == 'C') {
            System.out.println("so so");
        } else if (grade == 'D') {
            System.out.println("Bad");
        } else {
            System.out.println("Terrible");
        }

 

위 코드를 switch문을 통해 다음과 같이 나타낼 수 있다.

 

        char grade = 'A';
        switch (grade) {
            case 'S' :
            case 'A' :
                System.out.println("Excellent!!");
                break;
            case 'B' :
                System.out.println("Good!!");
                break;
            case 'C' :
                System.out.println("so so");
                break;
            case 'D' :
                System.out.println("Bad");
                break;
            default :
                System.out.println("Terrible");
        }

 

참고로 switch문의 조건식의 결과는 정수('문자' 포함) 또는 문자열이어야 하며 이와 일치하는 case문으로 이동하는데, break를 안할 경우 다음 case문으로 연이어 동작하게 되므로 유의해야 한다. 해당되는 case문이 하나도 없는 경우에는 default문으로 이동한다.

 

이러한 switch문은 Java 12에 와서 switch 표현식으로 바뀌게 되었는데, case문에 람다식을 사용할 수 있으며 multi case label이 가능하게 되었다. 이와 함께 매 case문마다 반복되던 break도 생략할 수 있게 됐다. 앞선 switch문을 switch 연산자를 적용하여 다음과 같이 나타낼 수 있다. 앞선 switch문 보다는 다소 가독성이 개선된 모습이다.

 

        char grade = 'A';
        switch (grade) {
            case 'S', 'A' -> System.out.println("Excellent!!");
            case 'B' -> System.out.println("Good!!");
            case 'C' -> System.out.println("so so");
            case 'D' -> System.out.println("Bad");
            default -> System.out.println("Terrible");
        }

 

또한 switch 표현식은 각 case문으로부터 결과값을 반환 받아 처리할 수도 있다.

 

        char grade = 'A';
        String result = switch (grade) {
            case 'S', 'A' -> "Excellent!!";
            case 'B' -> "Good!!";
            case 'C' -> "so so";
            case 'D' -> "Bad";
            default -> "Terrible";
        };
        System.out.println(result); // Excellent!!

 

Java 13에 들어와서는 yield 키워드 사용이 가능해져 블록 구문 내에서 반환값이 있는 표현식을 나타낼 수 있게 되었다.

 

        char grade = 'A';
        String result = switch (grade) {
            case 'S', 'A' -> {
                System.out.println("Excellent!!");
                yield "Diamond";
            }
            case 'B' -> {
                System.out.println("Good!!");
                yield "Gold";
            }
            case 'C' -> {
                System.out.println("so so");
                yield "Silver";
            }
            case 'D' -> {
                System.out.println("Bad");
                yield "Bronze";
            }
            default -> {
                System.out.println("Terrible");
                yield "No rank";
            }
        };
        System.out.println(result); // Diamond

 

 

반복문

반복문은 어떤 작업을 반복적으로 수행할 때 사용된다. 자바에서 반복문의 종류로는 for문, while문, do-while문이 있다. 이때 for문과 while문은 조건에 따라 한 번도 수행되지 않을 수도 있지만 do-while문의 경우 최소 한 번은 실행된다는 특징이 있다.

 

for문

for문의 구조는 '초기화', '조건식', '증감식', '블럭 { }'으로 구성되는데, 앞서 언급한 세가지 반복문 중에서 for문은 반복 횟수를 알고 있을 때 사용하기 적합하다. 대표적으로 어떤 배열이나 리스트 등의 컬렉션 데이터가 주어졌을 때 그 데이터의 길이 만큼 순회하는 경우에 주로 사용되곤 한다.

 

        char[] numbers = s.toCharArray();
        int presentNumber = numbers[0];
        int count = 1;
        int result = -1;

        for (int i = 1; i < numbers.length; i++) {
            if (presentNumber == numbers[i]) {
                count++;
            } else {
                presentNumber = numbers[i];
                count = 1;
            }
            if (count == 3) {
                StringBuilder sb = new StringBuilder();
                for (int j = 0; j < 3; j++) {
                    sb.append(presentNumber - 48);
                }
                result = Math.max(Integer.parseInt(sb.toString()), result);
            }
        }

 

JDK 1.5부터는 향상된 for문(enhanced for statement)을 통해 기존 for문 보다 편리하게 배열과 컬렉션에 저장된 데이터에 접근할 수 있게 되었다. 다만, 향상된 for문을 통해서는 데이터를 변경할 수 없다.

 

    Set<List<Integer>> burstPositions = new HashSet<>();
    
    int r, c;
    for (List<Integer> burstPosition : burstPositions) {
        r = burstPosition.get(0);
        c = burstPosition.get(1);
        board[r][c] = 0;
    }

 

또한 for문은 알고리즘 문제 풀이 시 정해진 크기의 2차원 배열 데이터를 입력받을 때도 주로 사용되곤 한다.

 

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int[][] board = new int[7][7];

        StringTokenizer st;
        for (int i = 0; i < 7; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < 7; j++) {
                board[i][j] = Integer.parseInt(st.nextToken());
            }
        }

 

참고로 for문을 구성하는 '초기화', '조건식', '증감식' 부분들은 모두 생략이 가능한데, 이 경우 무한 반복문이 된다.

 

    for (;;) {
        System.out.println("Hello!!");
    }

 

while문

while문은 '조건식'과 '블럭 { }'만으로 구성되어 있는데, for문과 비교하여 초기화나 증감식이 필요하지 않아 구조가 간단하며, 조건식이 거짓이 될 때까지 블럭 내의 문장을 반복한다. 많은 경우에 사용되며 개인적으로는 BFS 탐색 시에 주로 사용하곤 한다. 큐에 노드를 삽입하면서 그래프 탐색을 수행하고 이 작업을 큐가 빌(empty) 때까지 계속 반복하는 것이다.

 

        boolean[][] visited = new boolean[height][width];

        Queue<Node> queue = new LinkedList<>();
        queue.add(new Node(0, 0, 1));
        visited[0][0] = true;

        int nx, ny;

        while (!queue.isEmpty()) {

            Node node = queue.poll();
            
            // ...
        }

 

참고로 while문의 조건식은 생략할 수 없으며, 조건식에 true를 넣어주면 무한 반복문이 된다. 이 경우에는 보통 while문 내부에 if문과 함께 break문을 사용하곤한다.

 

    while (true) {
        
        // ...
        
        if (count == 10) {
             // ...
             break;
        }
    }

 

do-while문

do-while문은 while문의 변형된 구조로 while문과 동일하게 '조건식'과 '블럭 { }'으로 구성되지만 블럭 { }을 먼저 수행하고 조건식이 실행되는 특징이 있다. 이로 인해 do-while문의 경우 while문과 다르게 최소한 한 번은 블럭 { } 내 로직들이 실행된다. (개인적으로는 잘 쓰지 않는 반복문이긴 하다.)

 

    do {
        // 반복 수행할 로직들 (최소 한 번은 실행)
    } while (조건식);

 

break문과 continue문

앞서 while-true문(무한 반복문)에서 언급했듯이 break문은 주로 if문과 함께 사용되어 특정 조건을 만족했을 경우 자신이 포함된 가장 가까운 반복문을 벗어날 수 있도록 해준다. 참고로 switch문에서도 break문이 사용될 수 있다.

 

        while (start < end) {
            characteristicValue = Math.abs(solutions[start] + solutions[end]);
            if (target > characteristicValue) {
                target = characteristicValue;
                result[0] = solutions[start];
                result[1] = solutions[end];
                if (target == 0) break;
            }
        
            // ...
        }

 

continue문은 반복문 내에서만 사용될 수 있으며, 반복이 진행되는 도중에 continue문을 만나면 반복문의 끝으로 이동하여 다음 반복으로 넘어간다. (for문의 경우 증감식으로, while문의 경우 조건식으로) break문과 달리 반복문을 그 즉시 종료하는 것이 아니라 다음 반복을 계속 수행한다는 점이 특징이다. 마찬가지로 주로 if문과 함께 사용된다.

 

    for (int col = 0; col < 7; col++) {
        int row = fall(ball, col);
        if (row == -1) {
            continue;
        }

        // ...
    }

 

 

참고자료

  • 도우출판 "자바의 정석"