Kotlin에서 Java 레코드 사용하기

Kotlin에서 Java 레코드 사용하기 (Using Java records in Kotlin)

레코드(record)는 Java에서 불변 데이터를 저장하기 위한 클래스예요. 레코드는 고정된 값 집합, 즉 레코드 컴포넌트(record component)를 담고 있고, Java에서 간결한 문법을 제공해서 반복적인 코드(boilerplate)를 줄여줍니다.

출처: Using Java records in Kotlin

본문

레코드는 Java에서 불변 데이터를 저장하기 위한 클래스예요. 레코드는 고정된 값 집합, 즉 레코드 컴포넌트(record component)를 가집니다. Java에서 문법이 간결해서 반복 코드를 일일이 작성하지 않아도 되죠.

// Java
public record Person (String name, int age) {}

컴파일러는 java.lang.Record를 상속한 final 클래스를 자동으로 생성해요. 이 클래스에는 다음 멤버들이 포함됩니다.

  • 각 레코드 컴포넌트에 대한 private final 필드
  • 모든 필드를 파라미터로 받는 public 생성자
  • 구조적 동등성(structural equality)을 구현하는 메서드들: equals(), hashCode(), toString()
  • 각 레코드 컴포넌트를 읽기 위한 public 메서드

레코드는 Kotlin의 데이터 클래스와 아주 비슷해요.

Kotlin 코드에서 Java 레코드 사용하기

Java로 선언된 컴포넌트를 가진 레코드 클래스는, Kotlin에서 프로퍼티를 가진 클래스를 사용하듯 그대로 쓸 수 있어요. 레코드 컴포넌트에 접근할 때는 Kotlin 프로퍼티처럼 이름만 쓰면 됩니다.

val newPerson = Person("Kotlin", 10)
val firstName = newPerson.name

Kotlin에서 레코드 선언하기

Kotlin은 데이터 클래스에 대해서만 레코드 선언을 지원하며, 그 데이터 클래스는 요구 사항을 충족해야 해요.

Kotlin에서 레코드 클래스를 선언하려면 @JvmRecord 애너테이션을 사용합니다.

기존 클래스에 @JvmRecord를 적용하는 것은 바이너리 호환 변경이 아니에요. 클래스 프로퍼티 접근자(accessor)의 명명 규칙을 바꾸기 때문입니다.

@JvmRecord
data class Person(val name: String, val age: Int)

이 JVM 전용 애너테이션은 다음을 생성하도록 해 줍니다.

  • 클래스 파일에서 클래스 프로퍼티에 해당하는 레코드 컴포넌트
  • Java 레코드 명명 규칙에 따라 이름이 붙여진 프로퍼티 접근자 메서드

데이터 클래스는 equals(), hashCode(), toString() 메서드 구현을 제공해요.

요구 사항 (Requirements)

@JvmRecord 애너테이션으로 데이터 클래스를 선언하려면 다음 요구 사항을 충족해야 합니다.

  • 클래스가 JVM 16 바이트코드를 타깃으로 하는 모듈에 있어야 해요. (-Xjvm-enable-preview 컴파일러 옵션이 활성화된 경우 15도 가능해요.)
  • 클래스는 다른 어떤 클래스도(그중 Any도) 명시적으로 상속할 수 없어요. 모든 JVM 레코드가 암시적으로 java.lang.Record를 상속하기 때문이에요. 단, 인터페이스는 구현할 수 있습니다.
  • 클래스는 backing field를 가진 프로퍼티를 선언할 수 없어요. 단, 해당 primary 생성자 파라미터로 초기화되는 프로퍼티는 예외입니다.
  • 클래스는 backing field를 가진 가변(mutable) 프로퍼티를 선언할 수 없어요.
  • 클래스는 로컬(local) 클래스일 수 없어요.
  • 클래스의 primary 생성자는 클래스 자신만큼 가시성이 있어야 해요.

JVM 레코드 활성화하기

JVM 레코드를 사용하려면 생성되는 JVM 바이트코드의 타깃 버전이 16 이상이어야 해요.

명시적으로 지정하려면 Gradle 또는 Maven에서 jvmTarget 컴파일러 옵션을 사용하세요.

Kotlin에서 레코드 컴포넌트에 애너테이션 달기

Java에서는 레코드 컴포넌트에 붙은 애너테이션이 backing field, getter, setter, 생성자 파라미터로 자동 전파돼요. Kotlin에서도 all use-site 타깃을 사용해 이 동작을 그대로 재현할 수 있습니다.

예를 들어:

@JvmRecord
data class Person(val name: String, @all:Positive val age: Int)

@JvmRecord와 함께 @all:을 사용하면 Kotlin은 이렇게 동작해요.

  • 프로퍼티, backing field, 생성자 파라미터, getter, setter로 애너테이션을 전파해요.
  • 애너테이션이 Java의 RECORD_COMPONENT를 지원한다면 레코드 컴포넌트에도 애너테이션을 적용해요.

애너테이션이 레코드 컴포넌트에서 동작하게 만들기

애너테이션이 Kotlin 프로퍼티와 Java 레코드 컴포넌트 양쪽에서 모두 사용 가능하도록 하려면, 애너테이션 선언에 다음 메타 애너테이션을 추가하세요.

예를 들어:

@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@java.lang.annotation.Target(ElementType.CLASS, ElementType.RECORD_COMPONENT)
annotation class ExampleClass

이제 @ExampleClass를 Kotlin 클래스와 프로퍼티, 그리고 Java 클래스와 레코드 컴포넌트에 모두 적용할 수 있어요.

더 알아보기 (Learn more)

JVM 레코드에 대한 더 자세한 기술 내용과 논의는 이 JVM 레코드 언어 제안 문서를 참고하세요.