티스토리 뷰

자바 14에 프리뷰로 등록되었고 자바 15에 2번째 프리뷰로 업데이트 되었다. 진행되는 흐름상 자바 16에는 정식 버전으로 추가될 예정이다. 이 포스팅에서는 자바 15 기준으로 설명할 것이며, 정식 버전과 호환되지 않을 수 있다. 실제로 과거 많은 프리뷰 버전이 개념상으로는 유사하지만 패키지명, 클래스명, 메소드명 및 사용방법에 다소 차이가 있어서 오류가 날 수 있으니 이러한 점을 고려해야 한다.

 

이 포스팅은 https://openjdk.java.net/jeps/384 를 참고하였다.

 

1. 기술적 동기

다음과 같은 클래스가 있다고 가정하자.

import java.util.Objects;

public 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);
    }
}

위의 예제는 특정한 위치 정보를 표현하기 위한 Point 클래스로 내부적으로 x, y 값을 가지며 이 값을 설정하기 위한 생성자와 값을 리턴 받기 위한 메소드들이 정의되어 있다. 그리고 동일 여부를 식별하기 위한 equals, hashCode를 오버라이드 하였고 문자열로 표현하기 위한 toString 메소드 역시 오버라이드 하였다. 만일 위의 코드에서 값의 정렬이 필요하다면 추가로 compareTo 메소드를 구현해야 한다.

 

정작 필요한 것은 x, y 두개의 값 뿐이지만 이를 위해 모든 작업을 개발자가 직접 코딩해야 하며 이러한 작업은 계속적으로 반복된다. 물론 최근 이클립스나 IntelliJ IDE 같은 통합 개발도구 들이 발달하면서 많은 부분 개발 도구가 작성을 도와주지만 완벽히 대체할 수 없다.

또한 개발자가 실수로 equals 메소드를 오버라이드 하지 않을 경우 데이터 비교시 오동작 할 가능성이 높고, 설령 작성하였더라도 클래스 내부에 관리해야 하는 데이터가 추가되거나 할 때 이를 equals 등에 적용하지 않으면 원인을 파악하기 어려운 문제에 직면할 수도 있다.

 

2. Records 정의

Records는 자바 언어적으로 새롭게 소개되는 클래스의 일종이다. 클래스, 인터페이스, 열거형과 동일한 레벨로 Records가 취급된다. Records의 목적은 클래스 내부에 선언되어 있는 소규모의 변수들을 정의하기 위한 새로운 방법을 제시하는 것이며 이것을 통해서 앞서 설명한 개발자의 반복작업을 언어적으로 줄여주기 위함이다.

Records에서 정의한 데이터는 불변성을 가진다. 즉, 처음 값을 설정한 이후 조회만 할 수 있고 변경은 불가능하며 이를 통해 멀티쓰레드 환경에서도 안전하게 사용할 수 있도록 하였다. 

 

3. Records의 목표

3-1. 목표
- 객체 지향의 사상에 맞게 데이터를 간결하게 표현하기 위한 방법을 제공
- 개발자가 동작을 확장하는 것보다 불변데이터를 모델링하는데 집중하도록 도와준다.
- 데이터 지향 메소드들을 자동으로 구현하도록 해준다.
- 오랫동안 유지되고 있는 자바의 사상과 호환되도록 한다.

3-2. 목표 아닌 것
- 자바빈즈의 명명 규칙을 사용하는 변경가능한 클래스들의 문제점을 해결하기 위한 것은 아니다 -> 자바빈즈를 대체하기 위한 기술은 아니다.
- 속성 혹은 어노테이션 지향적인 코드를 생성하기 위한 기능은 아니다. 

 

4. Records 설명

Records는 변수들을 모아 놓은 작은 그룹을 의미하며 앞에서 설명했듯이 불변성을 특징을 가진다. 그리고 해당 변수 그룹들의 상태들을 표현하기 위한 API 들을 개발자의 코딩 없이 자동으로 제공한다.

public record NewPoint(int x, int y) {
	
	public static void main(String[] args) {
		var record1 = new NewPoint(10, 10);
		var record2 = new NewPoint(10, 10);
		
		System.out.println(record1);
		// x와 y 메소드가 자동으로 생성됨
		System.out.println(record1.x());
		System.out.println(record1.y());
		// equals 메소드가 자동으로 구현됨
		System.out.println(record1.equals(record2));
	}
}

