공공부하자개발 · 영어 학습 노트
자바
고급모던 자바와 성능0/10 완료
  • 01제네릭과 와일드카드
  • 02멀티스레드와 동기화
  • 03람다식과 함수형 인터페이스
  • 04Stream API와 병렬 처리
  • 05Optional로 NPE 방지
  • 06메서드 활용 패턴 (고급)
  • 07Java 21 모던 문법
  • 08어노테이션·리플렉션·동적 프록시
  • 09CompletableFuture 심화와 가상 스레드 실전
  • 10JVM 메모리·GC·OOM 진단
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 고급 › 07 / 10

Java 21 모던 문법

var, record, 텍스트 블록, switch 식, 패턴 매칭, sealed
섹션 7진행 0 / 10
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 var: 타입 추론, 타입 생략이 아니다

var 는 선언문의 타입을 초기값에서 추론합니다. 타입이 없어지는 것이 아닙니다.

java
var list = new ArrayList<String>();   // ArrayList<String>
var count = 0;                        // int. count = "x" 는 컴파일 오류

쓸 수 있는 곳과 없는 곳이 정해져 있습니다.

가능 불가
초기값 있는 지역 변수 초기값 없는 선언 var x;
for·for-each 변수 필드, 매개변수, 반환 타입
try-with-resources 람다 var f = () -> 1;

두 가지를 조심합니다. var list = new ArrayList<>(); 는 ArrayList<Object> 가 됩니다. 다이아몬드와 함께 쓰면 타입 인자를 잃습니다. 그리고 var result = service.process(x); 처럼 우변에서 타입이 안 보이면 읽는 사람이 타입을 추적해야 합니다. 우변에 생성자나 리터럴이 있을 때, 또는 Map.Entry<String, List<Order>> 처럼 타입이 길 때 씁니다.

2.2 record: 불변 데이터 운반자

java
public record Rect(double w, double h) implements Shape {
    Rect { if (w <= 0 || h <= 0) throw new IllegalArgumentException(...); }   // 압축 생성자
    static Rect square(double side) { return new Rect(side, side); }            // 정적 팩토리
    public double area() { return w * h; }
}

한 줄 헤더에서 생성자, 접근자(w(), h()), equals, hashCode, toString 이 나옵니다. 중급 07 에서 직접 만들던 세 메서드가 전부 자동입니다.

압축 생성자는 매개변수 목록 없이 쓰는 생성자입니다. 필드 대입 직전에 실행되므로 검증과 정규화(스케일 통일, trim)를 여기서 합니다. this.w = w 는 자동으로 붙습니다.

record 가 못 하는 것도 정해져 있습니다.

못 하는 것 이유 대안
헤더 밖 인스턴스 필드 상태는 헤더가 전부 계산값은 메서드로
setter, 필드 변경 모든 필드가 final moveX(dx) 처럼 새 객체 반환
클래스 상속 암묵적으로 final, extends 불가 인터페이스 구현은 가능
JPA 엔티티 기본 생성자·가변 필드 필요 DTO·값 객체에만

메서드 안에 지역 record 를 선언할 수도 있습니다. 스트림 중간 결과를 담을 임시 타입이 필요할 때 씁니다.

2.3 텍스트 블록: 여러 줄 문자열을 그대로

java
String sql = """
        SELECT o.order_no, o.amount
          FROM orders o
         WHERE o.status = 'PAID'
        """;

