본문 바로가기

CS/JAVA

[JAVA] Record

스프링 프로젝트를 진행하며 레이어 간 통신을 위해 DTO, VO 등의 클래스를 만들어 사용했다. 데이터의 무결성을 유지하기 위해서는 필드의 타입을 final로 설정하고, public 생성자를 정의하며, equals hashCode의 함수를 재정의하는 등의 작업이 필요했다. 이런 작업은 반복되고 번거로워서 코드의 일관성을 유지하는 데 어려움을 겪었다. 그러나 자바 14부터는 데이터의 불변성을 강제할 수 있는 Record라는 새로운 타입이 등장하게 되었다. Record는 간결한 문법을 통해 필드를 정의하고, 불변성과 관련된 메서드들을 자동으로 생성해 주어, 개발자가 불필요한 코드 작업을 줄이고 데이터를 쉽게 다룰 수 있도록 도와준다. 이번 블로그를 통해 Record 타입에 대해 자세히 알아보려고 한다.


Record란?

JAVA14에서 도입되었고 16 버전에서 정식 스펙으로 채택된 Record는 데이터를 표현하기 위한 새로운 타입으로, 불변성을 강제하고 간결한 코드 작성을 지원한다. 이전에는 DTO(Data Transfer Object)나 VO(Value Object)등을 정의할 때 번거로운 작업들이 많았지만, Record를 사용하면 이러한 작업들을 자동으로 처리할 수 있다.

JPA의 Entity클래스로는 사용할 수 없다. 프록시 생성을 위해 필드의 접근 제어자가 final이면 안되고, 매개변수가 없는 기본 생성자 또한 제공하지 않는다. 그리고 getter 메서드의 경우 필드명 그대로를 사용하고 있기 때문에 getter메서드에 접근할 수 없다. 

 

※ 불변 객체를 사용하는 이유 

불변성(immutable)이란 객체가 생성된 후 내부 상태가 일정하게 유지되는 상태를 말한다. 불변 객체는 내부 상태가 변하지 않기 때문에 thread-safe하고 부작용이 발생하지 않는다.

 

Record의 특징

  • record는 불변 객체로 필드 앞에 암시적으로 private, final로 선언
  • 멤버변수 선언 불가
  • 타 클래스는 상속 불가, 인터페이스 구현 가능
  • 각 필드는 getter 메서드 자동 생성(getter 메서드의 이름은 필드의 이름과 일치)
  • 객체의 모든 필드를 포함한 기본 생성자(AllArgsConstructor) 자동 생성
  • equals 메서드 자동 생성(객체의 타입이 일치하고, 모든 필드의 값이 일치하는 경우 true 반환)
  • hashCode 메서드 자동 생성
  • toString 메서드 자동 생성
  • static 변수와 메서드 생성가능

class 타입과 record 타입의 비교

class타입과 record 타입 각각으로 불변 클래스를 만들어 비교해보고자 한다. 각각의 코드를 비교해 보기 바란다.

class에서 불변성을 구현하려면?

인스턴스 내부의 데이터가 생성 시점 이후는 변경되지 않도록 불변 객체를 만들었다. 불변 인스턴스는 객체가 파괴되는 순간까지 절대 달라지지 않는다. 특징을 살펴보자

public class Post {
    private final Integer id;
    private final String title;
    private final String body;
    
	public Post(Integer id, String title, String body) {
		this.id = id;
		this.title = title;
		this.body = body;
	}

	public Integer getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}


	public String getBody() {
		return body;
	}

	@Override
	public int hashCode() {
		return Objects.hash(body, id, title);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Post other = (Post) obj;
		return Objects.equals(body, other.body) && Objects.equals(id, other.id) && Objects.equals(title, other.title);
	}

	@Override
	public String toString() {
		return "Post [id=" + id + ", title=" + title + ", body=" + body + "]";
	}

}

 

  • class에 속한 각 필드는 private, final로 정의
  • 모든 필드를 매개변수로 하는 public 생성자(AllArgsConstructor) 정의
  • 모든 필드의 getter메서드 정의
  • equals(), hashCode() 메서드 재정의
  • toString() 메서드 재정의

