JEP 361: Switch Expressions (Standard)

16 min read

Switch Expressions (Standard)

Summary

확장 switch하여 명령문 또는 표현식으로 사용할 수 있도록하여 두 형식 모두 기존 case ... :레이블 (전파 포함) 또는 새 case ... ->레이블 (전파 없음)을 사용하고, switch표현 새 레이블을 사용하여 값을 산출합니다. 이러한 switch 변경 사항은 일상적인 코딩을 단순화하고의 패턴 일치 사용 방법을 준비합니다. 이것은 JDK 12 및 JDK 13 의 미리보기 언어 기능 이었습니다 .

History

스위치 표현은 2017 년 12 월 JEP 325에 의해 제안되었습니다 . JEP 325는 미리보기 기능으로 2018 년 8 월 JDK 12를 대상 으로했습니다 . JEP 325의 한 가지 측면은 스위치 표현식에서 결과 값을 리턴하기 위해 명령문을 오버로드하는 것입니다 . JDK 12에 대한 피드백은 이 사용 이 혼란 스럽다고 제안했습니다 . 피드백에 대한 응답으로, JEP 354 는 JEP 325의 진화로 작성되었습니다. JEP 354는 새로운 성명을 제안하고 의 원래 의미를 복원했습니다. JEP 354는 2019 년 6 월 JDK 13을 겨냥했습니다.break break yield break 미리보기 기능으로. JDK 13에 대한 피드백은 스위치 표현식이 JDK 14에서 더 이상 변경되지 않고 최종적이고 영구적이 될 준비가되었음을 제안했습니다.

Motivation

패턴 일치 (JEP 305) 를 지원하기 위해 Java 프로그래밍 언어를 향상시킬 준비를 할 때 기존 switch문장 의 몇 가지 불규칙성 ( 오랫동안 사용자에게 자극을 주었던)이 장애가됩니다. 여기에는 스위치 레이블 사이의 기본 제어 흐름 동작 (통과), 스위치 블록의 기본 범위 지정 (전체 블록이 하나의 범위로 처리됨) 및 switch 더 자연스럽지만 명령문으로 만 작동 하는 사실이 포함 됩니다. 다 방향 조건을 표현식으로 표현하십시오.

현재 Java switch문장 의 설계는 C 및 C ++와 같은 언어를 밀접하게 따르며 기본적으로 시맨틱를 지원합니다. 이 전통적인 제어 흐름은 switch높은 수준의 컨텍스트에서 사용되는 낮은 수준의 코드 (예 : 이진 인코딩 파서)를 작성하는 데 유용 하지만 오류가 발생하기 쉬운 특성이 유연성을 능가하기 시작합니다. 예를 들어, 다음 코드에서 많은 break 명령문은 불필요하게 장황하게 만들며,이 시각적 노이즈는 종종 오류를 디버그하기 어렵게합니다.


switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;
    case TUESDAY:
        System.out.println(7);
        break;
    case THURSDAY:
    case SATURDAY:
        System.out.println(8);
        break;
    case WEDNESDAY:
        System.out.println(9);
        break;
}


case L ->레이블이 일치하면 레이블 오른쪽에있는 코드 만 실행됨을 나타 내기 위해 새로운 형식의 스위치 레이블 "case L ->" 을 도입 할 것을 제안합니다 . 또한 사례 당 여러 상수를 쉼표로 구분하여 허용 할 것을 제안합니다.

이전 코드를 작성할 수 있습니다 :


switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

" case L ->"스위치 레이블 의 오른쪽에있는 코드 는 표현식, 블록 또는 (편의를 위해) throw명령문으로 제한됩니다. 이는 arms 이 로컬 변수를 도입해야하는 즐거운 결과를 가져 오며 블록에 포함되어야하고 따라서 스위치 블록의 다른 arms 에 대한 범위에 속하지 않습니다. 이것은 지역 변수의 범위가 전체 블록 인 전통적인 스위치 블록에 대한 또 다른 성가심을 제거합니다.

