[시행착오] Stream.toList()를 어디선가 사용했다면 확인해야하는 사실?!

khyojun·2024년 6월 21일
6

시행착오

목록 보기
8/11
post-thumbnail

일단 제가 겪었던 한 사례를 말씀드리려고 합니다.

프로젝트 진행 도중 갑자기 이런 오류가 발생합니다. Spring과 Spring Data JPA 를 사용해서 진행을 했었습니다.

한 오류가 발생했었습니다.

java.lang.UnsupportedOperationException: null
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142) ~[na:na]
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:149) ~[na:na]
at org.hibernate.collection.spi.PersistentBag.clear(PersistentBag.java:471) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:512) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
~~

이런 오류가 발생하였습니다.
오류를 분석하다보면 다음과 같은 사실을 알 수 있는데요..! ImmutableCollections 라는 말이 나온것을 확인하다보니 불변해야하는 어떤 친구들을 건드렸다는 것이었습니다.

그런데 이 오류가 어느곳에서 터졌었나면? 바로 흔하디 흔한 저희가 자주 사용하게되는 코드인

userRepository.save(user);

이 친구였습니다. 그런데 위에서 살펴보니 이 User라는 엔티티는 어떻게 구성이 되어있었냐면요?

User


@Entity
@Setter
@NoArgsConstructor
public class User {


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Integer age;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private List<Team> teamList = new ArrayList<>();

    public User(Integer age) {
       this.age = age;
    }
}

이런 상황이었습니다.

그리고 실제 사용하였던 코드는 다음과 같습니다.

    @Transactional
    public void setUser(){

        User user1 = new User(10);
        User user2 = new User(20);

        List<Team> list = Arrays.asList(new Team("team1"), new Team("team2")).stream()
            .map(team -> team).toList();

        user1.setTeamList(list);
        user2.setTeamList(new ArrayList<>(Arrays.asList(new Team("team3"), new Team("team4"))));

        userRepository.save(user1);
        userRepository.save(user2);

        updateUserRank(); 
    }


    private void updateUserRank() {
        List<User> age = userRepository.findAll(Sort.by("age").descending());
        for (User fUser : age) {
            fUser.setAge(30);
            userRepository.save(fUser);
        }
    }

이 코드를 보고 어느 부분에서 잘못되었는지 만약 찾으셨다면! 제 글을 굳이 읽지 않으셔도 됩니다.

정답은 제목에....

네 사실 정답은 제목에 있습니다. 바로 흔히 사용하는 stream -> map -> toList() 를 사용하였을때 오류가 생긴건데요! 그런데 이 List가 들어있는 User를 save하려다보니 어떤 변경사항이 발생하여서 오류가 생긴것이었습니다.

그래서 이 코드 내부를 한 번 들어가보시면

이렇게 ..어? Collections.unmodifiableList()입니다. 불변하다는것을 알 수 있는데요!

네... 제 글은 여기서 끝입니다..? 라고 하면 섭섭하죠..

Collectors.toList() vs Stream.toList()

비슷하지만 다른 이 친구입니다.

아까 Stream.toList()를 봤으니 Collectors.toList() 내부를 보겠습니다.

이런식으로 구성이 되어있는데요 이 친구는 불변하지 않습니다. 그런데??! 이렇게 코드를 보다보면 저희가 또 생각나는 친구가 있습니다.

Collectors.toUnmodifiableList()

Collectors.toUnmodifiableList

이 친구가 그러면 왜 존재하는지 사실 저는 조금 의문이었습니다. 왜냐하면 이와 같은 역할을 결국 Stream.toList() 에서 해주기 때문이었습니다.

이거 원래 있는데 왜 만들었냐?

출처 : https://rules.sonarsource.com/java/tag/java16/RSPEC-6204/

왜 생겼냐면 코드가 unmodifiableList() 까지 쓰기에는 코드가 장황해지기 때문에 java 16에서 Stream.toList()로 들어왔다. 라고 합니다.

최종 : 그럼 세 개를 비교하면?

출처 : https://stackoverflow.com/questions/65969919/differences-of-java-16s-stream-tolist-and-stream-collectcollectors-tolist

위 사진을 보게 되었을때 그냥 가변이고 null 허용인 collect(toList()) , 불변이었던 두 친구 collect(toUnmodifiableList()) Stream.toList()의 차이점은 바로 null을 허용하는지에 대한 여부였습니다.

그래서 어떻게 사용할 수 있는지 잘 알아보고 이제부터 사용해야겠습니다!

profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글