java 21 - Record Patterns 소개
Java의 pattern matching 도입 이후 구문 단순화와 코드 가독성 및 안전성 향상을 목표로 점진적으로 발전해 왔다.
switch 표현식에서의 pattern matching, instanceof에 대한 pattern matching이 대표적이다.
이번 포스팅에서는 이전 버전의 preview 기능을 거쳐 java 21에서 정식 릴리즈된 Record Patterns에 대해서 소개하고자 한다.
이전에 다뤘던 pattern matching에 대해서는 아래 포스팅에 정리해 보았다.
2023.08.26 - [자바] - jdk pattern matching for switch
2023.08.26 - [자바] - jdk pattern matching for instanceof
더불어 Record 클래스 타입에 대한 소개는 아래 포스팅에 정리해 보았다.
2023.08.19 - [자바] - java record 용법 - from jdk 14
Record 패턴은 Record 클래스 유형의 인스턴스인지 테스트하고 (레코드 클래스 참조), 인스턴스인 경우 구성 요소 값에 대해서 재귀적으로 패턴 일치를 수행할 수 있게 한다.
참고로 Record 패턴의 도입 배경은 JEP440 에서 확인해 볼 수 있다.
일반 용법
아래 샘플 코드는 인자로 전달된 obj 인스턴스가 Point 레코드의 인스턴지 여부를 Record 패턴과 함께 테스트한다.
record Point(double x, double y) {
static void checkPattern(Object obj) {
if ( obj instanceof Point(double x, double y) ) {
System.out.println("x: " + x + ", y: " + y);
}
}
}
=====================================
Point point = new Point( 1.0, 2.0 );
Point.checkPattern( point );
======================================
출력
Point x: 1.0, y: 2.0
샘플코드에서 알 수 있듯이 Record 패턴을 통해서 if문 내부에서 obj 인스턴스에서 x, y 값을 직접 추출하여 Point레코드의 접근자 메서드를 자동으로 호출하게 된다.
Record 패턴은 타입과 Record 패턴 목록으로 구성된다. 위 예시 코드에서 타입은 Point이고 패턴 목록은 (double x, double y)다.
체크하는 인스턴스가 null인 경우 어떠한 레코드 패턴과도 일치하지 않는다.
다음과 같이 Record 패턴 대신에 타입 패턴을 사용할 수 있다.
static void checkType(Object obj) {
if ( obj instanceof Point p ) {
System.out.println("Point x: " + p.x + ", y: " + p.y);
}
}
타입 패턴을 사용하는 경우에는 Record 패턴과 달리 x, y를 직접 호출하여 access 해야 한다.
Generic Records
Record 클래스가 Generic인 경우 Record 패턴에서 타입 인수를 명시적으로 지정할 수 있다.
record ValueRecord<T>(T t) {
static void checkTypeAndPrintValue(ValueRecord<String> obj) {
if ( obj instanceof ValueRecord<String>(String s) ) {
System.out.println("String ValueRecord: " + s);
}
}
}
===========================================
ValueRecord<String> valueRecord = new ValueRecord<>( "generic record" );
ValueRecord.checkTypeAndPrintValue( valueRecord );
===========================================
출력
String ValueRecord: generic record
checkTypeAndPrintValue 메서드의 파라미터에 Generic 타입이 명시적으로 지정되지 않은 경우에는 type cast 관련 오류를 만날 수 있다.
static void notCheckTypeAndPrintValue(ValueRecord obj) {
// error : ValueRecord cannot be safely cast to ValueRecord<String>
if ( obj instanceof ValueRecord<String> v ) {
System.out.println("ValueRecord: " + v.t);
}
}
static void notCheckTypeAndPrintValue(ValueRecord obj) {
// no error
if ( obj instanceof ValueRecord<?> v ) {
System.out.println("ValueRecord: " + v.t);
}
}
타입 추론
Record 패턴의 구성 요소 목록에서 var를 사용할 수 있다.
static void checkPattern(Object obj) {
if ( obj instanceof Point(var x, var y) ) {
System.out.println("Point x: " + x + ", y: " + y);
}
else {
System.out.println("not Point record type");
}
}
위 코드에서 컴파일러는 변수 x, y는 double로 타입 추론을 한다.
컴파일러는 패턴을 허용하는 모든 구조체(switch 문, switch 표현식, instanceof 표현식)에서 레코드 패턴의 타입을 추론할 수 있다.
Nested Record Patterns
중첩된 Record 패턴을 사용할 수 있다.
record NestedRecord(Point p1, Point p2) {
static void checkPattern(Object obj) {
if ( obj instanceof NestedRecord(Point(var x, double y), var p2) ) {
System.out.println("Point1 x: " + x + ", y: " + y);
System.out.println("Point2 x: " + p2.x + ", y: " + p2.y);
}
else {
System.out.println("not Point record type");
}
}
}
========================================
Point p1 = new Point( 1.0, 2.0 );
Point p2 = new Point( 3.0, 4.0 );
NestedRecord nestedRecord = new NestedRecord( p1, p2 );
NestedRecord.checkPattern( nestedRecord );
========================================
출력
Point1 x: 1.0, y: 2.0
Point2 x: 3.0, y: 4.0
Record 패턴은 기존 패턴 일치 기능에 원활하게 통합되어 레코드 해체를 단순화할 뿐 아니라 코드 가독성과 유지 관리성을 향상한다.
끝.