프로그래밍 언어/Java

Java 9 List.of() 메소드 사용시 주의점(ImmutableCollections, 불변 컬렉션)

코딩하는흑구 2021. 6. 2. 09:24

최근 Modern Java In Action 이라는 책을 보면서 예제를 작성하여 깃에 커밋하고 있었다.

 

이참에 Java 8 버전을 벗어나서 개인적으로 파격적이었던 Java 11로 코딩을 해보고 싶어서(물론 익숙한 방법을 쓰게된다) Modern Java In Action 혹은 이 블로그를 채우면서 새롭게 알아가는 내용들을 Java 11로 예제를 짜보기로 했다.

 

자바 8 이후에 등장한 내게 있어서 가장 써보고싶었던 기능은 LIst.of() 라는 메소드였다.

 

단지 Arrays.asList() 메소드가 있는데 괜스리 내가 쓰던 버전보다 진보적인 기능의 등장은 "이건 못참지" 수준이었고, 마침 컬렉션 정렬예제가 있길래 바로 써봤는데...

 

웬걸...

 

Exception in thread "main" java.lang.UnsupportedOperationException

 

바로 UnsupportedOperationException을 터뜨려 버렸다. 이거는 해당 연산을 지원안한다는 것인데... 보통 뭔가 해서는 안될 연산을 의미한다.(이름부터)

 

뭐지뭐지 어이없어하다가 이전에 회사 대리님으로부터 들었던 내용이 List.of() 메소드는 불필요하게 unModifiableCollection으로 만들 필요가 없다는 이야기가 생각이 났다. 그래서 직접 해당 구현메소드를 까봤더니 아니나 다를까...

 

List 인터페이스의 of() 메소드 구현부
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6) {
    return new ImmutableCollections.ListN<>(e1, e2, e3, e4, e5, e6);
}

List 인터페이스의 구현 컬렉션을 ImmutableCollections 객체로 만들고 있다. 이름부터가 굉장히 수상했고 내용을 들여다보니 역시나 안의 내용물들을 Immutable하게 관리하는 컬렉션이었다.

 

그럼 Arrays.asLIst() 메소드는 어떻게 컬렉션을 생성하고 있을까??

 

Arrays.asList() 메소드 구현부
public static <T> List<T> asList(T... a) {
	return new ArrayList<>(a);
}

상당히 심플하지만 큰 차이가 있다. 기본으로 쓰는 ArrayList 객체를 쓴다.  내부적으로 구현된 ArrayList객체를 쓴다. ArrayList 객체를 사용하게 되면 완전한 Immutable 상태는 아니다. 우리가 일반적으로 ORM이나 Sql Mapper 같은 라이브러리를 활용하게 된다면 정렬은 이미 Database 단에서 마무리하고 Formatting 정도나 진행하지 정렬기능을 활용하는 패턴은 대부분 아닐 것이다.(물론 비즈니스에 따라 다름)

 

따라서 내부의 ArrayList 객체를 썼을 것이고, 이 객체는 Collections.unmodifiableList()라는 메소드를 통해서 불변객체로 선언해두어야 개념상 수정되어서는 안되는 immutable 하게 컬렉션을 관리할 수 있게 된다.

 

* 내용 수정본.

저 new ArrayList() 에서 활용되는 객체는 Arrays 클래스 내부 클래스에 선언된 ArrayList 클래스이다. Arrays.asList() 메소드는 고정길이의 리스트를 생성하기 때문에 java.util 패키지 ArrayList 타입을  생성하는 줄 알고 오해하였으나. 바로 아래에 선언된 클래스를 활용하여 고정길이를 확보한다.(add 메소드가 없다.) 

 

하지만 sort 메소드를 오버라이딩 하여 재정의하고 있기 때문에 완전한 Immutable이라고 할 수는 없게 된다. 요소의 순서가 정렬되는 것 또한 immutable한 부분은 아니게 된다.

 

그래서 보통 Collections.unmodifiableList()을 활용하여 리스트 자체적으로 immutable 하도록 셋팅하게 만들어주는 패턴이 들어가게 된다.

List<Integer> integers = Arrays.asList(1, 2, 3);
        
Collections.unmodifiableCollection(integers);

 

하지만 List.of() 메소드는 해당 부분의 불편함(컬렉션을 생성하고 immutable 하기 위해서는 Collections.unmodifiableCollection 메소드를 사용하는 것)을 제거하기 위해서 ImmutableCollections 클래스를 사용하고 해당 클래스가 고안된 게 아닌가 생각된다.

 

ImmutableCollections 클래스의 내부 코드 중 객체 내부요소를 변경하는 메소드들이 아래와 같이 선언되어있다.

 

삽입, 수정, 제거 메소드를 호출하면 모두 UnsupprotedOperationException을 만날 수 있다.

 

    static UnsupportedOperationException uoe() { return new UnsupportedOperationException(); }

    static abstract class AbstractImmutableCollection<E> extends AbstractCollection<E> {
        // all mutating methods throw UnsupportedOperationException
        @Override public boolean add(E e) { throw uoe(); }
        @Override public boolean addAll(Collection<? extends E> c) { throw uoe(); }
        @Override public void    clear() { throw uoe(); }
        @Override public boolean remove(Object o) { throw uoe(); }
        @Override public boolean removeAll(Collection<?> c) { throw uoe(); }
        @Override public boolean removeIf(Predicate<? super E> filter) { throw uoe(); }
        @Override public boolean retainAll(Collection<?> c) { throw uoe(); }
    }

 

따라서 List.of() 메소드를 활용하게 될 경우, 요소로 포함시킬 객체들이 초기화시에 immutable한 요소인지 아닌지를 우선적으로 확인해야할 것 같다.


 

참조 : https://www.daleseo.com/java9-immutable-collections/

 

Java9의 불변 컬렉션 생성

Engineering Blog by Dale Seo

www.daleseo.com

 

강남언니 기술블로그에도 비슷한 내용(UnsupportedOperationException)이 나와있으니 한번 확인해보시면 좋을 것 같습니다.

https://blog.gangnamunni.com/post/Arrays-arrayList-ArrayList/

 

날 닮은 너를, 부족한 너를.

Arrays.arrayList 는 ArrayList 와 다르다 by 강남언니 블로그

blog.gangnamunni.com