본문으로 건너뛰기

안동민 개발노트

본문 시작

기본형 패턴과 switch

Java 25 프리뷰의 기본형 패턴과 switch를 실행해 정확한 축소 변환, 값 적합성 검사와 정보 손실의 경계를 확인합니다.

JEP 507은 Java 25에서 패턴의 기본형 타입, instanceof, switch를 세 번째 프리뷰로 제공합니다.

int instanceof byte small은 형변환 가능 여부만 보는 것이 아니라 현재 값을 정보 손실 없이 byte로 변환할 수 있는지 검사하고 바인딩합니다.

long·float·double·booleanswitch 선택자로 사용할 수 있습니다.

모든 소스는 프리뷰 컴파일·실행 플래그가 필요합니다.

강제 cast와 exact primitive pattern은 정보 손실 시점이 다르다

먼저 narrowing cast하면 원본 값이 잘린 뒤라 pattern이 이미 잃은 정보를 복구할 수 없습니다.

  1. (int) original

    항상 변환 · overflow/truncation 뒤에 검사

  2. original instanceof int exact

    정확 변환 가능할 때만 bind · 아니면 fallback


패턴 검사와 강제 축소 변환

기본형 패턴의 목적은 안전하고 정확한 변환을 확인하는 것입니다.

아래 코드는 큰 long을 먼저 int로 형변환해 정보를 잃고 난 뒤 패턴 switch를 수행합니다.

4,294,967,297은 int 1로 잘려 출력이 wrong-positive=1이 됩니다.

preview/PrimitiveNarrowingBeforePatternBug.java
public final class PrimitiveNarrowingBeforePatternBug {
    static String classify(long original) {
        int narrowed = (int) original;
        return switch (narrowed) {
            case int value when value > 0 -> "positive:" + value;
            case int value -> "other:" + value;
        };
    }

    public static void main(String[] args) {
        long original = 4_294_967_297L;
        System.out.println("original=" + original);
        System.out.println("wrong-positive=" + classify(original).substring("positive:".length()));
    }
}

원칙은 원래 넓은 타입을 패턴 검사 대상으로 유지하고 instanceof int converted처럼 정확한 변환이 성공했을 때만 좁은 바인딩을 사용하거나 long 분기를 유지하는 것입니다.


instanceof 기본형 패턴

전통적으로 범위 확인 뒤 형변환을 별도 작성해야 했습니다.

프리뷰 패턴은 현재 intbyte로 정확히 표현될 때만 바인딩을 만듭니다.

preview/ExactBytePatternProbe.java
public final class ExactBytePatternProbe {
    static String encode(int value) {
        if (value instanceof byte exact) {
            return "byte:" + exact;
        }
        return "int:" + value;
    }

    public static void main(String[] args) {
        System.out.println(encode(127));
        System.out.println(encode(128));
        System.out.println(encode(-128));
        System.out.println(encode(-129));
    }
}

관찰 결과 127과 -128만 byte 분기를 사용합니다.

instanceof byte 검사 형태도 가능하지만 바인딩 형태가 형변환 중복을 없앱니다.

정확성 규칙은 소스 타입, 대상 타입, 실행 값에 따라 달라지므로 경계값 표로 확인합니다.

javac --enable-preview --release 25 -Xlint:preview ExactBytePatternProbe.java
java --enable-preview ExactBytePatternProbe
# byte:127
# int:128
# byte:-128
# int:-129
int 값은 byte 경계 안에서만 exact pattern과 일치한다

byte pattern은 type 이름만 검사하지 않고 현재 정수 값이 -128..127 범위에 정확히 들어오는지 확인합니다.

  1. -129

    int fallback

  2. -128

    byte match

  3. 0

    byte match

  4. 127

    byte match

  5. 128

    int fallback


long·boolean switch 분기

Java 25 프리뷰는 이전 switch 선택자 제한을 넓힙니다.

상수 레이블 타입은 선택자와 맞춰 0L처럼 씁니다.

boolean switch는 삼항 연산자보다 여러 문장이 필요한 분기를 식으로 구성할 수 있습니다.

preview/ExpandedPrimitiveSwitch.java
public final class ExpandedPrimitiveSwitch {
    static String bucket(long value) {
        return switch (value) {
            case 0L -> "zero";
            case 1L -> "one";
            case long number when number > 1_000_000_000L -> "large";
            case long number when number > 0 -> "positive";
            case long ignored -> "negative";
        };
    }

    static String access(boolean loggedIn) {
        return switch (loggedIn) {
            case true -> "member";
            case false -> "guest";
        };
    }

    public static void main(String[] args) {
        System.out.println(bucket(0L));
        System.out.println(bucket(5_000_000_000L));
        System.out.println(bucket(-3L));
        System.out.println(access(true));
        System.out.println(access(false));
    }
}

일반 case long을 가드가 있는 case보다 앞에 두면 뒤 case에는 도달할 수 없습니다.

