안동민 개발노트

본문 시작

기본형 패턴과 switch

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

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

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

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

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


패턴 검사와 강제 축소 변환

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

아래 코드는 큰 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 기본형 패턴

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

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

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));
    }
}

다음은 원문 입력에 Java 25 프리뷰의 정확한 변환 규칙을 적용한 결과입니다.

원문 네 정수의 byte 패턴 판정

원문 코드와 Java 25 명세에 따른 조건과 결과를 비교합니다.

원문 네 정수의 byte 패턴 판정
int 입력byte로 정확한 변환원문의 반환값
127가능byte:127
128불가능int:128
-128가능byte:-128
-129불가능int:-129
127
byte로 정확한 변환: 가능
원문의 반환값: byte:127
128
byte로 정확한 변환: 불가능
원문의 반환값: int:128
-128
byte로 정확한 변환: 가능
원문의 반환값: byte:-128
-129
byte로 정확한 변환: 불가능
원문의 반환값: int:-129

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

javac --enable-preview --release 25 -Xlint:preview ExactBytePatternProbe.java
java --enable-preview ExactBytePatternProbe
# byte:127
# int:128
# byte:-128
# int:-129

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)));
    }
}

정확한 변환 실험표

기본형 변환은 단순 최솟값·최댓값 범위만이 아니라 부동소수점이 정수를 정확히 표현하는지, 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));
        }
    }
}

아래 표는 실제 실행 기록이 아니라 원문 입력과 Java 25 프리뷰 명세에 따른 분기입니다.

double에서 int로의 정확한 변환 경계

원문 코드와 Java 25 명세에 따른 조건과 결과를 비교합니다.

double에서 int로의 정확한 변환 경계
원문 입력int 패턴이유
0.0 · 42.0일치정수 값 보존
Integer.MAX_VALUE일치int 최댓값 보존
-0.0불일치음의 0 부호 손실
42.25불일치소수부 손실
MAX_VALUE + 1.0불일치int 범위 초과
NaN · +Infinity불일치정수로 정확히 표현할 수 없음
0.0 · 42.0
int 패턴: 일치
이유: 정수 값 보존
Integer.MAX_VALUE
int 패턴: 일치
이유: int 최댓값 보존
-0.0
int 패턴: 불일치
이유: 음의 0 부호 손실
42.25
int 패턴: 불일치
이유: 소수부 손실
MAX_VALUE + 1.0
int 패턴: 불일치
이유: int 범위 초과
NaN · +Infinity
int 패턴: 불일치
이유: 정수로 정확히 표현할 수 없음

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


적용 판단

  • 수치 파서와 직렬화 경계의 명시적 입력 검사를 대체하기 전에 오류 메시지 요구 사항을 확인합니다.
  • 정확한 변환 분기가 기존 형변환의 절삭·반올림과 다른지 비교합니다.
  • long·boolean switch가 복잡한 if보다 실제로 읽기 좋은지 검토합니다.
  • 안정 산출물에는 프리뷰 문법을 넣지 않고 실험 결과만 설계 문서에 반영합니다.
  • 다음 JDK 릴리스에서는 해당 버전의 명세와 마이그레이션 요구를 다시 확인합니다.
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이며 컴파일·실행 모두 프리뷰 플래그를 사용합니다.