개린이 탈출기

[JAVA] List 에 add가 왜 안돼? (feat.stream) 본문

에러 해결 목록

[JAVA] List 에 add가 왜 안돼? (feat.stream)

yooverd 2024. 11. 4. 15:54
728x90
반응형
SMALL

결론부터 말하자면

스트림의 toList()를 사용하여 만든 컬렉션은 수정불가한 List를 반환하기 때문에 collect(Collectors.toList()) 로 코드를 수정하면 바로 해결 가능하다.

 


1. 문제 발생

기존에 작업해 놓았던 코드를 예쁘게 다듬던 도중 다음과 같은 예외가 발생했다.

java.lang.UnsupportedOperationException: null
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142) ~[na:na]
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.addAll(ImmutableCollections.java:148) ~[na:na]

 

해당 예외가 발생한 부분의 소스코드는 다음과 같았다.

List<String> cdList = new ArrayList<>();
for (myEntity entity : entityList) {
	cdList.add(entityList.getCd());
}
cdList = userEmpnos.stream().filter(Objects::nonNull).distinct().toList();

////////////////// 중략 ///////////////

// 예외 발생 라인
cdList.addAll(nextEntityList.stream().map(MyEntity::getCd()).toList());

 


 

2. 문제 분석

자바 Stream 의 toList() 메서드는 다음과 같이 구현되어있다.

    default List<T> toList() {
        return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
    }

 

해당 메서드 toList()는 Collections 의 unmodifiableList 메서드를 호출하여 반환한다.
딱 보기에도 수정할 수 없는 컬렉션을 반환하는 것 같은데, 그 속을 자세히 알아보면 다음과 같다.

    public static <T> List<T> unmodifiableList(List<? extends T> list) {
        if (list.getClass() == UnmodifiableList.class || list.getClass() == UnmodifiableRandomAccessList.class) {
           return (List<T>) list;
        }

        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
    }

 

여기서 UnmodifiableList 내부를 살펴보면 다음과 같다.

static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        @java.io.Serial
        private static final long serialVersionUID = 1820017752578914078L;

        @SuppressWarnings("serial") // Conditionally serializable
        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
        }

        public int size()                          {return c.size();}
        public boolean isEmpty()                   {return c.isEmpty();}
        public boolean contains(Object o)          {return c.contains(o);}
        public Object[] toArray()                  {return c.toArray();}
        public <T> T[] toArray(T[] a)              {return c.toArray(a);}
        public <T> T[] toArray(IntFunction<T[]> f) {return c.toArray(f);}
        public String toString()                   {return c.toString();}

        public Iterator<E> iterator() {
            return new Iterator<>() {
                private final Iterator<? extends E> i = c.iterator();

                public boolean hasNext() {return i.hasNext();}
                public E next()          {return i.next();}
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    // Use backing collection version
                    i.forEachRemaining(action);
                }
            };
        }

        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        public boolean containsAll(Collection<?> coll) {
            return c.containsAll(coll);
        }
        public boolean addAll(Collection<? extends E> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean removeAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean retainAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public void clear() {
            throw new UnsupportedOperationException();
        }

        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> action) {
            c.forEach(action);
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            throw new UnsupportedOperationException();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Spliterator<E> spliterator() {
            return (Spliterator<E>)c.spliterator();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> stream() {
            return (Stream<E>)c.stream();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> parallelStream() {
            return (Stream<E>)c.parallelStream();
        }
    }

 

내가 이해한 바를 가볍게 설명해보자면,

1. 클래스 생성 시 받은 컬렉션을 그대로 받아 사용한다.

2. 컬렉션 자체에 대한 변경이 일어나는 메서드는 모두 UnsupportedOperationException 예외를 발생시킨다.

 

내 코드에서 발생했던 에러가 바로 UnsupportedOperationException  예외였고,
예외의 원인은 UnmodifiableList  에 addAll() 을 시도했기 때문이었다.

 


3. 문제 해결

collect() 메서드를 이용하여 문제를 해결할 수 있었다.

Map 을 만들 때 사용하던 방식이었는데, List도 만들 수 있는 메서드였다.

collect() 메서드 파라미터로 Collector 를 전달해주면 되는데, toList() 메서드를 활용하면 간단히 컬렉션을 생성하여 반환할 수 있다.

이 메서드는 toList() 와 다르게 ArrayList 를 반환하기 때문에 수정이 가능하기 때문이다.

따라서 나는 다음과 같이 코드를 수정하여 문제를 해결했다.

List<String> cdList = new ArrayList<>();
for (myEntity entity : entityList) {
	cdList.add(entityList.getCd());
}

// 수정 전 코드
// cdList = userEmpnos.stream().filter(Objects::nonNull).distinct().toList();
// 수정 후 코드
cdList = userEmpnos.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());

////////////////// 중략 ///////////////
cdList.addAll(nextEntityList.stream().map(MyEntity::getCd()).toList());

 

728x90
반응형
LIST