빌드노트
같은 값이 든 Java 배열인데 equals가 false다
같은 값의 별도 배열에서 equals가 실패하는 이유를 짧은 JShell 입력으로 확인한다. 일차원 int 배열은 Arrays.equals, 중첩 배열은 Arrays.deepEquals로 비교할 기준과 적용 범위를 설명한다.
역직렬화한 int[]와 기대 배열을 출력하면 둘 다 [1, 2, 3]인데 expected.equals(actual)가 거짓이라면, 비교식부터 확인한다. 배열의 equals는 같은 배열 객체인지 검사한다. 일차원 int[]의 원소를 비교하려면 Arrays.equals(expected, actual), 안쪽에도 배열이 있는 int[][]라면 Arrays.deepEquals(left, right)를 쓴다.
배열은 equals를 원소 비교로 재정의하지 않고 Object에서 상속한다. Java 21 언어 명세의 배열 멤버 (새 탭에서 열림)와 Object.equals의 계약 (새 탭에서 열림)에 따른 동작이다. 값이 같아도 따로 만든 두 배열이면 배열 자체의 equals는 거짓이다.
별도 배열과 같은 참조를 나란히 비교한다
JDK 17이 설치되어 있고 jshell을 PATH에서 실행할 수 있어야 한다. 터미널에서 버전을 확인하고, 식의 결과를 보여 주는 JShell 세션을 연다.
jshell --version
jshell --execution local --no-startup --feedback concise
다음 입력은 같은 값을 넣어 따로 만든 배열과, 같은 배열을 가리키는 alias를 비교한다. 주석은 Windows의 OpenJDK·JShell 17.0.2에서 실제로 확인한 반환값이다. JShell은 식의 결과를 $숫자 ==> 값 형태로 보여 주며 숫자는 입력 이력에 따라 달라진다.
import java.util.Arrays;
int[] expected = {1, 2, 3};
int[] actual = {1, 2, 3};
int[] alias = expected;
Arrays.toString(expected); // "[1, 2, 3]"
Arrays.toString(actual); // "[1, 2, 3]"
expected.equals(actual); // false
expected.equals(alias); // true
Arrays.equals(expected, actual); // true
문자열 표현은 같아도 별도 배열의 equals는 거짓이었다. alias와의 비교만 통과했다면 원소 비교가 올바르다는 증거로 삼을 수 없다. 기대 배열과 결과 배열을 따로 만든 조건으로 확인해야 한다. Arrays.equals의 int 배열 계약 (새 탭에서 열림)은 길이와 같은 위치의 값이 모두 같아야 참이라고 정한다. 순서도 비교에 포함된다.
중첩 배열에서는 안쪽 배열도 비교해야 한다
같은 JShell 세션에서 안쪽 행까지 따로 만든 두 배열을 비교한다.
int[][] left = {{1, 2}, {3}};
int[][] right = {{1, 2}, {3}};
left.equals(right); // false
Arrays.equals(left, right); // false
Arrays.deepEquals(left, right); // true
int[][]의 바깥 배열이 담은 원소는 int[] 참조다. Arrays.equals의 Object 배열 계약 (새 탭에서 열림)은 대응 원소의 equals 규칙을 따른다. 안쪽 int[]도 별도 객체이므로 여기서는 거짓이다. deepEquals는 배열을 따라 내려가 안쪽 원소까지 비교한다.
Arrays.deepEquals의 계약 (새 탭에서 열림)에서 매개변수는 Object[]다. int[][]는 전달할 수 있지만 일차원 int[]는 그대로 전달할 수 없다. 일차원 원시형 배열에는 해당 타입의 Arrays.equals를 사용한다. 일반 객체 원소는 그 객체의 equals에 따르므로 객체 필드나 컬렉션 속 배열까지 자동으로 펼쳐 비교하지 않는다. 자기 자신을 직접·간접적으로 담는 순환 배열의 동작은 정의되지 않는다.
비교식을 바꾼 뒤에도 거짓이면 비교 직전의 배열 타입, 길이, 원소 순서를 확인한다. 중첩 배열의 값은 Arrays.deepToString으로 살펴볼 수 있다. 문자열 출력은 진단에 쓰고, 테스트의 동등성 판단에는 배열에 맞는 비교 API를 쓴다.
이번 확인은 합성 int 배열 비교다. 실제 역직렬화와 특정 테스트 프레임워크는 실행하지 않았다. 중첩 배열은 2·3단계까지 직접 확인했으며 임의 깊이의 자원 안전성을 검증한 것은 아니다. Java 21은 공식 계약을 대조했고 실행한 런타임은 17.0.2다. JShell은 /exit로 종료한다.