switch (day) {
    case MONDAY:
    case TUESDAY:
        int temp = ...     // The scope of 'temp' continues to the }
        break;
    case WEDNESDAY:
    case THURSDAY:
        int temp2 = ...    // Can't call this variable 'temp'
        break;
    default:
        int temp3 = ...    // Can't call this variable 'temp'
}

기존의 많은 switch 명령문은 본질적으로 switch표현식의 시뮬레이션으로, 각 팔은 공통 대상 변수에 지정하거나 값을 반환합니다.


int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalStateException("Wat: " + day);
}

이것을 진술로 표현하는 것은 우회적이며 반복적이며 오류가 발생하기 쉽습니다. 저자는 numLetters매일 의 가치를 계산해야한다고 표현했습니다 . 보다 명확하고 안전한 switch expression을 사용하여 직접 말할 수 있어야합니다 .

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};

결과적으로 switch표현식을 지원하도록 확장 하면 흐름 분석 확장 (표현식이 항상 값을 계산하거나 갑자기 완료해야 함)과 같은 일부 추가 요구 사항이 발생하고 표현식의 일부 사례 switch가 값을 산출하지 않고 예외를 처리 할 수 있습니다.

Description

Arrow labels

case L :스위치 블록의 기존 " "레이블 외에도 " "레이블을 사용하여 새로운 단순화 된 양식을 정의합니다 case L ->. 레이블이 일치하면 화살표 오른쪽의 표현식 또는 명령문 만 실행됩니다. 넘어지지 않습니다. 예를 들어, switch새로운 형식의 레이블을 사용하는 다음 명령문이 제공됩니다.

static void howMany(int k) {
    switch (k) {
        case 1  -> System.out.println("one");
        case 2  -> System.out.println("two");
        default -> System.out.println("many");
    }
}

다음 코드 :

howMany(1);
howMany(2);
howMany(3);

결과는 다음과 같습니다.

one
two
many

Switch expressions

우리는 switch문장을 표현식으로 사용할 수 있도록 확장합니다 . 예를 들어, 표현식 howMany을 사용 하도록 이전 메소드를 다시 작성할 수 있으므로 switch단일 메소드 만 사용합니다 println.


static void howMany(int k) {
    System.out.println(
        switch (k) {
            case  1 -> "one";
            case  2 -> "two";
            default -> "many";
        }
    );
}

일반적인 경우 switch식은 다음과 같습니다.


T result = switch (arg) {
    case L1 -> e1;
    case L2 -> e2;
    default -> e3;
};

switch식은 폴리 표현이다; 대상 유형이 알려진 경우이 유형은 각 팔로 아래로 밀립니다. switch식 의 유형은 알려진 경우 대상 유형입니다. 그렇지 않은 경우 독립형 유형은 각 케이스 암의 유형을 결합하여 계산됩니다.

Yielding a value

대부분의 switch표현식에는 " case L ->"스위치 레이블 오른쪽에 단일 표현식이 있습니다. 전체 블록이 필요한 yield경우 값을 생성하는 새 명령문을 도입 하여 둘러싸는 switch표현식 의 값이됩니다 .


int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        yield result;
    }
};

switch표현은 같은 수 switch문, 또한 "전통적인 스위치 블록을 사용하는 case L:"스위치 라벨 (의미를 통해 가을을 의미). 이 경우 새 yield명령문을 사용하여 값이 산출됩니다 .


int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

break(라벨을 포함하거나 포함하지 않은) 두 개의 진술 yield은 switch진술과 switch표현 사이의 명확한 명확성을 용이하게 한다 : 표현은 switch아니지만 switch표현은 break진술 의 대상이 될 수있다 . 그리고 문장은 switch아니지만 표현은 switch문장의 대상이 될 수 있습니다 yield.

