등장 배경
자바 코드는 쓸데 없이 장황하다! ⇒ 바꿔 보자!
자바에서 좌표 평면 상의 위치를 표현하기 위한 클래스인 Point 클래스를 작성한다고 해보자. 당연히 내부에 x 와 y 좌표를 나타내는 필드 변수가 있을 것이다. 또한, 올바른 동등 비교를 위해서는 equals와 hashCode도 재정의해야 한다. 그리고 단순히 좌표 값들을 전달하는 용도로 Point를 사용한다면 getter도 정의해야 한다. 따라서, 아래와 같은 코드가 작성된다.
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
Java
복사
이런 코드에는 몇가지 문제가 있다.
1.
개발자가 이들을 재정의 하는 것을 까먹을 수 있다.
2.
IDE 를 이용해 이런 반복적인 코드를 쉽게 생성할 수 있지만, 그것이 Point 가 단순한 값의 전달 용 클래스 인 것을 알기 쉽게 해주지 않는다.
이런 문제를 “간결한 코드”로 해결 하기 위해 Record가 추가되었다.
Record를 사용해야 하는 경우
Record 는 단순한 값의 집합을 표현하는 클래스를 작성하는데 사용하는 것이 좋다.
문법
record([매개변수 타입] 매개변수 명, ...) {
}
Java
복사
예를 들어 앞선 Point 클래스를 Record로 변경한다면 다음과 같은 코드로 작성할 수 있다.
record Point(int x, int y) {
}
Java
복사
위와 같이 작성하면, 아래의 코드와 동일하게 동작한다.
import java.util.Objects;
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() {
return x;
}
public int y() {
return y;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Point) obj;
return this.x == that.x &&
this.y == that.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point[" +
"x=" + x + ", " +
"y=" + y + ']';
}
}
Java
복사
Record 에 생성자 추가하기
일반적인 클래스와 다르게, Record에 생성자를 추가하지 않는다면, 선언부에 적은 모든 필드를 매개변수로 하는 생성자가 자동으로 추가된다.
VO를 생성할 때, 값에 대한 검증을 하는 경우가 있다. 이를 간편하게 하기 위해 compact 생성자를 추가할 수 있다.
public record Point(int x, int y) {
public Point{
if(x < 0) {
throw new RuntimeException();
}
if(y < 0) {
throw new RuntimeException();
}
}
}
Java
복사
이렇게 아무런 매개변수 목록을 제공하지 않고, 바로 { 가 나와야 한다. 또한, 클래스의 접근 지정자 보다 더 큰 범위의 지정자를 사용해야 한다. 내부에서, 필드에 접근할 수는 없다. 위 코드는 아래 코드와 동일하게 동작한다.
public record Point(int x, int y) {
public Point(int x, int y) {
if(x < 0) {
throw new RuntimeException();
}
if(y < 0) {
throw new RuntimeException();
}
this.x = x;
this.y = y;
}
}
Java
복사
컴팩트한 생성자는 객체 생성 시점에서 필요한 유효성 검사를 하기에 적절하다.
기타 다른 클래스와 다른 점
Record는 다른 클래스를 상속할 수 없다.
Record는 추상클래스일 수 없다. 다른 클래스가 Record를 상속할 수 없다.
레코드는 그 정의 자체로 완전히 정의되어야 함을 의미한다.
Record의 필드는 final 이다.
일반적인 VO의 특성을 만족하기 위해서다.
Record의 본문에 인스턴스 필드를 추가할 수 없다.
레코드 선언부만으로 상태를 알 수 있어야 한다.
Record에 Native 메서드가 추가될 수 없다.
네이티브 메서드는 동작이 외부의 상태로 동작된다는 것을 의미하기 때문에, 존재할 수 없다.