이펙티브 자바를 보면, 다중 정의를 사용할 때에는 신중하게 하라고 되어 있습니다. 이펙티브 자바 3판의 아이템 52를 잘 읽어보면, 어떨 때 overloading을 신중하게 써야 하는지를 알려주는데요. 그 중 하나의 예로, ArrayList의 remove를 들고 있습니다. 이에 대한 은, 하도 질문이 들어와서 예전에 레퍼런스 분석에도 상세히 썼던 기억이 납니다. 그나마 이건 단순한 편이지, 상황이 조금이라도 더 복잡해 지면.

 


 왜 그 책에서, remove를 짚어서 이야기를 했을까 고민을 해 보겠습니다.

 

 

 보시면, remove는 int를 받는 것이 있고, Object를 받는 것이 있습니다. 파라미터가 다릅니다. 물론, 리턴형도 다르지만, 리턴형만 다르다고 오버로딩이 되었다고 하지는 않습니다. 이 글에서도 언급했습니다만 다시 언급을 짧게 해 드리자면

 

 

 class A에 있는 메서드 2개가 이렇게 선언이 되어 있다고 해 봅시다. 코드 상에서 A의 인스턴스 a가 f 메서드를 호출하였습니다. 예를 들자면, a.f(5)를 호출했다 해 봅시다. 그러면, 이것은 위에 것을 호출해야 할까요? 아래의 것을 호출해야 할까요?

 

 

 사실 어느 쪽을 선택해도 문제가 없습니다. 컴파일러 입장에서는 이를 모호하다고 이야기 합니다. 그러므로, 리턴 타입만 다르게 선언할 수 없어요. 어찌 되었던, remove는 Obj를 받는 것은 해당 Object를 찾아서 삭제하는 일을 하고, int를 받는 것은 x번째 원소를 삭제하는 일을 합니다.

 

 여기까지는 그리 어려운 것이 아닙니다. 그런데 이걸 보시면 이야기가 달라질 수도 있습니다.

 

 

 저는 List에 1, 2, 3, 4, 5를 추가하였습니다. 그리고 1을 제거하고자 합니다. 8번째 줄에 v.remove(k)를 호출하였습니다.

 

 

 그런데 제거 되어야 할 1이 제거되지 않고, 2가 제거되었습니다.

 

 


 왜 일까요? 아래 메서드가 호출되었기 때문입니다.

 

 

 이것은 단순히, index번째 원소를 제거하는 일을 수행합니다. 이는 컴파일러가 int 타입을 remove로 넘겼을 때, int를 파라미터로 가지는 것을 호출하라고 해석했기 때문입니다.

 

 

 의도대로 하려면, k를 boxing 해서 넘겨주는 게 좋습니다.

 

 

 그러면 1이 제거가 됩니다. 의도대로 동작한 셈입니다.

 

 

 이는 Object를 인자로 받는 remove를 호출했기 때문입니다. 그리고 이것은 내부적으로 지워야 할 Object와 비교하는 equals를 호출하게 되는데요. 이 과정에서 호출되는 것은 아래 메서드입니다.

 

 

 Integer 객체이면, intValue 값을 가지고 비교합니다. 일단 여기까지 흐름을 정리하면, Boxing 객체인 Integer 배열 리스트를 선언하였습니다. 그리고, Integer 3을 삭제하려고 했습니다. 무의식 적으로 remove에 3을 넘겼더니, 의도치 않게 3번째 원소를 삭제하는 메서드가 호출되었습니다.

 

 저 또한 몇 번 실수를 한 적이 있는 부분이고 쓸 때 마다 혼란스러운 부분입니다. 그래서, Java에서 Integer로 List를 만든 코드가 보이면, 해당 실수를 했는지 부터 보게 됩니다. 예를 들어서, 오늘 디버깅한 이 코드가 있겠네요.

 

 

 다행히도, 이 분은 의도한 결과대로 2번 연산을 수행합니다. idx[n]은, 수열에 저장되어 있는, 나머지가 n인 수의 개수입니다. 이것은 나머지가 n인 수가 Q개 있을 때, Q-1이 저장되어 있습니다. 7번째 줄을 보면 알 수 있습니다. remove의 특성상 비효율적이겠지만 어찌 되었던 의도한 연산을 수행할 겁니다. 문제는, 제가 이 코드를 한 번에 못 읽었다는 것입니다.

 

 

 심지어, 21번째 줄을 잘못 디버깅 했습니다. 특정한 수를 찾아 제거하는 것이 아니라, x번째 수를 찾아 제거하는 겁니다. 물론 intellij면 그 걱정을 줄여줄 수 있습니다만, 몇 번 데여봐야 아는 것은 그리 좋은 상황이 아닙니다. 심지어 저는 지금도 계속 데이고 있습니다. 레퍼런스 분석에 remove에 대한 것을 썼음에도.

 

 


 반면에, add는 걱정 없이 썼던 듯 합니다. 왜냐하면, parameter 수가 다르기 때문에 무슨 역할을 하는지 쉽게 구분을 할 수 있기 때문입니다. 1 하나만 넘기면, 뒤에 추가하는 거고, 2, 3 이렇게 2개를 넘기면 특정 위치에 Integer 객체를 추가하는 거구나. 이렇게 말입니다. E 하나만 있는 것은 뒤에다가만 추가하는 것입니다.

 

 

 객체 e를 뒤에 추가합니다.

 

 

 이것은 index 번째에 객체 element를 추가합니다. 이 둘을 어떻게 구분할까요? 앞에 index라는 네이밍만 보고, x번째에 추가하는 기능이 add(E)에 비해 추가로 붙은 것임을 알 수 있습니다. 그냥 add라는 메서드를 쓴 것을 보았을 때, 이게 어떤 일을 하는 것이구나를 빠르게 유추할 수 있습니다.

 

 그러니, 이 경우는 다중 정의를 해 놓았어도 remove보다는 상대적으로 덜 헷갈립니다. 이 문서나 이 질답글과 이 을 볼 필요가 없습니다.