switch 구문 수정하기

Java SE 14에서는 switch 키워드에 대해 보다 편리한 또 다른 구문인 switch 표현식을 사용할 수 있습니다.

이 새로운 구문을 만들게 된 동기는 몇 가지입니다.

  1. switch 레이블 사이의 기본 제어 흐름 동작은 통과하는 것입니다. 이 구문은 오류가 발생하기 쉬우며 애플리케이션에서 버그를 유발합니다.
  2. switch 블록은 하나의 블록으로 취급됩니다. 이는 특정 case 하나에만 변수를 정의해야 하는 경우에 장애가 될 수 있습니다.
  3. switch 문은 명령문입니다. 이전 섹션의 예제에서는 변수에 각각의 case에 값이 주어졌습니다. 이를 표현식으로 만들면 더 읽기 쉬운 코드가 될 수 있습니다.

이전 섹션에서 다룬 구문인 switch 문 은 Java SE 14에서도 여전히 사용할 수 있으며 의미도 변경되지 않았습니다. Java SE 14부터는 switch에 대한 새로운 구문인 _switch 표현식_을 사용할 수 있습니다.

이 구문은 switch 레이블의 구문을 수정합니다. 애플리케이션에 다음과 같은 switch 문 이 있다고 가정해 보겠습니다.

int day = ...; // any day
int len = 0;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        len = 6;
        break;
    case TUESDAY:
        len = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        len = 8;
        break;
    case WEDNESDAY:
        len = 9;
        break;
}
System.out.println("len = " + len);

스위치 표현식 구문을 사용하면 이제 다음과 같은 방식으로 작성할 수 있습니다.

int day = ...; // any day
int len =
    switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY                -> 7;
        case THURSDAY, SATURDAY     -> 8;
        case WEDNESDAY              -> 9;
    }
System.out.println("len = " + len);

이제 switch 레이블의 구문은 case L ->입니다. 레이블이 일치하는 경우 레이블 오른쪽에 있는 코드만 실행됩니다. 이 코드는 단일 표현식, 블록 또는 throw 명령문일 수 있습니다. 이 코드는 하나의 블록이므로 이 특정 블록에 로컬인 변수를 정의할 수 있습니다.

또한 이 구문은 이전 예제에서와 같이 쉼표로 구분된 케이스당 여러 개의 상수를 지원합니다.

 

값 생성하기

이 switch 문은 표현식으로 사용할 수 있습니다. 예를 들어, 이전 섹션의 예제는 다음과 같은 방식으로 switch 문을 사용하여 다시 작성할 수 있습니다.

int quarter = ...; // any value
 
String quarterLabel =
    switch (quarter) {
        case 0  -> "Q1 - Winter";
        case 1  -> "Q2 - Spring";
        case 2  -> "Q3 - Summer";
        case 3  -> "Q3 - Summer";
        default -> "Unknown quarter";
    };

case 블록에 명령문이 하나만 있는 경우 이 명령문에서 생성된 값은 switch 표현식에 의해 반환됩니다.

코드 블록의 경우 구문은 약간 다릅니다. 일반적으로 return 키워드는 코드 블록에서 생성된 값을 나타내는 데 사용됩니다. 안타깝게도 이 구문은 switch 문의 경우 모호성을 유발합니다. 다음 예시를 살펴보겠습니다. 이 코드는 컴파일되지 않으며 예제로만 제공됩니다.

// Be careful, this code does NOT compile!
public String convertToLabel(int quarter) {
    String quarterLabel =
        switch (quarter) {
            case 0  -> {
                System.out.println("Q1 - Winter");
                return "Q1 - Winter";
            };
            default -> "Unknown quarter";
        };
    }
    return quarterLabel;
}

quarter가 0인 경우 실행되는 코드 블록은 값을 반환해야 합니다. 이 값을 나타내기 위해 return 키워드를 사용합니다. 이 코드를 자세히 살펴보면 case 블록에 하나, 메서드 블록에 하나 등 두 개의 return 명령문이 있다는 것을 알 수 있습니다. 여기서 모호한 점이 있습니다. 첫 번째 return의 의미가 무엇인지 궁금할 수 있습니다. 프로그램이 이 값으로 메서드를 종료한다는 의미일까요? 아니면 switch 문을 종료한다는 의미일까요? 이러한 모호함은 가독성을 떨어뜨리고 오류가 발생하기 쉬운 코드로 이어집니다.

이러한 모호함을 해결하기 위해 새로운 구문이 만들어졌는데, 바로 yield 명령문입니다. 이전 예제의 코드는 다음과 같은 방식으로 작성해야 합니다.

public String convertToLabel(int quarter) {
    String quarterLabel =
        switch (quarter) {
            case 0  -> {
                System.out.println("Q1 - Winter");
                yield "Q1 - Winter";
            };
            default -> "Unknown quarter";
        };
    }
    return quarterLabel;
}

yield 문은 switch 문의 모든 case 블록에서 사용할 수 있는 명령문입니다. 이 명령문에는 값과 함께 제공되며, 이 값은 둘러싸는 switch 문의 값이 됩니다.

 

Default 절 추가하기

Default 절을 사용하면 코드에서 선택기 값이 어떤 case 상수와도 일치하지 않는 경우를 처리할 수 있습니다.

switch 표현식의 대소문자는 완전해야 합니다. 가능한 모든 값에 대해 일치하는 switch 레이블이 있어야 합니다. switch 문은 완전할 필요는 없습니다. 선택기 대상이 스위치 레이블과 일치하지 않으면 이 switch 문은 아무 작업도 수행하지 않습니다. 이는 애플리케이션에 버그를 숨길 수 있는 장소가 될 수 있으므로 피해야 합니다.

대부분의 경우 default 절을 사용하여 완전성을 확보할 수 있지만, 알려진 상수를 모두 포함하는 enum switch 식의 경우 이 default 절을 추가할 필요가 없습니다.

여전히 처리해야 할 경우가 있습니다. 누군가 열거형에 열거형 값을 추가했지만 이 열거형에 대한 switch 문을 업데이트하는 것을 잊어버리면 어떻게 될까요? 이 경우를 처리하기 위해 컴파일러는 철저한 switch 문에 default 절을 추가합니다. 이 default 절은 일반적인 경우에는 절대 실행되지 않습니다. 열거된 값이 추가된 경우에만 실행되며, IncompatibleClassChangeError를 던집니다.

완전성 처리는 기존의 switch 문에서는 제공되지 않으며 열거된 값에 대한 switch 이외의 다른 경우에 사용되는 switch 표현식의 기능입니다.

 

switch 표현식에서 콜론 대/소문자 쓰기

switch 표현식은 case L:이 포함된 기존 case 블록을 사용할 수도 있습니다. 이 경우 폴 스루(통과) 의미가 적용됩니다. 값은 yield 명령문을 사용하여 산출됩니다.

int quarter = ...; // any value
 
String quarterLabel =
    switch (quarter) {
        case 0 :  yield "Q1 - Winter";
        case 1 :  yield "Q2 - Spring";
        case 2 :  yield "Q3 - Summer";
        case 3 :  yield "Q3 - Summer";
        default: System.out.println("Unknown quarter");
                 yield "Unknown quarter";
    };

 

Null 값 다루기

지금까지 switch 명령문은 null 선택자 값을 허용하지 않습니다. 널 값에 대해 switch를 시도하면 NullPointerException이 발생합니다.

Java SE 17에는 null 값을 허용하도록 switch 표현식을 개선하는 미리보기 기능이 있으므로 이러한 상황이 변경될 것으로 예상할 수 있습니다.