<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Stream on monkshark.dev</title><link>https://monkshark.github.io/tags/stream/</link><description>Recent content in Stream on monkshark.dev</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 02 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://monkshark.github.io/tags/stream/index.xml" rel="self" type="application/rss+xml"/><item><title>Stream.toList()와 collect(toList())는 같지 않다</title><link>https://monkshark.github.io/p/stream-tolist-vs-collect/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://monkshark.github.io/p/stream-tolist-vs-collect/</guid><description>&lt;p&gt;Java 16에서 추가된 &lt;code&gt;Stream.toList()&lt;/code&gt;. 기존 &lt;code&gt;collect(Collectors.toList())&lt;/code&gt;보다 짧아서 무심코 일괄 치환했다가 &lt;code&gt;UnsupportedOperationException&lt;/code&gt;을 만나는 경우가 있다.&lt;/p&gt;
&lt;h2 id="결론부터"&gt;&lt;a href="#%ea%b2%b0%eb%a1%a0%eb%b6%80%ed%84%b0" class="header-anchor"&gt;&lt;/a&gt;결론부터
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;메서드&lt;/th&gt;
 &lt;th&gt;반환 List 변경 가능 여부&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Stream.toList()&lt;/code&gt; (Java 16+)&lt;/td&gt;
 &lt;td&gt;불가능 (immutable)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Collectors.toList()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;가능 (mutable, 일반적으로 ArrayList)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Collectors.toUnmodifiableList()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;불가능 (immutable)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="코드로-비교"&gt;&lt;a href="#%ec%bd%94%eb%93%9c%eb%a1%9c-%eb%b9%84%ea%b5%90" class="header-anchor"&gt;&lt;/a&gt;코드로 비교
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// UnsupportedOperationException&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Collectors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// OK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이름과 결과는 비슷하지만 동작이 다르다.&lt;/p&gt;
&lt;h2 id="왜-다른가"&gt;&lt;a href="#%ec%99%9c-%eb%8b%a4%eb%a5%b8%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 다른가
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Collectors.toList()&lt;/code&gt;의 명세에는 &amp;ldquo;반환 리스트의 type, mutability, serializability, thread-safety는 보장하지 않는다&amp;quot;라고 적혀 있다. 다만 실제 구현이 오랜 기간 &lt;code&gt;ArrayList&lt;/code&gt;였고, 사람들이 그 동작에 의존해서 add를 호출해 왔다. 코드베이스 곳곳에 mutable 가정이 박혀 있는 상태다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Stream.toList()&lt;/code&gt;는 새로 추가되면서 처음부터 immutable이라고 명시했다. 명세대로 변경 메서드는 던진다.&lt;/p&gt;
&lt;h2 id="어떻게-갈아끼울지"&gt;&lt;a href="#%ec%96%b4%eb%96%bb%ea%b2%8c-%ea%b0%88%ec%95%84%eb%81%bc%ec%9a%b8%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;어떻게 갈아끼울지
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;결과를 그대로 읽기만 하면 &lt;code&gt;Stream.toList()&lt;/code&gt;가 낫다. 짧고 의도(불변)도 명확하다.&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;add&lt;/code&gt; / &lt;code&gt;remove&lt;/code&gt; / 정렬 등 변경이 필요하면 &lt;code&gt;collect(Collectors.toList())&lt;/code&gt; 또는 &lt;code&gt;new ArrayList&amp;lt;&amp;gt;(stream.toList())&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;명시적으로 불변을 보장하고 싶으면 &lt;code&gt;Collectors.toUnmodifiableList()&lt;/code&gt; 또는 &lt;code&gt;List.copyOf(...)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="마무리"&gt;&lt;a href="#%eb%a7%88%eb%ac%b4%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;마무리
&lt;/h2&gt;&lt;p&gt;리팩토링하면서 IDE 일괄 변환으로 &lt;code&gt;.collect(Collectors.toList())&lt;/code&gt;를 &lt;code&gt;.toList()&lt;/code&gt;로 모두 바꿨다가, mutable에 의존한 코드가 한참 후 단위 테스트에서 깨지는 경우가 있다. 같은 이름이라도 명세가 다르면 결과도 다르다.&lt;/p&gt;</description></item></channel></rss>