위의 코드는 main 문장을 제외하면 public record NewPoint(int x, int y) {} 로 정의할 수 있다. Records는 크게 이름, 헤더, 바디로 구분되는데 위의 코드에서 이름은 Records이고 헤더는 int x, int y이다. 그리고 바디는 별도로 구현되지 않았다.

이렇게 정의한 것만으로도 내부적인 속성과 값을 할당하고 해당 상태에 접근할 수 있는 메소드가 자동으로 생성된다. 뿐만 아니라 equals 메소드도 자동으로 구현되어서 개발자의 코딩 없이도 객체의 동일 여부를 판단할 수 있다.

특히 흥미로운 것은 자바의 호환성을 유지하기 위해서 x()와 y() 메소드가 외부에 노출된 것이다. 이클립스를 이용해서 보면 아래 그림과 같이 메소드가 추가된 것으로 보인다.

 

이를 통해 Records의 기능을 유추할 수 있는 것을 정리하면 다음과 같다. 

 

- 헤더에 정의한 Records 정보를 기반으로 생성자가 내부에 선언되었다.

- 헤더에 정의한 변수가 private final 로 내부에 선언되었다.

- 변수명과 동일하게 상태를 조회할 수 있는 메소드가 내부에 선언되었다.

- 변수명을 기준으로 equals와 hashCode 메소드가 구현되었다.

- 포함하고 있는 모든 변수 정보를 출력할 수 있는 toString 메소드가 구현되었다.

 

결론적으로 클래스의 구성 요소인, 생성자 / 메소드 / 변수가 자동으로 생성된 것을 알 수 있다.

 

5. Records 제약 사항

Record의 헤더 정보를 기반으로 자동 생성되는 것 외에 개발자가 직접 추가적인 제어를 하고 싶다면 기존 클래스를 작성하는 것과 동일하게 코딩을 하면 된다. 

코딩을 할 때 몇가지 제약 사항이 있으며 그 내용을 정리하면 다음과 같다.

 

  • 레코드는 다른 클래스를 상속 받을 수 없다. 즉 extends 선언을 할 수 없으며 내부적으로는 java.lang.Record를 상속한 것이다. enum이 java.lang.Enum을 상속한 것과 동일한 개념이다.
  • 레코드는 암묵적으로 final 클래스이며 abstract로 선언할 수 없다. 
  • 레코드 내부에 멤버 변수를 선언할 수 없다. 단, static 변수는 생성 가능하다. 이 제약은 헤더에 정의한 멤버만을 레코드에서 관리하기 위한 것이다.
  • 잘 사용하진 않겠지만 레코드 내부에 native 메소드를 선언할 수 없다.

이 제약 외에는 전통적으로 자바의 클래스 개발과 동일하게 사용할 수 있으며 그 내용을 정리하면 다음과 같다.

 

  • new 키워드를 이용해서 객체화 할 수 있다.
  • static 메서드, static 필드를 선언할 수 있다.
  • 일반 클래스외에 중첩 클래스를 사용할 수 있으며 제네릭의 타입으로 지정할 수 있다.
  • 클래스를 상속할 수는 없지만 인터페이스를 구현(implements)할 수는 있다.

생성자 역시 내부적으로 선언되지만, 필요에 따라 직접 선언해서 사용할 수 있다. 주로 데이터 정합성을 체크하는 용도로 활용 가능하며 예제는 다음과 같다.

 

public record NewPoint(int x, int y) {
	public NewPoint(int x, int y) {
		if(x < 0 || y < 0) {
			throw new java.lang.IllegalArgumentException(String.format("Greater than 0 : %d, %d", x, y));
		}
		
		// 이 코드를 넣지 않으면 컴파일 에러 발생
		this.x = x;
		this.y = y;
	}
}

 

위의 코드에서 주의할 점은 반드시 this.x = x와 this.y = y 처럼 값을 할당해 줘야 한다. Record는 private final과 동일하기 때문에 객체가 생성되는 시점에 값을 할당해야하기 때문이다.

 

지금까지 살펴본 것처럼 Record는 비교적 쉽게 사용할 수 있으며 사용상 제약 조건만 익숙해 지면 큰 어려움 없이 실제 프로그래밍에 반영할 수 있을 것으로보인다.

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

2021.03.16 자바 16에 Records가 정식으로 추가되었다. 이제 프리뷰나 인큐베이터 옵션을 이용해서 사용할 필요 없이 정식으로 사용이 가능하다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함