JDK 14 핵심 기능 — Records, 패턴 매칭, 그리고 유용한 NPE 메시지

Records (Preview)

Java 14에서 등장한 Record(JEP 359)는 불변 데이터 클래스를 한 줄로 선언하는 문법입니다. equals(), hashCode(), toString(), getter를 컴파일러가 자동 생성합니다.

택배 송장에 비유하면, 기존 클래스는 송장 양식을 매번 수기로 그리는 것이고, Record는 표준 송장 용지에 이름과 주소만 채우는 것입니다.

// === Record 선언: 한 줄이면 충분 ===
record Point(int x, int y) {}

record User(String name, int age, String email) {
    // 컴팩트 생성자: 매개변수 선언 없이 검증 로직 작성
    User {
        if (age < 0) {
            throw new IllegalArgumentException("나이는 0 이상이어야 합니다: " + age);
        }
        // name, age, email 필드는 자동 할당됨
    }

    // 커스텀 메서드 추가 가능
    String displayName() {
        return name + " (" + age + "세)";
    }
}

public class RecordDemo {
    public static void main(String[] args) {
        // Record 인스턴스 생성
        Point p1 = new Point(10, 20);
        Point p2 = new Point(10, 20);

        // 자동 생성된 toString()
        System.out.println("p1 = " + p1);
        // 출력: p1 = Point[x=10, y=20]

        // 자동 생성된 equals() — 값 기반 비교
        System.out.println("p1.equals(p2) = " + p1.equals(p2));
        // 출력: p1.equals(p2) = true

        // 접근자 메서드 — getX()가 아닌 x()
        System.out.println("p1.x() = " + p1.x());
        System.out.println("p1.y() = " + p1.y());
        // 출력: p1.x() = 10
        // 출력: p1.y() = 20

        // 커스텀 검증 및 메서드
        User user = new User("홍길동", 30, "hong@example.com");
        System.out.println(user.displayName());
        // 출력: 홍길동 (30세)

        // 검증 실패 시 예외
        try {
            new User("김영희", -1, "kim@example.com");
        } catch (IllegalArgumentException e) {
            System.out.println("검증 실패: " + e.getMessage());
            // 출력: 검증 실패: 나이는 0 이상이어야 합니다: -1
        }
    }
}

Record는 다음을 할 수 없습니다: 다른 클래스 상속(암묵적으로 java.lang.Record 상속), 인스턴스 필드 추가(선언된 컴포넌트만 가능), 필드 값 변경(final). 이 제약이 오히려 불변 데이터 객체로서의 명확한 의도를 보장합니다.

Pattern Matching for instanceof (Preview)

instanceof 검사 후 바로 캐스팅하는 반복 패턴이 Java 14에서 간결해졌습니다(JEP 305).

public class PatternMatchingDemo {
    // 다양한 타입의 정보를 출력하는 메서드
    static String formatValue(Object obj) {
        // === 기존 방식: instanceof + 캐스팅 반복 ===
        // if (obj instanceof String) {
        //     String s = (String) obj;  // 캐스팅 필요
        //     return "문자열(길이=" + s.length() + "): " + s;
        // }

        // === 새로운 방식: 패턴 변수로 자동 캐스팅 ===
        if (obj instanceof String s) {
            // s는 이미 String 타입 — 캐스팅 불필요
            return "문자열(길이=" + s.length() + "): " + s;
        } else if (obj instanceof Integer i) {
            return "정수: " + i + " (짝수? " + (i % 2 == 0) + ")";
        } else if (obj instanceof Double d) {
            return "실수: " + String.format("%.2f", d);
        } else if (obj instanceof int[] arr) {
            return "배열(크기=" + arr.length + ")";
        } else {
            return "기타: " + obj.getClass().getSimpleName();
        }
    }

    public static void main(String[] args) {
        System.out.println(formatValue("안녕하세요"));
        System.out.println(formatValue(42));
        System.out.println(formatValue(3.14159));
        System.out.println(formatValue(new int[]{1, 2, 3}));
        // 출력:
        // 문자열(길이=5): 안녕하세요
        // 정수: 42 (짝수? true)
        // 실수: 3.14
        // 배열(크기=3)

        // 패턴 변수는 논리 연산자와 결합 가능
        Object value = "Java 14";
        if (value instanceof String s && s.length() > 5) {
            System.out.println("5자 초과 문자열: " + s);
        }
        // 출력: 5자 초과 문자열: Java 14
    }
}

