switch

if~else문에서 여러 개의 조건문이 올 때 else if문으로 해결할 수도 있지만, 너무 많다 보면 코드가 산만해 보일 수도 있습니다. 이렇게 판단해야 할 조건문이 많을 경우에는 switch문을 사용합니다. switch문의 기본 구성은 아래와 같습니다.

switch (표현식) { // = 변수 또는 연산식이 올 수 있음
    case 값1:
    	문장;
        ...
        break;
    case 값2:
    	문장;
    	...
    	break;
    ...
    case 값N:
    	문장;
    	...
    	break;
    default:
    	문장;
    	...
}

switch문의 괄호 안에 쓰인 표현식의 값과 같은 case로 이동합니다. 여기서 표현식의 값은 char형, byte형, short형, int형, String형 중 하나만 올 수 있습니다(나중에 살펴보겠지만 대응되는 래퍼 타입이나 열거형도 올 수 있음). 그리고 default문은 일치하는 case가 없을 때 실행되는 영역으로 생략할 수 있습니다. 이해를 위해서 바로 예제를 보도록 하겠습니다.

public class SwitchExamples {
	public static void main(String[] args) {
        int month = 2;

        switch (month) {
            case 1: System.out.println("1월"); break;
            case 2: System.out.println("2월"); break;
            case 3: System.out.println("3월"); break;
            case 4: System.out.println("4월"); break;
            case 5: System.out.println("5월"); break;
            case 6: System.out.println("6월"); break;
            case 7: System.out.println("7월"); break;
            case 8: System.out.println("8월"); break;
            case 9: System.out.println("9월"); break;
            case 10: System.out.println("10월"); break;
            case 11: System.out.println("11월"); break;
            case 12: System.out.println("12월"); break;
            default: System.out.println("해당하는 달이 없습니다."); break;
        }
	}
}

결과는 '2월'이 출력됩니다. 코드를 보시면, switch문 괄호 안에 온 변수 month의 값이 2이므로 2에 해당하는 case로 이동합니다. 그리고 '2월'을 출력한 다음에 break문을 만나 switch문 밖으로 빠져나가게 됩니다.

낙하(fall-through)

만약 break문이 없으면 어떻게 될까요? 아래와 같이 코드를 수정하고 컴파일 후 결과를 확인하면 case가 연달아 실행되는 모습을 볼 수 있습니다.

public class SwitchExamples {
	public static void main(String[] args) {
        int month = 2;

        switch (month) {
            case 1: System.out.println("1월");
            case 2: System.out.println("2월");
            case 3: System.out.println("3월");
            case 4: System.out.println("4월");
            case 5: System.out.println("5월");
            case 6: System.out.println("6월");
            case 7: System.out.println("7월");
            case 8: System.out.println("8월");
            case 9: System.out.println("9월");
            case 10: System.out.println("10월");
            case 11: System.out.println("11월");
            case 12: System.out.println("12월");
            default: System.out.println("해당하는 달이 없습니다.");
        }
	}
}

코드를 살펴보면, switch 괄호 안의 변수 month의 값은 2이므로 그에 해당하는 case로 이동합니다. 이어서 '2월'을 출력하지만 break문이 없어서 switch문 밖으로 빠져나가지 못하고 switch문의 끝에 도달할 때까지 그 아래에 있는 문장을 모두 실행시킵니다. 출력하고 switch문을 빠져나오게 하려면 case마다 break문을 달아줘야 합니다.

이를 이용해서 의도적으로 아래와 같이 쓸 수도 있습니다.

public class SwitchExamples {
    public static void main(String[] args) {
        int month = 3;
        int monthDays = 0;

        switch (month) {
            case 1: case 3: case 5: case 7: case 8: case 10: case 12:
                monthDays = 31;
                break;
            case 4: case 6: case 9: case 11:
                monthDays = 30;
                break;
            case 2:
                monthDays = 28;
                break;
            default:
                System.out.println("해당하는 달이 없습니다.");
        }

        if (monthDays != 0) {
            System.out.println(month + "월은 " + monthDays + "일 까지 있습니다.");
        }
    }
}

스위치 표현식(Switch Expression)

자바 12에서 switch 표현식이 프리뷰 기능으로 추가되었습니다. 프리뷰 기능은 여러 개발자의 피드백을 받고 향후 변경될 여지가 있는 새로운 기능을 말합니다. 그러다가 버전이 올라가면서 자바 14부터 표준 기능으로 추가되었습니다. 어떻게 변경되었는지 차근차근 살펴보도록 하겠습니다.

case L ->

