기본 패턴
java
// record — 한 줄로 불변 값 객체
record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3 — 접근자 자동 생성
System.out.println(p); // Point[x=3, y=4] — toString 자동
// instanceof 패턴 매칭 — 타입 검사 + 변수 선언 한 번에
Object obj = "hello";
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // cast 없이 바로 사용
}설명
record — 보일러플레이트 없는 불변 값 객체
record는 데이터를 담기 위한 클래스를 만들 때 생성자, 접근자, equals, hashCode, toString을 자동으로 생성합니다. DTO, 좌표, 범위처럼 "데이터 묶음"이 목적인 클래스에서 반복적인 코드를 없애줍니다.
record의 필드는 모두 final이므로 생성 이후 변경할 수 없습니다. 불변이기 때문에 다중 스레드 환경에서 안전하게 공유할 수 있습니다.
java
// ❌ 전통 방식 — 보일러플레이트 반복
class Point {
private final int x;
private final int y;
Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
@Override public boolean equals(Object o) { ... }
@Override public int hashCode() { ... }
@Override public String toString() { ... }
}
// ✅ record — 동일한 기능을 한 줄로
record Point(int x, int y) {}
// 컴팩트 생성자로 유효성 검사 추가 가능
record Range(int min, int max) {
Range {
if (min > max) throw new IllegalArgumentException("min > max");
}
}instanceof 패턴 매칭 — 타입 검사와 cast를 한 번에
Java 16 이전에는 instanceof로 타입을 확인한 뒤 별도로 cast해야 했습니다. 패턴 매칭은 타입 검사와 변수 선언을 한 문장으로 합칩니다. if 블록 안에서 cast 없이 바로 타입별 메서드를 쓸 수 있습니다.
java
// ❌ 전통 방식 — instanceof + 별도 cast
Object obj = getShape();
if (obj instanceof Circle) {
Circle c = (Circle) obj; // 이미 확인했는데 또 cast
System.out.println(c.radius());
}
// ✅ 패턴 매칭 — 한 번에 처리
if (obj instanceof Circle c) {
System.out.println(c.radius()); // c는 Circle 타입으로 바로 사용
}
// switch 패턴 매칭 (Java 21+) — 타입별 분기
String describe = switch (obj) {
case Circle c -> "원, 반지름 " + c.radius();
case Rectangle r -> "사각형, 넓이 " + r.area();
case null -> "null";
default -> "알 수 없는 도형";
};record의 제약 — 상속 불가, 불변만
record는 암묵적으로 java.lang.Record를 상속하므로 다른 클래스를 extends할 수 없습니다. 또 모든 필드가 final이므로 가변 상태를 담을 수 없습니다. 이 두 제약이 불편하면 일반 클래스를 사용해야 합니다.
java
// ❌ record는 상속 불가
record Child(int value) extends Parent {} // 컴파일 오류
// ❌ record는 가변 필드 불가
record Counter(int count) {
void increment() { this.count++; } // 컴파일 오류 — final
}
// ✅ 가변 상태가 필요하면 일반 클래스
class Counter {
private int count;
void increment() { count++; }
}
// ✅ record는 인터페이스 구현 가능
record Point(int x, int y) implements Comparable<Point> {
@Override
public int compareTo(Point other) {
return Integer.compare(this.x, other.x);
}
}빠른 정리
| 상황 | 선택 |
|---|---|
| 불변 데이터 묶음 (DTO, 값 객체) | record |
| 가변 상태 필요 | 일반 class |
| 상속이 필요 | 일반 class |
| 타입 검사 후 해당 타입 메서드 사용 | instanceof 패턴 매칭 |
| 여러 타입 분기 | switch 패턴 매칭 (Java 21+) |
주의할 점
record의 접근자는 필드 이름과 동일한 메서드입니다. getX()가 아니라 x()입니다. JavaBeans 규약(getX())을 기대하는 프레임워크(일부 직렬화 라이브러리, 구버전 Spring 등)에서 호환 문제가 생길 수 있습니다.
java
record User(String name, int age) {}
User u = new User("Kim", 30);
// ✅ record 접근자 — 필드명 그대로
String name = u.name(); // "Kim"
int age = u.age(); // 30
// ❌ JavaBeans 스타일은 자동 생성되지 않음
u.getName(); // 컴파일 오류 — 메서드 없음참고 링크
2 sources