자바에서 Array 둘을 비교할 때에는 어떻게 해야 할까요? 카톡방에 올라온 질문이였습니다. 이에 대한 제 대답은 Arrays.deepEquals를 써라였습니다. 이 글에서도 잠깐 언급을 한 적이 있긴 했습니다만, 질문을 받다 보니, Arrays.equals라던지 equals랑 혼동되는 경우가 있는 듯 하였습니다. 예제 프로그램 하나를 보면서 이해해 보도록 합시다.

 


 2차원 배열이 있습니다. 이들은 각각 2x2짜리 배열입니다. 그리고, 같은 내용물을 담고 있습니다. 첫 번째는 Arrays의 deepEquals, 또 다른 하나는 그냥 Arrays의 equals, 다른 하나는 그냥 equals였습니다. 실행 결과는 어떻게 나왔을까요?

 

 

 결과는 true, false, false가 나옵니다. 어떻게 된 일인지, 8번째 줄부터 보도록 하겠습니다.

 

 

 디버거를 돌려보면, 8번째 줄은 그냥 Object의 equals를 호출합니다. 여기서 this는 arr, obj로 넘어온 것은 brr을 의미합니다. 즉, 그냥 equals를 호출하면 arr과 brr의 주솟값이 같은지를 비교합니다. 그렇다면 7번째 줄은 어떨까요?

 

 

 내부 소스를 보면, 두 개의 배열을 비교하기는 합니다. 중요한 것은, 밑에 부분입니다.

 

 

 각각의 배열 요소들을 비교하는데요. 비교를 할 때 들어가는 로직이 2829번째 줄에 있습니다. 여기서 o1.equals(o2)가 나오는데요. 저는 2차원 배열을 받았습니다. 이들은 배열을 요소로 받습니다. 참조 값입니다. 이를 그림으로 나타내면 아래와 같습니다.

 

 

 위 그림은 arr과 brr의 메모리 레이아웃을 나타냅니다. arr[0]과 brr[0]이 참조하는 곳이 다릅니다. 그렇기 때문에, o1.equals(o2)가 false가 되고, 첫 번째 원소를 비교했을 때 둘이 같지 않았기 때문에, 다르다고 리턴해 버립니다. 이는 디버거로 뜯어봐도 확인할 수 있는 부분입니다.

 

 

 @494와 @496을 주목해 보세요. 그리고 호출 스택을 보세요. 중요한 것은, 원소들 각각을 비교하기 위해서, Object의 equals를 호출했다는 점입니다. 그래서 false가 출력되었습니다. 그러면, 이 글의 내용을 잘 이해했는지, 테스트 문제를 하나 더 내 보도록 하겠습니다.

 

 

 6번째 줄을 수행한 결과가 어떻게 나올까요? 당연히 True가 나오는 게 아닌가요? 라고 하셨다면. 네. 맞습니다. 그 이유를 위에서 설명한 내용에서 찾아보도록 하겠습니다.

 

 

 2829번째 줄에서 o1.equals(o2)를 봅시다.

 

 

 그러면, Integer 객체 둘은 내용은 같습니다. 1000101로 같은데, 참조값이 다름을 알 수 있습니다. 하나는 493, 다른 하나는 497입니다. 위에 논리대로라면 당연하게도 false가 떨어져야 할 텐데 말이죠.

 

 

 이는, Integer에 equals가 재정의 되어 있기 때문입니다. 실제로, 이것은 value가 같은지를 검사합니다. 그러므로, true가 나옵니다.

 

 

 참조값은 다릅니다. 그런데, 동등 비교를 하는 것이 재정의가 되었기 때문에, 실제로 Integer는 참조값이 아니라, wrapping된 값을 비교하게 됩니다. 따라서, true를 리턴하게 됩니다. 결과적으로 내용이 같은 1차원 Integer 배열끼리 비교하면 Arrays의 equals는 true를 리턴하게 됩니다.

 

 

 그리고, primitive type 배열에 대해서도 비교하는 메서드가 있습니다. 당연하게도 이들은, 그냥 ==으로 비교합니다. 하지만 n이 2이상인, n차원 배열의 경우에는, primitive n차원이라도, n차원 배열의 아이템이 배열이므로, Object로 들어갑니다. 그러니, Object끼리 비교하는 것을 호출합니다. 이 점을 주의하시면 됩니다.

 


 다시 예제 프로그램 1로 돌아옵시다.

 

 

 6번째 줄은 true를 리턴했습니다. 왜 그럴까요?

 

 

 Arrays의 deepEquals의 초입부는 equals와 다르지 않습니다.

 

 

 그런데, 원소끼리 비교하는 것이 다릅니다. 4269번째 줄을 보면, 어떤 메소드를 호출함을 알 수 있습니다.

 

 

 그리고, 이것은 해당 원소가 primitive type의 배열일 때와 Object일 때 나눠서 처리하고 있음을 알 수 있습니다. Object인 경우에는 다시 deepEquals를 재귀 호출하고 있는데요. 이를 통해서 깊은 데까지 비교함을 알 수 있습니다. 자세한 플로우는 이 글에서 설명하고 있으니 참고하셔도 좋겠습니다.