자바

java record 용법 - from jdk 14

알쓸개잡 2023. 8. 19.

jdk 14 에서 새로운 타입 으로 record 가 preview 형태로 도입되었습니다. Enum 타입과 마찬가지로 record 타입은 몇가지 제약사항을 가지고 있으며 불변의 데이터 셋을 정의하는데 상당한 간소함을 제공합니다.

JEP 359 에 기술된 내용을 보면 record 의 도입배경과 목적 외에 여러 관련 사항들을 확인해 볼 수 있습니다. 

이번 포스팅에서는 record의 제약사항 및 용법에 대해서 알아보겠습니다.

 

record 의 도입배경과 목적

불변의 데이터 셋 (data carrier) 역할을 하는 클래스의 경우에도 생성자, 접근자, equals(), hashCode(), toString() 등 반복적인 코드를 많이 작성해야 하는 불편함과 함께 record 는 불변하는 데이터 집합을 보다 쉽고, 명확하고, 간결하게 하기 위한 목적이 있습니다. 

Lombok 과 같이 데이터를 간소화 할 수 있는 annotation 을 정의하면 코드를 간소화 할 수는 있지만 해당 클래스가 불변의 데이터 셋 역할을 한다는 설계 의도를 명확하게 파악할 수는 없습니다. record 의 사용은 불변의 데이터 셋을 나타내는 명확한 설계 의도를 나타낼 수 있습니다.

 

record 제약사항

  • 다른 클래스를 확장할 수 없습니다.
  • private final 필드 이외의 인스턴스 필드를 선언할 수 없습니다.
  • private final 필드가 아닌 다른 모든 필드는 static 이어야 합니다.
  • record 는 abstract 가 될 수 없으며 암묵적으로 final 입니다. (다른 클래스를 확장할 수 없음과 연결됩니다.)
  • record 의 구성요소는 암묵적으로 final 입니다.

 

record 일반사항

  • class 내에 선언할 수 있으며 중첩된 record 는 암묵적으로 static 입니다.
  • record 는 inteface 를 구현할 수 있습니다.
  • 일반 class 와 마찬가지로 new 키워드로 record 를 인스턴스화 합니다.
  • record 본문에서 static method, static fields, static initializers, constructors, instance method 및 중첩된 유형을 선언할 수 있습니다.
  • record 및 record 개별 구성 요소에 annotation 을 붙일 수 있습니다.

 

record 기본 용법

package com.example.test.record;

import lombok.Builder;

import java.util.UUID;

final public class Student {

	private final UUID uuid;
	private final String name;
	private final String grade;

	@Builder
	public Student(UUID uuid, String name, String grade) {
		this.uuid = uuid;
		this.name = name;
		this.grade = grade;
	}

	public UUID uuid() {
		return uuid;
	}

	public String name() {
		return name;
	}

	public String grade() {
		return grade;
	}
}

Student class 는 uuid(), name(), grade() 3개의 getter 메소드와 모든 요소를 파라미터로 가지는 생성자로 구성되어 있습니다.

위 클래스를 record 를 사용하면 아래와 같이 선언할 수 있습니다.

package com.example.test.record;

import lombok.Builder;

import java.util.UUID;

@Builder
public record Student(UUID uuid, String name, String grade) {}

record 는 아래의 내용을 자동으로 처리한다.

  • 각 구성요소를 private final 필드로 처리합니다.
  • 각 구성요소의 이름과 동일한 public getter 메소드를 제공합니다. uuid(), name(), grade()
  • 모든 구성요소를 포함하는 public 생성자를 제공합니다.
  • 모든 구성 요소에 대한 equals(), hashCode(), toString() 를 생성합니다.

equals(), hashCode(), toString() 에 대한 내용은 아래 테스트 코드를 통해서 검증이 가능합니다.

@Test
void record_test() {
    UUID uuid = UUID.randomUUID();
    String name = "student1";
    String grade = "A";

    Student record = Student.builder()
        .uuid(uuid)
        .name(name)
        .grade(grade)
        .build();

    Student record2 = Student.builder()
        .uuid(uuid)
        .name(name)
        .grade(grade)
        .build();

    Assertions.assertThat(record.hashCode()).isEqualTo(record2.hashCode());
    Assertions.assertThat(record.toString()).isEqualTo(record2.toString());
    Assertions.assertThat(record.equals(record2)).isTrue();
}

 

Compact Constructors

record 에서 자동 생성한 생성자가 각 필드를 초기화 하는 것 이상의 작업을 수행하도록 하려면 record 에 대한 사용자 지정 생성자를 정의 할 수 있습니다. 이 생성자 정의를 Compact Constructor 라고 하며 argument 정의가 없습니다.

public record Student(UUID uuid, String name, String grade) {
	public Student {
		Objects.requireNonNull(uuid);
		Objects.requireNonNull(name);
		Objects.requireNonNull(grade);
	}
}

또한 record 의 모든 필드에 대한 public 생성자 이외에 생성자 구현을 사용자 정의 할 수 있습니다.

 

 

public record Student(UUID uuid, String name, String grade) {
	public Student(String uuid) {
		this(uuid, "unknown", "unknown");
	}
}

record 가 생성한 public 생성자와 동일한 argument 를 사용하여 생성자를 생성할 수 있지만, 이를 위해서는 각 필드를 수동으로 초기화 해야 합니다.

 

public record Student(UUID uuid, String name, String grade) {
	public Student(UUID uuid, String name, String grade) {
		this.uuid = uuid;
		this.name = name;
		this.grade = grade;
	}
}

 

Compact 생성자와 모든 argument 목록을 정의한 생성자가 함께 정의된 경우에는 compile error 가 발생합니다.

//compile error!!!
public record Student(UUID uuid, String name, String grade) {
	public Student {
		Objects.requireNonNull(uuid);
		Objects.requireNonNull(name);
		Objects.requireNonNull(grade);
	}
    
    public Student(UUID uuid, String name, String grade) {
    	this.uuid = uuid;
        this.name = name;
        this.grade = grade;
    }
}

 

Static 변수와 메소드

private final 필드 이외의 필드는 static 필드여야 하고, record 는 static 메소드를 가질 수 있습니다.

package com.example.test.record;

import lombok.Builder;

import java.util.UUID;

@Builder
public record Student(UUID uuid, String name, String grade) {
	public static String UNKNOWN_NAME = "unknown";
    
    public static Student unknownStudent(UUID uuid, String grade) {
    	return Student.builder()
			.uuid(uuid)
			.name(UNKNOWN_NAME)
			.grade(grade)
			.build;
    }
}

 

@Test
void record_static_method_test() {
    UUID uuid = UUID.randomUUID();
    String grade = "A";
    StudentRecord unknownStudent = StudentRecord.unknownStudent(uuid, grade);
    Assertions.assertThat(unknownStudent.name()).isEqualTo(StudentRecord.UNKNOWN_NAME);
}

 

지금까지 java record 에 대해서 알아 보았습니다. 도움이 되었기를 바랍니다.

'자바' 카테고리의 다른 글

jdk pattern matching for instanceof  (0) 2023.08.26
java switch expression - from jdk 14  (0) 2023.08.20
java Array vs ArrayList  (0) 2023.08.19
CompletableFuture 를 알아보자  (0) 2023.08.06
byte 배열에서 charset 정보 detecting 하기  (0) 2023.08.04

댓글

💲 추천 글