키워드 yield가 아닌 제한된 식별자 (예 var:) yield는 이름 이 지정된 클래스 가 불법 임을 의미합니다 . yield범위에 단항 메소드가있는 경우 , 표현식 yield(x)은 모호하며 (메소드가 괄호로 묶인 표현식 인 메소드 호출 또는 yield 문일 수 있음),이 모호함은 yield 명령문을 위해 해결됩니다. 메소드 호출이 선호 this되는 경우 인스턴스 메소드 또는 정적 메소드의 클래스 이름으로 메소드를 규정해야합니다 .

Exhaustiveness

switch표현 의 경우 철저 해야합니다 . 가능한 모든 값에 대해 일치하는 스위치 레이블이 있어야합니다. (명쾌하게 switch진술 할 필요는 없습니다.)

실제로 이것은 일반적으로 default조항이 필요함을 의미 합니다. 그러나 enum switch알려진 모든 상수를 포함 하는 표현식 의 경우 default, enum정의가 컴파일 시간과 런타임 사이에 변경 되었음을 나타내는 절이 컴파일러에 의해 삽입됩니다 . 이 암시 적 default절 삽입 에 의존 하면보다 강력한 코드가 만들어집니다. 이제 코드가 다시 컴파일 될 때 컴파일러는 모든 사례가 명시 적으로 처리되는지 확인합니다. 개발자가 명시 적 default절 (오늘과 마찬가지로)을 삽입 한 경우 가능한 오류가 숨겨져 있습니다.

또한 switch표현식은 값을 사용하여 정상적으로 완료되거나 예외를 발생시켜 갑자기 완료되어야합니다. 이것은 많은 결과를 초래합니다. 먼저, 컴파일러는 모든 스위치 레이블에 대해 일치하는지 확인한 후 값을 얻을 수 있는지 확인합니다.


int i = switch (day) {
    case MONDAY -> {
        System.out.println("Monday"); 
        // ERROR! Block doesn't contain a yield statement
    }
    default -> 1;
};

i = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY: 
        yield 0;
    default: 
        System.out.println("Second half of the week");
        // ERROR! Group doesn't contain a yield statement
};

또 다른 결과는 제어 문,이다 break, yield, return과 continue, 스루 점프 할 수없는 switch등 다음과 같이 표현 :


z: 
    for (int i = 0; i < MAX_VALUE; ++i) {
        int k = switch (e) { 
            case 0:  
                yield 1;
            case 1:
                yield 2;
            default: 
                continue z; 
                // ERROR! Illegal jump through a switch expression 
        };
    ...
    }


Dependencies

이 JEP는 JEP 325 및 JEP 354 에서 발전했습니다 . 그러나이 JEP는 독립형이며이 두 JEP에 의존하지 않습니다.

JEP 305로 시작하는 패턴 일치에 대한 향후 지원은 이 JEP를 기반으로합니다.

Risks and Assumptions

레이블이 있는 switch진술 의 필요성 case L ->은 불분명합니다. 다음 고려 사항이 포함을 지원했습니다.

  • switch부작용으로 운영하지만, 일반적으로 여전히 "라벨 당 하나 개의 행동"이다 문이 있습니다. 새로운 스타일의 레이블을 사용하여 접을 수있게하면 문장이보다 간단하고 오류 발생이 줄어 듭니다.

  • switch문장 블록 의 기본 제어 흐름은 깨지기보다는 넘어가는 것이 Java 역사 초기에 불행한 선택이었으며 개발자에게 계속 큰 문제가되고 있습니다. 표현식 switch뿐만 아니라 일반적으로 구성에 대해이 문제를 해결하면 switch이 선택의 영향이 줄어 듭니다.

  • 직교 특징으로 원하는 이점 (표현성, 더 나은 제어 흐름, 더 넓은 범위 지정)을 괴롭 히면 switch표현과 switch진술이 더 공통적 일 수 있습니다. switch표현과 표현 의 차이가 클수록 switch언어를 배우는 것이 더 복잡해지고 개발자가 스스로 이해하기가 더 예리합니다.

참조