Java 16 Record class
1. record 키워드란?
Java 16에서 도입된 record는 불변 객체를 생성하기 위한 특별한 클래스 유형이다.
쉽게 말해, record를 사용하면 모든 필드가 자동으로 final로 선언되며, 객체의 상태를 변경할 수 없게 된다. 이는 데이터 전송 객체(DTO; Data Transfer Object)나 간단한 데이터 구조를 만들 때 매우 유용하다. record는 기본적으로 데이터의 저장과 접근을 간편하게 해주는 메서드를 자동으로 생성해 주기 때문에, 개발자는 반복적인 코드 작성을 줄일 수 있다.
예를 들어, 일반적인 Java 클래스에서는 필드를 정의하고, 생성자, 접근자(getter) 메서드, toString(), equals(), hashCode() 메서드를 수동으로 작성해야 했다. 하지만 record를 사용하면 이러한 작업이 자동으로 처리된다. 따라서 코드의 가독성이 높아지고, 유지보수도 쉬워진다.
2. record 키워드의 특징
record의 가장 큰 특징은 불변성이다. record로 선언된 클래스는 내부 필드가 final로 선언되기 때문에, 객체가 생성된 이후에는 필드 값을 변경할 수 없다. 이는 멀티스레드 환경에서 안전성을 높여주며, 데이터의 일관성을 유지하는 데 큰 도움이 된다.
또한, record는 자동으로 생성되는 메서드가 있다. 예를 들어, record를 정의하면 다음과 같은 메서드가 자동으로 생성된다:
종류 설정
생성자 | 모든 필드를 초기화하는 생성자가 자동으로 생성된다. |
Getter | 각 필드에 대한 접근자 메서드가 자동으로 생성된다. 예를 들어, name이라는 필드가 있다면 name()이라는 메서드가 생성된다. |
toString() | 객체의 내용을 문자열로 표현하는 toString() 메서드가 자동으로 생성된다. |
equals() | 객체의 동등성을 비교하는 equals() 메서드가 자동으로 생성된다. |
hashCode() | 해시 코드를 생성하는 메서드가 자동으로 생성된다. |
이러한 자동 생성 기능 덕분에 개발자는 코드의 중복을 줄이고, 더 간결한 코드를 작성할 수 있다.
3. record 클래스 사용 예제
이제 record를 실제로 어떻게 사용하는지 간단한 예제를 통해 살펴보자. 아래는 Person이라는 record를 정의하는 예제이다.
// Person.java
public record Person(String name, int age) {}
위의 예제에서 볼 수 있듯이, 선언이 아주 간결하다.
// Main.class
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
// 자동으로 생성된 getter 메서드 사용
System.out.println(person1.name()); // Alice
System.out.println(person1.age()); // 25
// 자동 생성된 toString() 메서드 사용
System.out.println(person1); // Person[name=Alice, age=25]
// 자동 생성된 equals()와 hashCode() 메서드 사용
System.out.println(person1.equals(person2)); // false
System.out.println(person1.hashCode() == person2.hashCode()); // false
}
}
위 예제에서 Person 객체는 record로 정의되었고, name(), age()와 같은 메서드는 자동으로 생성된다. 또한 toString(), equals(), hashCode() 메서드도 자동으로 생성되어 객체 비교와 출력이 가능하다. 또한 생성자를 오버라이딩하여 객체 생성 시 무결성 검사를 추가할 수 있다. 단, 일반적인 클래스와는 다르게 record는 선언부에서 이미 필드를 선언함과 동시에 매개변수를 받고 있어 생성자에서 괄호와 매개변수를 작성하지 않는다.
// Person.java
public record Person(String name, int age) {
// 생성자 오버라이딩 (무결성 검사)
public Person { // 매개변수 없음
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
생성자가 오버라이딩이 가능한 것처럼 자동으로 생성되는 toString(), equals(), hashCode() 메서드도 오버라이딩이 가능하다.
// Person.java
public record Person(String name, int age) {
// 생성자 오버라이딩 (무결성 검사)
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
// toString() 오버라이드
@Override
public String toString() {
return "Person[name=" + name.toUpperCase() + ", age=" + age + "]";
}
// equals() 오버라이드
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
// hashCode() 오버라이드
@Override
public int hashCode() {
return 31 * name.hashCode() + age;
}
}
4. 정리
✅ record는 불변 객체를 쉽게 생성 가능
✅ 자동으로 생성되는 메서드 덕분에 코드 가독성이 높아짐
✅ 데이터 전송 객체나 간단한 데이터 구조를 만들 때 유용
✅ 불변 객체이므로 멀티스레드 환경에서도 안전성 제공
✅ 코드 유지보수가 쉬워짐
❌ 복잡한 객체에는 적합하지 않을 수 있음
❌ 상속이 불가능하여 확장성이 제한됨
❌ 객체 초기화 과정에서 불편할 수 있음 (특히 필드가 많을 경우)