1. record 클래스 깊이 이해하기
record 패턴으로 들어가기 전에, record 클래스가 무엇인지부터 짚고 넘어가겠습니다.
Record는 equals, hashCode, toString 메서드를 자동으로 생성하고, 모든 컴포넌트에 대한 게터도 자동으로 만들어 주는 간결하고 불변(immutable)한 클래스입니다. Record 클래스는 Java 16에서 등장했고, DTO를 일일이 손코딩하던 개발자들에게 큰 선물이 되었습니다.
// 고전: 두 필드를 가진 Point
record Point(int x, int y) {}
객체 생성과 필드 접근:
Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p); // Point[x=10, y=20]
왜 레코드 패턴이 필요한가
Java 21 이전에는 어떤 객체가 특정 record인지 확인하고 그 필드를 얻으려면, instanceof로 타입을 검사하고 캐스팅한 뒤 게터를 호출하는 수동 코드가 필요했습니다.
Object obj = new Point(5, 7);
if (obj instanceof Point) {
Point p = (Point) obj;
int x = p.x();
int y = p.y();
System.out.println("x=" + x + ", y=" + y);
}
record가 복잡하거나 중첩되면 코드는 금세 언패킹이 난무하는 형태가 됩니다. 이런 인식 로직을 switch에서 쓰는 것도 예전에는 불편했습니다.
레코드 패턴은 record 클래스 값을 패턴 매칭 안에서 바로 풀어낼 수 있게 해 줍니다. instanceof에서도, switch에서도요. 코드가 더 짧고, 더 안전하며, 더 이해하기 쉬워집니다.
2. 레코드 패턴 문법: 가장 간단한 예
record Point(int x, int y) {}
Object obj = new Point(10, 20);
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
여기서 무슨 일이 일어나나요?
- obj instanceof Point(int x, int y) — 타입을 검사하면서 컴포넌트를 곧바로 x, y 변수에 언패킹합니다.
- x와 y 변수는 if 블록 내부에서만 유효합니다.
결과:
x=10, y=20
주의: obj가 Point가 아니거나 null이면 if 블록은 실행되지 않으며, 변수는 선언되지 않습니다.
3. switch에서의 레코드 패턴: 간결함과 안전성
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
Object shape = new Point(3, 4);
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
default -> System.out.println("Unknown shape!");
}
- case Point(int x, int y)에서 x, y에 바로 접근할 수 있습니다.
- case Circle(Point center, int r)에서는 center와 r을 사용할 수 있습니다.
결과:
Point: x=3, y=4
sealed 계층과 함께라면 switch는 포괄적(exhaustive)이 됩니다: 처리하지 않은 경우가 있으면 컴파일러가 경고합니다.
4. 중첩 레코드 패턴: 패턴 안의 패턴 언패킹
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Line(new Point(1, 2), new Point(3, 4));
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}
결과:
Line from (1,2) to (3,4)
트리, 그래프, 복잡한 구조를 다루나요? 중첩 패턴은 강력한 도구입니다.
5. 가드 식(when)을 곁들인 레코드 패턴
Object obj = new Point(0, 100);
if (obj instanceof Point(int x, int y) && x == 0) {
System.out.println("점이 Y축에 있음: y=" + y);
}
switch에서는 이렇게 when을 사용합니다:
switch (obj) {
case Point(int x, int y) when x == 0 -> System.out.println("Y축 위: y=" + y);
case Point(int x, int y) when y == 0 -> System.out.println("X축 위: x=" + x);
case Point(int x, int y) -> System.out.println("일반 점");
default -> System.out.println("점이 아님");
}
6. 실제 애플리케이션에서의 레코드 패턴 활용
실용적인 예로 기하 도형을 살펴봅시다.
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
switch (shape) {
case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
case Circle(Point(int x, int y), int radius) ->
System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
case Rectangle(Point(int x, int y), int width, int height) ->
System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
default -> System.out.println("Unknown shape");
}
}
호출 예:
printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));
결과:
Rectangle: topLeft=(5, 10), size=20x30
7. 레코드 패턴 사용 시 제약과 주의점
record 클래스에서만 동작. 레코드 패턴은 실제로 record 클래스인 객체에만 적용됩니다.
class NotARecord {
int a, b;
NotARecord(int a, int b) { this.a = a; this.b = b; }
}
// 오류! record가 아님
// if (obj instanceof NotARecord(int a, int b)) { ... }
구성요소의 개수와 타입이 일치해야 함. 패턴은 record 클래스의 컴포넌트와 일치해야 합니다.
record Pair(int a, String b) {}
// 오류: 타입이 일치하지 않음
// if (obj instanceof Pair(String a, int b)) { ... }
변수는 블록 내부에서만 유효. 패턴에서 선언된 이름은 매칭이 성공한 특정 if나 case의 본문에서만 보입니다.
8. 중첩 패턴: 트리 예제
sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}
record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
return switch (expr) {
case NumberExpr(int value) -> value;
case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
};
}
사용 예:
Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14
여기서는 NumberExpr(int value), PlusExpr(Expr left, Expr right), MinusExpr(Expr left, Expr right)를 곧바로 언패킹합니다 — 코드가 간결하고 표현력이 좋아집니다.
9. 표: record 패턴 유무에 따른 패턴 매칭 비교
| 방식 | 수동 캐스팅 | record 패턴을 이용한 Pattern Matching |
|---|---|---|
| 타입 검사 | |
|
| 캐스팅 | |
필요 없음 |
| 필드 접근 | |
|
| 중첩 언패킹 | 수동 코드가 많음 | 중첩 패턴 |
| switch에서의 사용 | 불편함/불가능 | 쉽고 간결함 |
10. 레코드 패턴 사용 시 흔한 실수
오류 1: record가 아닌 클래스에 record 패턴을 적용하려는 경우. 일반 클래스를 작성해 놓고 record처럼 “언패킹”하려 하면 컴파일러가 허용하지 않습니다. 레코드 패턴은 진짜 record 클래스에만 동작합니다.
오류 2: 컴포넌트의 개수나 타입이 맞지 않는 경우. 패턴은 record의 컴포넌트 시그니처와 완전히 일치해야 합니다. 예를 들어 record Pair(int a, String b)에 대해 Pair(int x, int y)처럼 쓸 수 없습니다.
오류 3: 패턴 변수들을 블록 밖에서 사용하려는 경우. 패턴에서 선언된 이름은 해당 if/case 블록 내부에서만 유효합니다. 블록 밖에서는 이런 변수들이 존재하지 않습니다.
오류 4: 오래된 JDK에서 레코드 패턴을 사용하려는 경우. 지원은 Java 21에서 도입되었습니다. Java 17 이하에서는 문법 오류가 발생합니다. JDK 버전과 IDE 지원을 확인하세요.
오류 5: 지나치게 깊은 중첩 패턴. 중첩은 강력하지만, 필요 이상으로 복잡하게 만들지 마세요. 과도한 중첩 구조는 가독성을 해치고 오류 가능성을 높입니다.
GO TO FULL VERSION