패턴 변수의 스코프를 주의해야 합니다. if 블록 안에서만 유효하며, else 블록에서는 사용할 수 없습니다. 또한 && 연산자와는 결합되지만 ||와는 결합되지 않습니다. obj instanceof String s || s.isEmpty()는 컴파일 오류입니다. ||의 오른쪽에서 s가 바인딩되지 않은 상태일 수 있기 때문입니다.

Helpful NullPointerExceptions (JEP 358)

Java 개발자가 가장 많이 보는 예외 메시지가 더 유용해졌습니다. 어떤 변수가 null인지 정확히 알려줍니다.

import java.util.List;
import java.util.Map;

public class HelpfulNpeDemo {
    record Address(String city, String zipCode) {}
    record Company(Address address) {}
    record Employee(String name, Company company) {}

    public static void main(String[] args) {
        // 중첩된 null 참조
        Employee emp = new Employee("홍길동", new Company(null));

        try {
            // company.address가 null → address.city() 호출 시 NPE
            String city = emp.company().address().city();
        } catch (NullPointerException e) {
            System.out.println("NPE 메시지: " + e.getMessage());
            // Java 14 이전:
            //   NullPointerException (메시지 없음 또는 null)
            //
            // Java 14 이후:
            //   Cannot invoke "HelpfulNpeDemo$Address.city()"
            //   because the return value of
            //   "HelpfulNpeDemo$Company.address()" is null
        }

        // Map에서의 NPE도 상세 메시지 제공
        Map<String, List<String>> data = Map.of("fruits", List.of("사과", "배"));

        try {
            // "vegetables" 키가 없으므로 get()이 null 반환
            int size = data.get("vegetables").size();
        } catch (NullPointerException e) {
            System.out.println("NPE 메시지: " + e.getMessage());
            // 출력: Cannot invoke "java.util.List.size()"
            //        because the return value of
            //        "java.util.Map.get(Object)" is null
        }
    }
}

이 기능은 Java 14에서 -XX:+ShowCodeDetailsInExceptionMessages 플래그로 활성화할 수 있고, Java 15부터는 기본 활성화됩니다. 프로덕션 환경에서도 NPE 디버깅 시간을 크게 줄여줍니다.

Switch Expressions 정식 확정

Java 12에서 프리뷰로 시작해 Java 13에서 yield가 추가된 Switch Expressions가 Java 14에서 정식 기능(JEP 361)이 되었습니다. 더 이상 --enable-preview 플래그가 필요 없습니다.

최종 확정된 규칙을 정리하면 다음과 같습니다.

문법값 반환 방식fall-through
case L -> (화살표)표현식 또는 yield없음
case L: (콜론)yield 필수있음 (break로 방지)

Text Blocks (Second Preview)

Java 13에서 첫 프리뷰였던 Text Blocks가 두 번째 프리뷰로 이어집니다. 새로 추가된 이스케이프 시퀀스 두 가지가 있습니다.

  • \s — 공백 한 칸 (후행 공백 보존용)
  • \ (줄 끝) — 줄바꿈 방지 (긴 줄을 소스에서만 줄바꿈)

성능: ZGC와 G1 확장

ZGC macOS/Windows 지원(JEP 364, 365): 기존에 Linux 전용이던 ZGC가 macOS와 Windows에서도 실험적으로 사용 가능해졌습니다. 개발 환경에서 프로덕션과 동일한 GC를 테스트할 수 있게 되었습니다.

NUMA-Aware G1(JEP 345): 멀티소켓 서버에서 G1 GC가 NUMA(Non-Uniform Memory Access) 아키텍처를 인식하여 로컬 메모리를 우선 할당합니다. 대형 서버에서 GC 성능이 향상됩니다.

정리

기능상태이후 변화
RecordsPreviewJava 16에서 정식
Pattern Matching (instanceof)PreviewJava 16에서 정식
Helpful NPE정식 (플래그 필요)Java 15에서 기본 활성화
Switch Expressions정식 확정완료
Text BlocksSecond PreviewJava 15에서 정식
ZGC macOS/Windows실험적Java 15에서 프로덕션 레디

Java 14는 “프리뷰의 꽃”이라 할 수 있습니다. Records와 Pattern Matching이라는 Java의 미래를 결정짓는 두 기능이 동시에 등장했고, Switch Expressions가 정식으로 자리 잡았습니다. Helpful NPE는 작지만 일상적 디버깅에 가장 체감되는 변화입니다. 프로덕션에서 Java 8이나 11을 사용 중이라면, 이 기능들이 Java 17 LTS로 업그레이드하는 강력한 동기가 됩니다.

이 글이 도움이 되었나요?