...
자바에서 리스트를 만드는 방법
자바에서 리스트를 만드는 방식은 대표적으로 3가지 정도 존재한다.
하나는 생성자로 직접 리스트 객체를 인스턴화 시키는 것이고, 좀 더 간편하게 원소가 들은 리스트를 한방에 생성하기 위해 별도로 Arrays.asList() 와 List.of() 메서드를 지원한다.
public static void main(String[] args) {
// 생성자 방식
List<Number> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
// Arrays 클래스의 asList 메서드
List<Number> asList = Arrays.asList(1, 2, 3);
// List 인터페이스의 of 메서드 (jdk 9)
List<Number> listOf = List.of(1, 2, 3);
}
Arrays.asList()는 배열을 리스트로 변환하는 메서드이고, List.of()는 자바9 부터 지원하는 List 인터페이스의 디폴트 메서드인 정적 팩토리 메서드이다. 이 둘은 반환하는 결과는 같은 것 같아 보이지만 섬세한 몇가지 차이점이 존재한다.
이번 시간에는 구체적으로 이 둘에 대해 어떤 차이가 있는지에 대해 정리해 볼 예정이다. 이때 차이점을 단순하게 눈으로 훑고 넘어가는 것이 아닌, 왜 그런지에 대해 내부 구현을 뜯어보며, 맨땅 암기가 아닌 이해를 통해 습득해 보도록 해보자. 내부 구성을 확인하는 습관을 들임으로써 다른 상황에서도 독자분들이 능동적으로 확인할 수 있도록 훈련을 하는 것이 개발자로서 성장하는데 발판이 되기 때문이다.
Arrays.asList 와 List.of 차이점
1. 리스트 변경 가능 여부
생성자로 리스트를 만드는 것과는 달리 메서드를 통해 리스트를 만들 경우 반환되는 이 리스트들은 불변 리스트 이게 된다. 그래서 리스트가 불변이기 때문에 요소를 리스트에 추가 및 삭제가 불가능 하다.
반면 Arrays.asList는 요소를 변경하는 set() 동작에 대해선 가능하지만, List.of는 모두 UnsuportedOperationException 예외를 발생한다. 즉, List.of를 통해 생성된 리스트는 완전한 불변 리스트 이지만, Arrays.asList를 통해 생성된 리스트는 반만 불변이라고 보면 된다.
원소 추가/삭제 | set 사용 | |
new ArrayList<>() | ✔️ | ✔️ |
Arrays.asList() | ❌ | ✔️ |
List.of() | ❌ | ❌ |
// Arrays 클래스의 asList 메서드
List<Number> asList = Arrays.asList(1, 2, 3);
// List 인터페이스의 of 메서드 (jdk 9)
List<Number> listOf = List.of(1, 2, 3);
asList.add(1); // UnsupportedOperationException
listOf.add(2); // UnsupportedOperationException
asList.set(0, 3); // 가능
listOf.set(0, 3); // UnsupportedOperationException
💬 Arrays.asList의 반환 리스트는 java.util.ArrayList가 아니다
새내기분들이 많이들 착각하는 부분이 Arrays.asList()의 반환값이 java.util 패키지에 위치한 ArrayList 인 줄 안다는 점이다. 하지만 정확히 말하자면 Arrays.asList() 가 반환하는 리스트는 비슷한 가짜 ArrayList 이다.
직접 asList() 메서드 구현부를 본다면 아래와 같이 ArrayList 인스턴스를 반환하는 것처럼 보이지만, 이를 따라 올라가면 이 ArrayList는 Arrays 클래스 내부에 구현된 또다른 별개의 ArrayList임을 알 수 가 있다.
위에서 Arrays.asList 로 생성한 리스트는 추가 / 삭제는 불가능하고 변경을 가능하다라고 했었다. 그 이유 역시 심플하다. 이 가짜 ArrayList 내에 정의된 메서드 목록을 살펴보면 set은 구현되어있지만 add / remove는 구현되어 있지 않기 때문이다.
역시나 List.of 로 반환되는 리스트도 ArrayList가 아니다. 내부 구현부를 보면 전혀 새로운 ImmutableCollections 객체의 내부 클래스인 ListN 객체를 생성하는 것을 볼 수 있다. 역시 이 ListN 클래스에도 add / remove / set 에 대해서 메서드가 구현되어 있지 않아 사용이 불가능한 것이다.
상속하고 있는 부모 클래스인 AbstractImmutableList를 따라 가보면 add, remove 메서드에 대해서 강제로 UnsupportedOperationException 예외를 발생하도록 구현했음을 볼 수 있다.
💬 왜 불변 리스트 인가
Arrays.asList 와 List.of 를 통해 생성된 리스트가 추가, 삭제가 불가능한 불변 객체로 구성된 이유는, 불변 객체만의 이점을 이용해 다른 컬렉션 자료구조로 변환이 용이하게 하기 위해서 이다.
Queue<Number> queue = new ArrayDeque<>(List.of(1, 2, 3));
Set<Number> set = new HashSet<>(List.of(1, 2, 3));
불변 객체의 몇가지 이점을 정리하면 다음과 같다.
- 스레드 안전성 : 불변 객체는 동기화 없이도 여러 스레드에서 안전하게 공유하고 액세스할 수 있다. (추가, 삭제가 안되기 때문에)
- 코드 간소화 : 불변 개체는 동시성을 위해 설계할 필요가 없으므로 코드가 간소화되고 버그 가능성이 낮다.
- 향상된 성능 : 변경 불가능한 개체는 항상 동일한 상태를 유지하므로 캐시하고 재사용할 수 있다.
이처러 불변 개체를 사용하면 코드의 안정성과 성능을 향상시킬 수 있으므로 많은 프로그래밍 컨텍스트에서 권장되는 방법이기 때문에 이런식으로 설계된 것이다. 이외에도 라이브러리나 프레임워크로 소스를 제공하다보면, 꼭 말안듣는 개발자들이 값을 자기 마음대로 조작하다가 오류를 유발시키는 경우가 많아, 아예 그런 행동을 막아 부수효과를 줄이기 위한 이유도 있다.
2. 리스트 내부 배열 참조 여부
List.of는 참조한 원본 배열의 값이 바뀌어도 리스트의 값은 바뀌지 않는다.
반면 Arrays.asList는 참조한 원본 배열의 값이 바뀌면 리스트의 값도 바뀌고, 반대로 List의 값을 수정해도 원본 배열의 값도 바뀌게 된다.
Number[] array = { 1, 2, 3 };
// Arrays 클래스의 asList 메서드
List<Number> asList = Arrays.asList(array);
// List 인터페이스의 of 메서드 (jdk 9)
List<Number> listOf = List.of(array);
// 배열 원소를 수정
array[1] = 999;
System.out.println(asList); // [1, 999, 3]
System.out.println(listOf); // [1, 2, 3]
// Arrays.asList 원소를 수정
asList.set(0, 888);
System.out.println(Arrays.toString(array)); // [888, 999, 3]
System.out.println(asList); // [888, 999, 3]
이러한 현상이 일어나는 이유도 내부 구성을 보면 알수 있는데, Arrays.asList의 ArrayList 객체는 인자로 받은 배열 레퍼런스 변수를 그대로 참조(얕은 복사) 하고 있기 때문이다.
반면 List.of는 인자로 방은 배열 원소를 순회하여 일일히 새로 만든 배열 변수에 대입하고 넘겨주기 때문에 완전 불변성을 띄는 것이다.
Collections.unmodifiableList
unmodifiableList() 는 인자로 받은 리스트를 불변 리스트로 변환해주는 Collections 클래스의 메서드이다.
어찌보면 Arrays.asList 와 List.of 를 통해 생성된 리스트와 비슷하다고 볼 수 있지만, 이 역시 Arrays.asList 와 같이 원본 배열을 참조하기 때문에 조심하여야 한다. 이밖에도 null 허용 여부 등 여러모로 Arrays.asList와 비슷하다고 보면 된다.
Number[] array = { 1, 2, 3 };
List<Number> asList = Arrays.asList(array);
List<Number> listOf = List.of(array);
List<Number> unList = Collections.unmodifiableList(asList);
// 배열 원소를 수정
array[1] = 999;
System.out.println(asList); // [1, 999, 3]
System.out.println(listOf); // [1, 2, 3]
System.out.println(unList); // [1, 999, 3]
3. NULL 값을 가질수 있는 여부
Arrays.asList는 원소에 null 값을 가질 수 있다.
List.of는 원소에 null 값을 절대 가질 수 없다.
List<Number> list = Arrays.asList(1, 2, null); // OK
List<Number> list = List.of(1, 2, null); // Fails with NullPointerException
이 역시 내부 구성을 보면 한눈에 이해가 가능하다.
Objects.requireNonNull 메서드
메서드 이름으로 유추할수 있듯이, 아무 타입의 매개변수를 받아 해당 매개변수가 null이면 NullPointerException 예외를 발생시키고, 그렇지 않으면 그대로 반환하는 매우 심플한 null 검증용 메서드라고 보면 된다.
추가적으로 contains() 메서드의 인자에도 null 값이 전달해도 마찬가지이다.
List<Number> list = Arrays.asList(1, 2, 3);
list.contains(null); // false
List<Number> list2 = List.of(1, 2, 3);
list2.contains(null); // Fails with NullPointerException
4. 메모리 사용량 차이
일반적으로 변경 불가능한 컬렉션 인스턴스는 변경 가능한 컬렉션 인스턴스 보다 훨씬 적은 메모리를 사용한다. 변경 불가능한 컬렉션은 JVM 내에서 캐싱될수 있기 때문이다.
따라서 반 불변인 Arrays.asList보단 완전 불변인 List.of 사용이 권장 되는 편이다.
5. 이외의 메서드 동작 유무
add, remove, set 이외의 리스트 메서드에 대해서도 각 동작에 대해서 허용 범위는 아래 표와 같다. List.of는 완전 불변이기에 수정을 가하는 메서드는 모두 안된다고 보면 된다.
List.of | Arrays.asList | |
add | ❌ | ❌ |
addAll | ❌ | ❌ |
clear | ❌ | ❌ |
remove | ❌ | ❌ |
removeAll | ❌ | ❗️ |
retainAll | ❌ | ❗️ |
replaceAll | ❌ | ✔️ |
set | ❌ | ✔️ |
sort | ❌ | ✔️ |
remove on iterator | ❌ | ❌ |
set on list-iterator | ❌ | ✔️ |
- ✔️ 방법이 지원됨을 의미
- ❌는 이 메서드를 호출하면 UnsupportedOperationException
- ❗️ 메서드의 인수가 변형을 일으키지 않는 경우에만 메서드가 지원됨을 의미
Collections.singletonList("foo").retainAll("foo")은 괜찮지만,Collections.singletonList("foo").retainAll("bar")는 UnsupportedOperationException
Arrays.asList vs List.of 총정리
위에서 언급한 내용을 종합하여 총정리하면 다음과 같이 결론 되어 진다.
- Arrays.asList는 변경이 가능하기 때문에 Thread-Safe 하지 않음. List.of는 완전 불변하기 때문에 쓰레드에 안전함.
- Arrays.asList()는 null 요소를 허용하고 List.of()는 null 요소를 허용하지 않음
- 메모리는 List.of 가 덜 사용. 따라서 반 불변인 Arrays.asList 보단 완전 불변인 List.of 사용 권장
- Arrays.asList , List.of 모두 변경할 수 없기 때문에 별도로 Collections을 생성해서 요소의 값을 복사하여 사용해야 한다.
# 참고자료
https://stackoverflow.com/questions/46579074/what-is-the-difference-between-list-of-and-arrays-aslist
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.