오늘 문득 이게 궁금해졌다. 종종 List에 다른 원소들을 추가하는 코드를 보면
list.addAll(otherElements)
이런 식이 아니라
Collections.addAll(list, otherElements)
이런 식으로 되어있는 경우들이 있다.
전에는 똑같은 역할을 하는 메서드가 Collections에도 있나보다 하고 지나갔었는데, 오늘은 왠지 똑같은 메서드를 왜 굳이 따로 집어넣어놨을까 하는 생각이 들었다.
addAll.
특별할 것 없는 메서드이다.
그런데 왜 Collections에 따로 만들어뒀을까?
Collections.binarySearch 같은 뭔가 있어보이는 메서드도 아니고, 소스코드를 봐도 루프 돌면서 add 호출하는 것밖에 없는 네 줄짜리 메서드이다.
찾아보니 Collections.addAll이 더 빠르다고 한다.
but this method is likely to run significantly faster under most implementations.
그냥 빠른 것도 아니고 significantly 빠르다고? 왜 빠르지?
자료들을 좀 찾아보니 Arrays.asList 호출을 생략할 수 있어서 그렇다고 한다. Collection 인터페이스의 addAll은 배열이나 varargs를 인자로 받도록 선언된 것이 없다. 그래서 배열의 원소를 List에 추가하려면 배열을 먼저 List로 만들고 그 List를 addAll의 인자로 전달해야하는 것이다. 추가해야할 원소가 많을 때는 List를 만들 때 성능 저하가 발생할 수밖에 없을 것이다.
Collection, 그 중에서도 List, 그 중에서도 많이 쓰는 클래스 중의 하나가 ArrayList인데, 이 ArrayList 클래스의 addAll 구현이 최악인 것 같다. 소스코드를 보면 ArrayList의 addAll은 인자로 받은 Collection을 array로 바꿔서(toArray()) System.arraycopy로 backing array에 복사한다. 즉, ArrayList에 배열을 추가하려면 그 배열을 List로 바꿔서 ArrayList에 전달해야하고, ArrayList는 그 List를 다시 배열로 바꿔서 복사를 해야한다는 얘기다.
그러면 그냥 Collection에 넣지 왜 따로 Collections에다가 정적 메서드로 넣어뒀을까?
혹시 Collection 인터페이스와 Collections 클래스의 구현 시점의 차이가 있나싶어 확인해보니 둘 다 1.2부터이다.
배포 버전도 똑같은데 왜 굳이 따로 만들어뒀을까 하는 생각을 하다가 다시 자세히 보니 Collections.addAll은 1.5부터 추가된 것을 발견했다.
Java 5 new features를 보자.
Base Libraries 분류에 Collections Framework 항목이 있다.
https://docs.oracle.com/javase/1.5.0/docs/relnotes/features.html#collect
좀 더 자세한 내용은 여기에 나와있다.
https://docs.oracle.com/javase/1.5.0/docs/guide/collections/changes5.html
addAll에 대한 설명은 이렇게 적혀있다.
convenience method to add all of the elements in the specified array to the specified collection.
그럼 기존 Collection 인터페이스에 varargs를 인자로 취하는 메서드 선언을 추가하면 되지 않았을까? 음…그렇게 하면 기존의 Collection 인터페이스를 구현한 클래스들이 와장창 깨지겠구나;
정리하자면, Collection 구현체 클래스들의 addAll은 배열의 원소를 추가하는 작업을 하기에 적절하지 않기 때문에 루프 돌면서 add를 호출해주는 함수를 직접 만들어야하는 불편이 있었고, 인터페이스를 변경하지 않으면서 이런 기능을 제공하려다보니 별도의 정적 메서드로 Collections에 추가한 것.
그런데 Java 8부터는 interface가 default method를 가질 수 있게 되었으니 나중에는 Collection 인터페이스에 추가될 수도 있지 않을까 하는 생각도 든다. 동일한 코드를 굳이 또 만들 것 같진 않기도 하지만…내부에서 그냥 Collections.addAll 호출하는 식으로 하면 코드 중복은 또 피할 수 있을 것 같기도 하고…