콜론(:) 대신에 화살표(->)를 이용하면 각 값을 쉼표(,)로 구분할 수 있으며 반복되는 키워드인 case를 하나로 줄일 수 있습니다. 그리고 의무적으로 작성해야 했던 break문도 따로 작성할 필요가 사라지면서 실수로 이를 빠뜨릴 염려 없이 더 가독성 높은 코드를 작성할 수 있게 되었습니다. 아래를 살펴보면 표현식 외에도 throw문이나 블록이 올 수도 있습니다.

case label_1, label_2, ..., label_n -> 표현식(expression);|throw문;|블록(block)

우선 낙하에서 살펴봤던 switch문에서 콜론(:)이 아니라 새로 도입된 화살표(->)로 바꿔보도록 하겠습니다. 결과를 실행해보면 break문이 없어도 case문이 연달아 실행되지 않는 걸 볼 수 있습니다.

int month = 3;
int monthDays = 0;

switch (month) {
	case 1, 3, 5, 7, 8, 10, 12  -> monthDays = 31;
	case 4, 6, 9                -> monthDays = 30;
	case 2                      -> monthDays = 28;
    // throw가 뭐고 new가 뭔지는 나중에 예외 처리 편에서 살펴볼 것이다.
    // 우선은 month에 -1이나 15가 들어왔을 때 에러를 발생시키는 것이라고 생각해두자.
	default -> throw new IllegalArgumentException("해당하는 달이 없습니다.");
}
System.out.println(month + "월은 " + monthDays + "일 까지 있습니다.");

여기서 이를 문장의 일부인 switch 표현식으로도 쓸 수 있습니다. 화살표 오른쪽에 있는 코드가 표현식이면 그 표현식의 값이 곧 switch 표현식의 값이 됩니다. 예를 들어서, month가 3일 때 화살표 우측에는 표현식 monthDays = 31이 있는데 이 식의 값은 31이므로 switch 표현식의 값은 31이 됩니다.

int monthDays = switch (month) {
	case 1, 3, 5, 7, 8, 10, 12  -> monthDays = 31;
	case 4, 6, 9                -> monthDays = 30;
	case 2                      -> monthDays = 28;
	default -> throw new IllegalArgumentException("해당하는 달이 없습니다.");
};

여기서 대입 연산자는 불필요하므로 위의 코드를 다시 아래와 같이 작성할 수 있습니다. 주의할 점은 모든 실행 경로에서 switch 표현식의 값이 정의되어야 한다는 것입니다. 예를 들어서 아래의 코드에서 default문을 없애면 month가 13일 때 switch 표현식의 값을 정의할 수 없으므로 컴파일 에러가 발생합니다.

switch 표현식의 완전성(exhaustiveness)

위에서 '모든 실행 경로에서 switch 표현식의 값이 정의되어야 한다'고 말했습니다. 예외적으로, switch 표현식에서 모든 열거형 상수를 나열했을 때 default문은 의미가 없으므로 컴파일러가 암시적으로 default절을 삽입합니다. 열거형도 뒤에서 다룰 내용이며 열거형을 접하신 적이 없다면 그냥 넘어가셔도 무방합니다.

Day day = Day.MONDAY;  
int i = switch (day) {  
    case MONDAY -> 1;  
    case TUESDAY -> 2;  
    case WEDNESDAY -> 3;  
    case THURSDAY -> 4;  
    case FRIDAY -> 5;  
    case SATURDAY -> 6;  
    case SUNDAY -> 7;
    // default -> throw new IncompatibleClassChangeError();
};
int monthDays = switch (month) {  
    case 1, 3, 5, 7, 8, 10, 12  -> 31;  
    case 4, 6, 9                -> 30;  
    case 2                      -> 28;  
    default -> throw new IllegalArgumentException("해당하는 달이 없습니다.");  
};

yield

switch 표현식에 새로운 화살표(->) 대신 기존의 콜론(:)을 사용할 수도 있습니다. 이때는 break문 대신에 yield문을 사용해야 합니다. yield문은 break문의 역할과 함께 switch 표현식의 값을 정의하는 역할도 합니다. 

int monthDays = switch (month) {  
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:  
        System.out.println("31일");  
        yield 31;  
    case 4: case 6: case 9:  
        System.out.println("30일");  
        yield 30;  
    case 2:  
        System.out.println("28일");  
        yield 28;  
    default:  
        throw new IllegalArgumentException("해당하는 달이 없습니다.");  
};

switch  표현식에서는 break문 대신에 yield문을 써야 하며, switch문에서는 break문은 쓸 수 있으나 yield문을 사용할 수는 없음에 주의합시다.

참고

'프로그래밍 관련 > 자바' 카테고리의 다른 글

11편. 반복문 (2)  (11) 2012.08.08
10편. 반복문 (1)  (6) 2012.07.31
8편. 제어문 (1)  (16) 2012.07.30
7편. 연산자 (2)  (27) 2012.07.25
6편. 연산자 (1)  (19) 2012.07.25