""" 로 열고 닫습니다. 안에서는 따옴표 이스케이프가 필요 없고 줄바꿈이 그대로 들어갑니다. 규칙은 셋입니다.

  • 들여쓰기 기준은 닫는 """ 의 위치입니다. 내용과 닫는 따옴표 중 가장 왼쪽 열이 0 이 됩니다.
  • 닫는 """ 를 다음 줄에 두면 끝에 줄바꿈이 붙고, 같은 줄에 두면 붙지 않습니다.
  • 줄 끝 \ 는 줄바꿈을 지웁니다. 소스에서는 두 줄, 실행 시에는 한 줄이 됩니다.

값 주입은 formatted() 입니다. String.format 과 같지만 문자열 뒤에 붙여 읽기 쉽습니다. SQL 에 사용자 값을 formatted 로 넣으면 SQL 인젝션이므로, SQL 은 텍스트 블록으로 쓰되 값은 바인딩 파라미터(:from, ?)로 넘깁니다.

2.4 switch 식: 값을 돌려주고 빠뜨리면 컴파일 오류

java
String kind = switch (day) {
    case 1, 7 -> "주말";
    case 2, 3, 4, 5, 6 -> "평일";
    default -> { String msg = "요일 아님: " + day; yield msg; }
};

옛 switch 와 세 가지가 다릅니다.

옛 switch 문 switch 식
break 를 빼먹으면 다음 case 로 흘러내림 화살표 -> 는 흘러내림 없음
값을 못 돌려줌, 변수에 대입 필요 식이므로 = 오른쪽에 온다
빠뜨린 case 는 조용히 통과 모든 경우를 다루지 않으면 컴파일 오류

블록이 필요하면 yield 로 값을 냅니다. 여러 label 은 쉼표로 묶습니다. 완전성 검사가 핵심입니다. int 처럼 경우가 무한한 타입은 default 가 필요하지만, enum 과 sealed 타입은 모든 상수·하위 타입을 나열하면 default 없이 컴파일됩니다. 이때 default 를 일부러 빼야 합니다. 새 경우가 추가됐을 때 컴파일 오류로 알려 주는 것이 목적이기 때문입니다.

2.5 패턴 매칭: 검사, 캐스팅, 분해를 한 번에

java
if (o instanceof String str && str.length() > 3) { ... }          // ① instanceof 패턴
case Circle c -> "원 r=" + c.r();                                  // ② 타입 패턴
case Rect(double w, double h) when w == h -> "정사각형";            // ③ 해체 패턴 + 가드
case null -> "결과 없음";                                           // ④ null 케이스

①은 instanceof 검사와 캐스팅된 변수 선언을 합칩니다. (String) o 를 쓸 일이 없어집니다. ②는 switch 가 타입으로 분기합니다. ③은 record 의 구성 요소를 바로 변수로 꺼내고, when 뒤 조건이 맞을 때만 그 case 를 탑니다.

case 의 순서가 중요합니다. Circle(double rad) when rad > 5 를 Circle(double rad) 뒤에 두면 절대 도달할 수 없어 컴파일 오류입니다. 특수한 조건을 앞에, 일반 경우를 뒤에 둡니다. ④ case null 이 없으면 switch 에 null 이 들어올 때 NullPointerException 입니다.

2.6 sealed: 타입의 집합을 닫는다

java
public sealed interface PaymentResult permits Approved, Declined, Pending { }
record Approved(String txId, BigDecimal amount) implements PaymentResult {}
record Declined(String reason, boolean retryable) implements PaymentResult {}
record Pending(int etaSeconds) implements PaymentResult {}

permits 목록 밖의 클래스는 이 인터페이스를 구현할 수 없습니다. 그래서 컴파일러가 "PaymentResult 는 이 셋뿐" 임을 알고, switch 완전성 검사에 씁니다.

옛 방식은 결과 클래스 하나에 status, reason, eta 필드를 전부 두고 null 로 구분했습니다. 어느 필드가 언제 유효한지는 주석으로만 알 수 있었습니다. sealed + record 는 경우마다 필요한 데이터만 갖습니다. 거절에는 사유가 있고 보류에는 예상 시각이 있으며, 승인에는 둘 다 없습니다.

enum 과의 차이는 경우마다 데이터가 다른가입니다. 상태 이름만 필요하면 enum, 경우마다 다른 필드가 붙으면 sealed 입니다. 새 경우(Refunded)를 추가하면 permits 와 모든 switch 가 컴파일 오류를 내므로 빠뜨릴 수 없습니다.

2.7 매일 쓰는 신규 API

버전 API 대체하는 것
11 String.strip/isBlank/lines/repeat trim, 정규식, 반복문
11 Files.readString/writeString BufferedReader 보일러플레이트
11 HttpClient HttpURLConnection
15 String.formatted String.format
16 Stream.toList() collect(Collectors.toList()). 결과가 수정 불가
21 SequencedCollection: getFirst/getLast/reversed get(0), get(size()-1)
21 가상 스레드 다음 레슨
핵심 원리
  • 2.1 var: 타입 추론, 타입 생략이 아니다
  • 2.2 record: 불변 데이터 운반자
  • 2.3 텍스트 블록: 여러 줄 문자열을 그대로
  • 2.4 switch 식: 값을 돌려주고 빠뜨리면 컴파일 오류
  • 2.5 패턴 매칭: 검사, 캐스팅, 분해를 한 번에
  • 2.6 sealed: 타입의 집합을 닫는다
  • 2.7 매일 쓰는 신규 API
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제