불변 클래스를 만들기 위해서는 위와 같은 작업들이 필요하다. 이러한 코드를 Boilerplate 코드라고 하는데, Boilerplate 코드란 주로 반복되는, 그 자체로는 비즈니스 로직이나 핵심 기능을 나타내지 않고, 프레임워크, 라이브러리, 언어 등의 특정 규약을 따르기 위한 코드를 말한다. 이러한 코드는 개발자에게 번거로움을 주며, 오류가 발생하기 쉽고, 필드가 변경될 시에 유지보수가 어렵다는 단점이 있다. 하지만 Boilerplate 코드를 작성하는 것이 단점만 있는 것은 아니니 특정한 요구사항을 따라야 하거나, 외부 라이브러리를 연동하는 등 상황에 맞게 사용하는 것이 중요하다. 

record로 구현하기

public record Post(Integer id, String title, String body) {

}

위 코드가 class로 구현한 클래스와 동일한 동작을 한다. 모든 필드는 private final의 접근제어자를 가지고 있고, getter메서드, equals(), hashCode(), toString() 메서드를 내장하고 있다.

  • 불변성 강제 : record 타입을 사용함으로써 데이터의 안정성을 확보하고, 예측 가능한 코드를 작성하는데 도움이 된다. 해당 클래스는 데이터 클래스임을 명시적으로 선언한 것으로 볼 수 있다.
  • 간결한 코드 : 개발자는 반복적인 작업을 줄이고, 가독성 높은 코드를 작성할 수 있다.
  • 자동 생성 메서드 : getter메서드, equals(), hashCode(), toString() 메서드들이 자동 생성되기 때문에 이는 객체 간의 동등성 비교 및 디버깅이나 로깅에 유용하다.

컴팩트 생성자(Compact Constructor)

record는 기본적으로 모든 필드를 초기화할 수 있는 생성자 하나만을 자동으로 생성한다. 그 이상의 작업을 하기 위해서는 사용자가 직접 생성자를 정의할 수도 있다. 컴팩트 생성자는 입력받은 데이터를 검증할 때 사용한다. 예를 들어 매개변수로 받은 데이터가 null 값인지, 또는 음수인지를 확인하여 에러를 반환할 수 있다.

public record Post(Integer id, String title, String body) {
    
    // Compact 생성자를 사용하여 객체 생성
    public Post {
        Objects.requireNonNull(id, "id must not be null");
        Objects.requireNonNull(title, "title must not be null");
        Objects.requireNonNull(body, "body must not be null");
    }
}

위의 예는 매개변수의 값이 null인지 체크하고, null일 경우 에러를 반환한다.

 

만약 별도의 다른 생성자를 필요로 하면 다음과 같이 추가하면 된다.

public record Person(String name, int age) {

    public Person(int age) {
        this.name = "Unknown";
        this.age = age;
    }
}

참고사이트(참고하면 좋은 사이트)

Java 14 Record Keyword

record VS lombok

 

[Java]자바 record를 entity로?

자바의 record를 JPA의 entity로 사용할 수 있을까요? record는 entity보다는 DTO에 보다 적합한 오브젝트입니다.

velog.io

 

[Java] Java14 레코드(Record)를 알아보자

Java 8 , 11 버전만 사용하다 보니 이후 버전에 추가된 것들에 대해서 학습을 하려고 합니다. Java 14에서 추가된 레코드(Record)에 대해서 레코드의 목적, 자동생성 항목 등 레코드의 기본 사항에 대해

colevelup.tistory.com

 

'CS > JAVA' 카테고리의 다른 글

[JAVA] JDK21의 Virtual Thread  (2) 2024.01.08
JAVA의 리플렉션 API  (0) 2023.01.08
[JAVA] String 그리고 StringBuffer와 StringBuilder  (0) 2022.08.30
Abstract Class와 Interface  (0) 2022.08.23
Java에서의 Hash  (0) 2022.08.16