Java 28 목표로 확정된 JEP 401, 값 객체가 바꾸는 == 의미

OpenJDK JEP 401이 Java 28을 목표로 갱신되며, 값 객체(Value Objects) 미리보기가 == 연산자의 실질적 의미를 어떻게 바꾸는지 정리했습니다.

Java 28 목표 JEP 401 값 객체 도입 배경과 핵심 변화, 적용 예시를 요약한 카드형 다이어그램
JEP 401이 Java 28을 목표로 확정되며 == 비교 방식이 정체성 비교에서 값 비교로 바뀌는 핵심을 요약했습니다.

OpenJDK의 JEP 401 "Value Objects (Preview)"가 7월 30일 업데이트를 거쳐 Java 28을 목표 릴리스로 확정됐다. 오랫동안 Project Valhalla라는 이름으로 진행돼 온 작업 중 하나로, Java 언어에서 객체를 다루는 가장 근본적인 규칙 중 하나를 바꾸는 제안이다.

Java에서 객체는 지금까지 예외 없이 고유한 정체성(identity)을 가졌다. 같은 값을 담고 있어도 서로 다른 인스턴스라면 별개의 객체로 취급된다. JEP 401은 이 규칙에 예외를 둔다. 필드 값만으로 구별되고, 정체성을 갖지 않는 "값 객체(value object)"라는 새로운 종류의 객체를 도입하겠다는 것이다.

같은 날짜인데 왜 ==는 false를 돌려줄까

JEP 문서는 `LocalDate`를 예로 든다. 1996년 1월 23일을 나타내는 `LocalDate` 객체(d1)를 만들고, 30년을 더했다가(d2) 다시 30년을 빼면(d3) 결과는 다시 1996년 1월 23일이 된다. `d1.equals(d3)`는 true를 돌려준다. 값으로 보면 둘은 같은 날짜다. 하지만 `d1 == d3`는 false다. 두 객체는 값이 같을 뿐, JVM 안에서는 서로 다른 고유 정체성을 가진 별개의 객체이기 때문이다.

이 문서는 이 현상을 "많은 개발자가 한 번쯤 겪어 본, `==`을 잘못 써서 당황했던 경험"이라고 표현한다. 날짜, 복소수, 색상 값처럼 변하지 않는 데이터를 다룰 때는 값이 같으면 사실상 같은 것으로 취급해도 무방한 경우가 대부분이다. 그런데 지금 Java의 모든 객체는 예외 없이 정체성을 갖도록 설계돼 있어서, 이런 불변 데이터에도 굳이 필요 없는 정체성 유지 비용이 따라붙는다.

일반 식별자 객체와 값 객체를 == 비교, 정체성 보유 여부, 대상 예시, JVM 최적화 여지 기준으로 비교한 표
OpenJDK 공식 JEP 401 문서에 실린 LocalDate 예시를 바탕으로 두 객체 방식을 비교한 표다.

가변 객체와 불변 객체는 애초에 다르게 다뤄야 한다

정체성이 필요한 이유는 원래 가변(mutable) 객체 때문이다. 텍스트 편집기에서 한 줄을 표현하는 객체가 있다고 하면, 사용자가 그 줄을 수정할 때 정확히 그 객체의 내부 상태를 바꿔야 하고, 다른 줄과 헷갈리면 안 된다. 이때는 정체성이 꼭 필요하다. 하지만 불변 데이터는 다르다. 상태가 절대 바뀌지 않는 두 객체가 같은 값을 담고 있다면, 사실상 서로 바꿔 써도 무방한 존재다. 굳이 "이 인스턴스와 저 인스턴스는 다르다"는 정체성을 유지할 이유가 없다.

가변 객체는 정체성 구별이 필요하고 불변 객체는 상호교환 가능함을 비교한 그림
JEP 401 문서가 드는 두 가지 예시(Line, LocalDate)를 기준으로, 정체성이 필요한 경우와 필요 없는 경우를 나눴다.

JEP 401은 이 구분을 언어 차원에서 명시적으로 지원하려 한다. 개발자가 클래스를 만들 때 값 객체로 쓸지 여부를 직접 선택하도록 하고, 기존 클래스를 값 객체로 호환성 있게 전환하는 경로도 제공한다. 문서에는 Java 플랫폼 API의 `Integer`, `LocalDate` 같은 클래스를 값 객체 인스턴스로 옮기는 것도 목표로 명시돼 있다.

모든 객체가 자동으로 값 객체가 되는 것은 아니다

JEP는 이 제안의 한계도 분명히 한다. 기존 클래스의 인스턴스를 자동으로 값 객체 취급하지 않는다. 값 객체는 다른 객체와 똑같이 동작하지 않기 때문에, 클래스를 작성하는 쪽에서 명시적으로 선택해야 한다. 또한 `==` 연산자를 `equals` 메서드 대신 쓸 수 있도록 의미를 바꾸는 것도 목표가 아니다. 대부분의 맥락에서 객체를 비교할 때는 여전히 `equals`를 쓰라는 일반적인 조언이 유효하다. `==`의 의미는 정체성이 없는 새로운 종류의 객체를 다루는 데 필요한 만큼만 재정의된다.

JEP 401이 명시한 네 가지 Non-Goals 항목을 정리한 목록
공식 문서의 Non-Goals 섹션을 그대로 정리했다. 자동 전환과 == 재정의는 이번 제안의 범위가 아니다.

이번 업데이트로 JEP 401은 Java 28을 타깃 릴리스로 확정했다. 아직 프리뷰 기능이라는 점은 변하지 않았다. 프리뷰는 정식 기능이 되기 전 단계로, API와 동작이 이후 릴리스에서 바뀔 수 있다. 지금 당장 실무 코드에 적용할 단계는 아니지만, `equals`와 `==`를 혼동해 본 적이 있는 Java 개발자라면 앞으로 몇 개 릴리스에 걸쳐 이 변화가 어떤 클래스에, 어떤 방식으로 적용되는지 지켜볼 만하다. Integer나 LocalDate처럼 널리 쓰이는 클래스가 값 객체로 바뀐다면, 그 여파는 특정 라이브러리 사용자에 그치지 않고 Java 생태계 전반에 미칠 수 있다.


출처: OpenJDK JEP 401: Value Objects (Preview)