안정 패턴 switch에서 배운 위에서 아래 선택 규칙이 기본형 패턴에도 적용됩니다.


record 패턴의 변환 규칙

record 구성 요소가 double이어도 현재 값을 int로 정확히 변환할 수 있으면 int whole 패턴과 일치합니다.

소수부가 있거나 범위를 벗어나거나 표현 손실이 발생하는 값은 대체 경로인 double 패턴으로 갑니다.

preview/PrimitiveRecordPatternConversion.java
public final class PrimitiveRecordPatternConversion {
    private record Measurement(double value) {}

    static String render(Measurement measurement) {
        return switch (measurement) {
            case Measurement(int whole) -> "whole:" + whole;
            case Measurement(double decimal) -> "decimal:" + decimal;
        };
    }

    public static void main(String[] args) {
        System.out.println(render(new Measurement(25.0)));
        System.out.println(render(new Measurement(25.5)));
        System.out.println(render(new Measurement(3_000_000_000.0)));
    }
}
double record component는 정확한 정수일 때만 int pattern으로 분기한다

소수부가 없다는 사실만으로 int 범위와 표현 정확성까지 보장되는 것은 아닙니다.

  1. 25.0

    Measurement(int whole) → 25

  2. 25.5

    Measurement(double decimal)

  3. 3_000_000_000.0

    int 범위 밖 → double

  4. NaN/Infinity

    double fallback


정확한 변환 실험표

기본형 변환은 단순 최솟값·최댓값 범위만이 아니라 부동소수점이 정수를 정확히 표현하는지, NaN·무한대·부호 있는 0을 어떻게 다루는지 확인해야 합니다.

운영 파서를 프리뷰 패턴 하나로 바꾸기 전 기존 입력 검사 의미와 비교합니다.

preview/PrimitivePatternBoundaryTable.java
public final class PrimitivePatternBoundaryTable {
    static String asInt(double value) {
        if (value instanceof int exact) {
            return "int:" + exact;
        }
        return "double:" + value;
    }

    public static void main(String[] args) {
        double[] values = {
            0.0,
            -0.0,
            42.0,
            42.25,
            Integer.MAX_VALUE,
            (double) Integer.MAX_VALUE + 1,
            Double.NaN,
            Double.POSITIVE_INFINITY
        };
        for (double value : values) {
            System.out.println(asInt(value));
        }
    }
}

예상 산출물에 부동소수점 텍스트 전체를 고정하기보다 어느 caseint인지 표로 기록합니다.

프리뷰 명세가 다음 릴리스에서 바뀌면 같은 점검 프로그램을 다시 실행합니다.

Primitive pattern은 경계값과 특수값을 표로 실험한다

대표 양수 하나만 성공시키면 부동소수점의 NaN·무한대·부호 있는 0과 exact integer 경계를 놓칩니다.

입력 군예시확인
정수 경계MIN-1·MIN·MAX·MAX+1target match
소수25.5·-0.5fallback 유지
큰 exact double3_000_000_000.0int 범위
특수값NaN·±Infinity·-0.0명세 분기
회귀기존 parser fixture오류 message

적용 판단

  • 수치 파서와 직렬화 경계의 명시적 입력 검사를 대체하기 전에 오류 메시지 요구 사항을 확인합니다.
  • 정확한 변환 분기가 기존 형변환의 절삭·반올림과 다른지 비교합니다.
  • long·boolean switch가 복잡한 if보다 실제로 읽기 좋은지 검토합니다.
  • 안정 산출물에는 프리뷰 문법을 넣지 않고 실험 결과만 설계 문서에 반영합니다.
  • JDK 26의 네 번째 프리뷰에서 변화한 명세와 마이그레이션을 다시 확인합니다.
instanceof 기본형 패턴이 일치하면 모든 수치 변환이 안전한가요?

해당 소스 값을 대상 타입으로 정확하게 변환할 수 있다는 뜻입니다.

업무 범위, 단위, 오버플로 이후 이미 손실된 값까지 보장하지 않습니다.

원본을 먼저 형변환하거나 계산에서 오버플로가 난 뒤 검사하면 패턴은 잃어버린 정보를 복구하지 못합니다.


연습 문제

0, 양수 int 범위, int를 넘는 양수, 음수를 분리하세요.

원본 long을 미리 형변환하지 않습니다.

해설 보기
preview/LongViewCountClassificationSolution.java
public final class LongViewCountClassificationSolution {
    static String classify(long viewCount) {
        return switch (viewCount) {
            case 0L -> "zero";
            case int exact when exact > 0 -> "compact:" + exact;
            case long value when value > 0 -> "large:" + value;
            case long value -> "invalid:" + value;
        };
    }

    public static void main(String[] args) {
        System.out.println(classify(45L));
        System.out.println(classify((long) Integer.MAX_VALUE + 1));
        System.out.println(classify(-1L));
    }
}

종료 기준은 compact:45, large:2147483648, invalid:-1이며 컴파일·실행 모두 프리뷰 플래그를 사용합니다.