<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>공부하는 개발자</title>
    <link>https://lannstark.tistory.com/</link>
    <description>생각을 정리하고 공부한 것을 공유하는 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 12:29:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>lannstark</managingEditor>
    <image>
      <title>공부하는 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/3189774/attach/c757866cf4da495fa14dd519b5f427c5</url>
      <link>https://lannstark.tistory.com</link>
    </image>
    <item>
      <title>자바는 여전히 공짜다 (Java is Still Free, Oct 2021)</title>
      <link>https://lannstark.tistory.com/242</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698813629421&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java Is Still Free 3.0.0 (Ocrt 2021)&quot; data-og-description=&quot;With the changes to Oracle JDK distribution and support, there has been considerable uncertainty over the rights to use Oracle JDK vs&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&quot; data-og-url=&quot;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bA5Xlt/hyUnL7Woo6/eFT46r2RiCtzkaxKHuEtak/img.png?width=1200&amp;amp;height=697&amp;amp;face=0_0_1200_697,https://scrap.kakaocdn.net/dn/Cp70u/hyUnPJhjPq/lEkmCVtKseCRrvb0jcOdV0/img.png?width=1358&amp;amp;height=681&amp;amp;face=0_0_1358_681,https://scrap.kakaocdn.net/dn/dObq52/hyUlrQz8MD/1geUE2YKOOyDqtZqoeQMvk/img.png?width=1230&amp;amp;height=520&amp;amp;face=0_0_1230_520&quot;&gt;&lt;a href=&quot;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@javachampions/java-is-still-free-3-0-0-ocrt-2021-bca75c88d23b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bA5Xlt/hyUnL7Woo6/eFT46r2RiCtzkaxKHuEtak/img.png?width=1200&amp;amp;height=697&amp;amp;face=0_0_1200_697,https://scrap.kakaocdn.net/dn/Cp70u/hyUnPJhjPq/lEkmCVtKseCRrvb0jcOdV0/img.png?width=1358&amp;amp;height=681&amp;amp;face=0_0_1358_681,https://scrap.kakaocdn.net/dn/dObq52/hyUlrQz8MD/1geUE2YKOOyDqtZqoeQMvk/img.png?width=1230&amp;amp;height=520&amp;amp;face=0_0_1230_520');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Is Still Free 3.0.0 (Ocrt 2021)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;With the changes to Oracle JDK distribution and support, there has been considerable uncertainty over the rights to use Oracle JDK vs&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 나오는 &quot;&lt;b&gt;요약글&lt;/b&gt;&quot;만을 번역한 내용입니다. 원활한 전달을 위해 일부 의역이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle JDK 배포와 지원 정책이 변화되며&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Oracle JDK&lt;/li&gt;
&lt;li&gt;Oracle Open JDK 빌드&lt;/li&gt;
&lt;li&gt;다양한 벤더사의 Open JDK 빌드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하는 권리에 관한 상당한 불확실성이 있었다. 우리는 다양한 벤더사가 제공하는 무료 업데이트 모델 (보안 포함) 및 유료 지원 모델 (신규 및 기존) 을 이용할 수 있다. 이 문서에서 요약글 / 긴 글로 각각 나누어 살펴보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 Oracle을 포함하여 다양한 벤더사가 제공하는 OpenJDK 빌드를 여전히 사용할 수 있고, 이는 &lt;a href=&quot;https://openjdk.org/legal/gplv2+ce.html&quot;&gt;GPLv2+CE&lt;/a&gt; 라이센스이기에 완전한 자유를 갖는다. 어떤 측면에서는 Oracle JDK가 여전히 무료로 비춰질 수 있는 것이다. 이 의미를 제대로 확인하기 위해서는 이 글의 다른 내용을 참고하길 바란다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java SE / OpenJDK / Oracle OpenJDK Builds / Oracle JDK&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenJDK 커뮤니티는 Java SE 명세의 Reference Implementation (RI) 를 오픈소스 (GPLv2 + CE)로 만들고 유지한다. 이 Java SE 명세는 &lt;a href=&quot;https://www.jcp.org/en/home/index&quot;&gt;Java Community Process&lt;/a&gt; (JCP) 에 의해 관리되고, 각 배포 버전에 따른 포괄적인 Java Specification Request (JSR) 를 통해 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 세상에는 Java SE를 구현한 다양한 구현체들이 존재한다. (대부분은 Open JDK를 기반으로 하고 있다) Alibaba, Amazon, Azu, BellSoft, Eclipse Adoptium (AdoptOpenJDK의 후속 버전), IBM, MicroSoft, Red Hat, Oracle, SAP, 등등의 다양한 업체들이 이런 구현체들을 만들고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 Oracle JDK 8는 &lt;a href=&quot;https://blogs.oracle.com/java/post/end-of-public-updates-is-a-process-not-an-event&quot;&gt;공개 업데이트 종료&lt;/a&gt;를 발표 했고, 따라서 2019년 4월부터 프로덕션 사용을 위해 업데이트를 하려면 지원 계약을 맺어야 했다. 위에서 언급했듯이, Oracle이 아닌 다른 업체가 만든 완전 무료 라이센스의 OpenJDK 8, 11, 17 버전은 여전히 존재한다. Oracle은 Oracle JDK 17의 zero-cost 바이너리 역시 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK를 얻기 위해선 다양한 선택지가 있다. 이 문서에서는 Java SE 8, 11, 17에 집중해서 다룬다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java SE 8에 머무르기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇몇 사람들은 다양한 이유로 Java SE 8을 계속 사용하고 싶어한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;2019년 4월 업데이트 이후, Oracle JDK 8은 상업 사용 제한에 들어갔다. Java SE 8 binary 를 업데이트 하기 위해서, 사용자들은 Oracle JDK 8을 위한 플랜을 구입하거나, 다른 벤더사의 Java SE 8 / OpenJDK 8 바이너리를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;Oracle JDK 8을 사용하고 있지 않다면, 현재 Java SE 8 / OpenJDK 8 벤더사들은 업데이트를 제공하거나 유료 지원 플랜을 제공할 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 Java SE 8 - 무엇을 선택해야 하는가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java SE 8의 보안을 포함한 무료 업데이트를 원한다면, TCK를 통과한 OpenJDK 배포판을 사용해라. (Amazon, Azu, BellSoft, Eclipse Adoptium, IBM, Microsoft, Red Hat, SAP 등)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java SE 11 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇몇 선택지가 있다. Oracle JDK가 Java SE 11을 어떻게 관리하고 업데이트하는지에 초점을 맞춰 주의깊게 읽어주길 바란다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Java SE 11에 관해, Oracle은 OpenJDK 기반의 &lt;a href=&quot;https://blogs.oracle.com/java/post/oracle-jdk-releases-for-java-11-and-later&quot;&gt;Oracle JDK&lt;/a&gt;를 제공한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Oracle OpenJDK 11 builds - GPLv2 + CE 라이센스&lt;/li&gt;
&lt;li&gt;Oracle JDK - 기본적으로 유료 라이센스 (개별 사용, 배포, 테스트, 프로토타이핑, 몇몇 특정 애플리케이션 유형에 대해서는 무료) 이고, GPLv2_CE를 사용하고 싶지 않거나, Oracle 제품 혹은 서비스와 함께 Oracle JDK를 사용하고 싶은 사람을 위한 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 Oracle OpenJDK 빌드에 대해 두 분기에 걸쳐 업데이트를 제공하고, 6개월마다 다음 버전을 제공한다. (LTS를 포함하여)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 Oracle에서 나온 제품이 아니더라도, 다양한 벤더사가 제공하는 Java SE / OpenJDK 바이너리 배포판을 사용할 수 있다. 이 벤더사들은 보안을 포함한 업데이트를 제공하며, LTS 버전에 대해서는 조금 더 긴 시간동안 지원한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 Java SE 11 - 무엇을 선택해야 하는가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java SE 11의 보안을 포함한 무료 업데이트를 원한다면, TCK를 통과한 OpenJDK 배포판을 사용해라. (Amazon, Azu, BellSoft, Eclipse Adoptium, IBM, Microsoft, Red Hat, SAP 등)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java SE 17 (LTS) 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇몇 선택지가 있다. Oracle JDK가 Java SE 17을 어떻게 관리하고 업데이트하는지에 초점을 맞춰 주의깊게 읽어주길 바란다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Java SE 17에 관해, Oracle은 OpenJDK 기반의 &lt;a href=&quot;https://blogs.oracle.com/java/post/oracle-jdk-releases-for-java-11-and-later&quot;&gt;Oracle JDK&lt;/a&gt;를 제공한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Oracle OpenJDK builds - GPLv2 + CE 라이센스&lt;/li&gt;
&lt;li&gt;Oracle JDK - 3년 간은 &lt;a href=&quot;https://www.oracle.com/downloads/licenses/no-fee-license.html&quot;&gt;NFTC 라이센스&lt;/a&gt;이고, 그 이후는 일반적인 상업 라이센스이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle은 Oracle OpenJDK 빌드에 대해 두 분기에 걸쳐 업데이트를 제공하고, 6개월마다 다음 버전을 제공한다. (LTS를 포함하여)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 Oracle에서 나온 제품이 아니더라도, 다양한 벤더사가 제공하는 Java SE / OpenJDK 바이너리 배포판을 사용할 수 있다. 이 벤더사들은 보안을 포함한 업데이트를 제공하며, LTS 버전에 대해서는 조금 더 긴 시간동안 지원한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 Java SE 17 - 무엇을 선택해야 하는가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java SE 11의 보안을 포함한 무료 업데이트를 원한다면, TCK를 통과한 OpenJDK 배포판을 사용해라. (Amazon, Azu, BellSoft, Eclipse Adoptium, IBM, Microsoft, Red Hat, SAP 등)&lt;/p&gt;</description>
      <category>개발 공부 기록하기/01. JAVA &amp;amp; Kotlin</category>
      <category>jdk</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/242</guid>
      <comments>https://lannstark.tistory.com/242#entry242comment</comments>
      <pubDate>Wed, 1 Nov 2023 13:46:43 +0900</pubDate>
    </item>
    <item>
      <title>[리액트 공식 문서 번역] useRef</title>
      <link>https://lannstark.tistory.com/241</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useRef&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react.dev/reference/react/useRef&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1698335240773&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;useRef &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/useRef&quot; data-og-url=&quot;https://react.dev/reference/react/useRef&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X6voZ/hyUkikIVS9/0rt81dqitA7iuX7vbgAZqK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/dHBL7j/hyUkbMD4Rm/tuDEZkz4L0RYuDcxxoAYXk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useRef&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/reference/react/useRef&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X6voZ/hyUkikIVS9/0rt81dqitA7iuX7vbgAZqK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/dHBL7j/hyUkbMD4Rm/tuDEZkz4L0RYuDcxxoAYXk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;useRef &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 렌더링을 위해 필요하지 않은 값을 참조할 수 있게 해주는 React Hook이다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const ref = useRef(initialValue)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트의 top level에서 &lt;code&gt;useRef&lt;/code&gt; 를 호출하면, &lt;code&gt;ref&lt;/code&gt; 를 선언한다. &lt;code&gt;ref&lt;/code&gt; 란, 컴포넌트가 어떤 정보를 &amp;ldquo;기억&amp;rdquo;하고 싶은데 해당 정보가 리렌더링을 일으키지 않고 싶을 때 사용하는 객체를 말한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;initialValue&lt;/code&gt; : ref 객체가 초기화 될 때, &lt;code&gt;current&lt;/code&gt; 프로퍼티에 들고 있는 값. 어떤 타입이든 될 수 있다. 이 매개변수는 최초 렌더링 이후 무시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const ref = useRef(0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 코드를 사용하면, ref는 아래와 같은 객체가 된다. 즉, &lt;code&gt;useRef&lt;/code&gt; 는 아래와 같은 객체를 반환한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;{
  current: 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;current&lt;/code&gt; : 최초 전달된 &lt;code&gt;initialValue&lt;/code&gt;로 정해진다. 추후 다른 값으로 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 렌더링이 일어날 때 &lt;code&gt;useRef&lt;/code&gt; 는 동일한 객체를 반환할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 메커니즘&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ref.current&lt;/code&gt; 값을 변경할 수 있다. &lt;code&gt;ref&lt;/code&gt;는 state와 다르게 mutable 하다. 하지만, &lt;code&gt;ref&lt;/code&gt; 가 렌더링에 사용되는 객체를 들고 있다면, 이 객체를 변경할 수는 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ref.current&lt;/code&gt; 값을 바꿀 때 리액트는 컴포넌트를 리렌더링하지 않는다. &lt;code&gt;ref&lt;/code&gt; 는 단순한 JS object이기 때문에 리액트는 &lt;code&gt;ref.current&lt;/code&gt; 값이 변경되었는지도 모른다.&lt;/li&gt;
&lt;li&gt;초기화 과정을 제외하고는, 렌더링 과정에서 &lt;code&gt;ref.current&lt;/code&gt; 값을 읽거나 쓰지 않기를 바란다. 만약 그렇지 않다면 컴포넌트의 동작이 예측불가능해진다.&lt;/li&gt;
&lt;li&gt;Strict Mode에서 리액트는 혹시 모를 오류를 찾기 위해 컴포넌트를 두 번 호출 한다. 이런 동작은 개발환경에서만 존재하고, 운영환경에는 영향을 미치지 않는다. &lt;code&gt;useRef&lt;/code&gt; 로 인해 생긴 &lt;code&gt;ref&lt;/code&gt; 오브젝트 역시 두 번 생기지만, 그 중 하나는 버려진다. 만약 컴포넌트가 pure 하다면, 행동에 영향을 미치지 않을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 사례&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ref를 이용한 값 참조.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useRef&lt;/code&gt; 를 컴포넌트 최상단에서 호출하면, 하나 혹은 그 이상의 &lt;code&gt;ref&lt;/code&gt; 를 얻을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function Stopwatch() {
  const intervalRef = useRef(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useRef&lt;/code&gt; 는 &lt;code&gt;current&lt;/code&gt; 프로퍼티 하나만 갖고 있는 &lt;code&gt;ref&lt;/code&gt; 객체를 주어진 초기값으로 초기화하여 설정한다. 그리고 그 다음 렌더링이 일어날 때 &lt;code&gt;useRef&lt;/code&gt; 는 동일한 객체를 반환한다. 정보를 저장하거나 추후 정보를 꺼내기 위해 &lt;code&gt;current&lt;/code&gt; 프로퍼티를 바꿀 수 있다. 이는 &lt;code&gt;state&lt;/code&gt; 와 비슷하게 느껴지지만, 중요한 차이가 존재한다. &lt;code&gt;ref&lt;/code&gt; 를 바꾸는 것은 리렌더링을 일으키지 않는다. 즉, &lt;code&gt;ref&lt;/code&gt; 는 컴포넌트의 시각적 결과물에 영향을 주지 않고 정보를 저장하는 최적의 방법이다. 예를 들어, interval ID를 저장하고 나중에 해당 값을 사용해야 한다면, &lt;code&gt;ref&lt;/code&gt; 에 집어 넣을 수 있다. &lt;code&gt;ref&lt;/code&gt; 안에 있는 값을 변경하기 위해 &lt;code&gt;current&lt;/code&gt; 프로퍼티를 수동으로 바꿀 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function handleStartClick() {
  const intervalId = setInterval(() =&amp;gt; {
    // ...
  }, 1000);
  intervalRef.current = intervalId;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에, interval ID를 ref로부터 읽어와 interval를 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref&lt;/code&gt; 를 사용하기 위해서 다음을 명심해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리렌더링 사이에 정보를 저장할 수 있다. (일반적인 변수들은 리렌더링 될 때마다 reset 된다)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt; 의 값을 바꾼다고 해서, 리렌더링을 발동시키지 않는다. (&lt;code&gt;state&lt;/code&gt; 는 변경되면 리렌더링 시키게 된다)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt; 는 컴포넌트의 인스턴스마다 지역적으로 존재한다. (컴포넌트 바깥에 있는 변수는 공유된다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref&lt;/code&gt; 는 리렌더링을 발동시키지 않기 때문에, 화면에 보이는 정보를 저장하는 목적으로는 부적절하다. 이 경우는 &lt;code&gt;state&lt;/code&gt;를 사용해야 한다. &lt;a href=&quot;https://react.dev/learn/referencing-values-with-refs#differences-between-refs-and-state&quot;&gt;https://react.dev/learn/referencing-values-with-refs#differences-between-refs-and-state&lt;/a&gt; 를 읽어보라.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 1, Click counter&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      Click me!
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트는 버튼이 얼마나 클릭되었는지 추적하기 위해 &lt;code&gt;ref&lt;/code&gt; 를 사용한다. 클릭 횟수는 화면에 보이지 않고, event handler에서만 읽고 쓰이기 때문에 &lt;code&gt;ref&lt;/code&gt; 를 사용해도 괜찮다. 만약 JSX 안에서 &lt;code&gt;{ref.current}&lt;/code&gt;를 보여주려고 하면, 클릭을 하더라도 숫자가 업데이트 되지는 않을 것이다. 그 이유는 &lt;code&gt;ref.current&lt;/code&gt; 의 값을 바꾼다고 해서 리렌더링이 일어나지 않기 때문이다. 리렌더링을 위한 정보 저장은 state에 되어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 2, A stopwatch&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() =&amp;gt; {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null &amp;amp;&amp;amp; now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;Time passed: {secondsPassed.toFixed(3)}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={handleStart}&amp;gt;
        Start
      &amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleStop}&amp;gt;
        Stop
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 &lt;code&gt;state&lt;/code&gt;와 &lt;code&gt;ref&lt;/code&gt;의 조합이다. &lt;code&gt;startTime&lt;/code&gt; 과 &lt;code&gt;now&lt;/code&gt; 는 리렌더링에 사용되기 때문에 state 이고, 버튼이 눌렸을 때 반복되는 interval를 멈추기 위한 interval id는 &lt;code&gt;ref&lt;/code&gt; 이다. interval id는 리렌더링에 사용되지 않기 때문에 &lt;code&gt;ref&lt;/code&gt;에 저장하는 것이 적합하고, 수동으로 업데이트할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref.current&lt;/code&gt; 를 렌더링 과정에 읽거나 쓰지 않아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 컴포넌트의 body가 순수 함수 (pure function) 처럼 동작하기를 바란다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 Input (props, state, context)이 같다면, 항상 동일한 JSX를 반환해야 한다.&lt;/li&gt;
&lt;li&gt;순서를 다르게 호출하거나 다른 매개변수를 준다고 해서, 또 다든 호출의 결과에 영향을 줘서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 정에서 &lt;code&gt;ref&lt;/code&gt; 를 읽거나 쓰면 예외를 던질 것이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function MyComponent() {
  // ...
  //   리렌더링 과정에서 ref를 쓰지 마라.
  myRef.current = 123;
  // ...
  //   리렌더링 과정에서 ref를 읽지 마라.
  return &amp;lt;h1&amp;gt;{myOtherRef.current}&amp;lt;/h1&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러나 effect 에서는 읽고 쓸 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function MyComponent() {
  // ...
  useEffect(() =&amp;gt; {
    // ✅ ref를 effect에서 읽거나 쓸 수 있다.
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ ref를 이벤트 핸들러에서 읽거나 쓸 수 있다.
    doSomething(myOtherRef.current);
  }
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 과정에서 어떤 값을 읽고 써야 한다면, &lt;code&gt;state&lt;/code&gt; 를 대신 사용해라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 규칙을 지키지 않는다면, component가 동작은 하겠지만, React에 추가되는 새로운 기능들은 이런 규칙 위해서 동작하기 때문에 잘 호환되지 않을 수 있다. &lt;a href=&quot;https://react.dev/learn/keeping-components-pure#where-you-_can_-cause-side-effects&quot;&gt;https://react.dev/learn/keeping-components-pure#where-you-_can_-cause-side-effects&lt;/a&gt; 도 읽어봐라.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ref로 DOM 조작하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM을 조작하기 위해 &lt;code&gt;ref&lt;/code&gt; 를 사용하는 것은 특히 흔한 일이다. 리액트는 이런 경우를 위한 지원 기능이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, &lt;code&gt;ref&lt;/code&gt; 객체를 &lt;code&gt;null&lt;/code&gt; 값으로 초기화 해라.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 &lt;code&gt;ref&lt;/code&gt; 객체를 조작하고 싶은 DOM node의 JSX &lt;code&gt;ref&lt;/code&gt; 속성에 넣어라.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ...
return &amp;lt;input ref={inputRef} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트가 DOM node를 만들고 화면에 띄운 후, React는 &lt;code&gt;ref&lt;/code&gt; 오브젝트의 &lt;code&gt;current&lt;/code&gt; 프로퍼티를 설정된 DOM node에 연결할 것이다. 이제 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 의 DOM node에 접근해 &lt;code&gt;focus()&lt;/code&gt; 와 같은 메소드를 호출할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function handleClick() {
  inputRef.current.focus();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 해당 node가 화면으로부터 제거되었을 때 &lt;code&gt;current&lt;/code&gt; 프로퍼티에 null을 집어 넣는다. &lt;a href=&quot;https://react.dev/learn/manipulating-the-dom-with-refs&quot;&gt;https://react.dev/learn/manipulating-the-dom-with-refs&lt;/a&gt; 도 읽어보라.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 1, Focusing a text input&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 클릭하면, input에 foucs 되도록 만들어보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input ref={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;
        Focus the input
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 2, Scrolling an image into view&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 클릭하면, 해당 이미지가 있는 곳으로 스크롤을 해보자. 이 때 &lt;code&gt;ref&lt;/code&gt;가 DOM node를 갖고 있고, DOM의 &lt;code&gt;querySelectorAll&lt;/code&gt; API를 이용해 원하는 이미지를 찾을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;import { useRef } from 'react';

export default function CatFriends() {
  const listRef = useRef(null);

  function scrollToIndex(index) {
    const listNode = listRef.current;
    // This line assumes a particular DOM structure:
    const imgNode = listNode.querySelectorAll('li &amp;gt; img')[index];
    imgNode.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToIndex(0)}&amp;gt;
          Tom
        &amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToIndex(1)}&amp;gt;
          Maru
        &amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToIndex(2)}&amp;gt;
          Jellylorum
        &amp;lt;/button&amp;gt;
      &amp;lt;/nav&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;ul ref={listRef}&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;img
              src=&quot;https://placekitten.com/g/200/200&quot;
              alt=&quot;Tom&quot;
            /&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;img
              src=&quot;https://placekitten.com/g/300/200&quot;
              alt=&quot;Maru&quot;
            /&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;img
              src=&quot;https://placekitten.com/g/250/200&quot;
              alt=&quot;Jellylorum&quot;
            /&amp;gt;
          &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 3, Playing and pausing a video&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref&lt;/code&gt; 를 이용해 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; DOM node를 &lt;code&gt;play()&lt;/code&gt; 하거나 &lt;code&gt;pause()&lt;/code&gt; 해보자.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;import { useState, useRef } from 'react';

export default function VideoPlayer() {
  const [isPlaying, setIsPlaying] = useState(false);
  const ref = useRef(null);

  function handleClick() {
    const nextIsPlaying = !isPlaying;
    setIsPlaying(nextIsPlaying);

    if (nextIsPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;
        {isPlaying ? 'Pause' : 'Play'}
      &amp;lt;/button&amp;gt;
      &amp;lt;video
        width=&quot;250&quot;
        ref={ref}
        onPlay={() =&amp;gt; setIsPlaying(true)}
        onPause={() =&amp;gt; setIsPlaying(false)}
      &amp;gt;
        &amp;lt;source
          src=&quot;https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4&quot;
          type=&quot;video/mp4&quot;
        /&amp;gt;
      &amp;lt;/video&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Example 4, Exposing a ref to your own component&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때때로, 당신이 만든 컴포넌트 내부의 DOM을 부모 컴포넌트가 제어해야 하는 경우가 있을 수 있다. 예를 들어, &lt;code&gt;MyInput&lt;/code&gt; 컴포넌트를 만든 후, 부모 컴포넌트가 내부의 input에 focus를 할 수 있게 만들어야 한다고 하자. 이런 경우, &lt;code&gt;useRef&lt;/code&gt; 가 input을 갖고 있게 만들고, &lt;code&gt;forwardRef&lt;/code&gt; 를 이용해 부모 컴포넌트에 노출할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) =&amp;gt; {
  return &amp;lt;input {...props} ref={ref} /&amp;gt;;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;MyInput ref={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;
        Focus the input
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;ref&lt;/code&gt; 컨텐츠가 다시 만들어지는 경우를 피하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 ref 값을 최초로 한 번 저장한 이후, 다음 렌더링 때는 이를 무시한다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Video() {
  const playerRef = useRef(new VideoPlayer());
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, &lt;code&gt;new VideoPlayer()&lt;/code&gt; 의 결과는 최초 렌더링 때만 사용되고 그 후에는 무시되지만, &lt;code&gt;VideoPlayer&lt;/code&gt; 의 인스턴스는 렌더링을 할 때마다 생기게 된다. 만약 비싼 객체를 만들어야 한다면 리소스가 낭비되는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 &lt;code&gt;ref&lt;/code&gt; 초기화를 다음과 같이 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Video() {
  const playerRef = useRef(null);
  if (playerRef.current === null) {
    playerRef.current = new VideoPlayer();
  }
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;code&gt;ref.current&lt;/code&gt; 를 렌더링 과정에서 읽거나 쓸 수 없다. 하지만, 이 경우는 함수형 컴포넌트의 실행 결과가 항상 같기 때문에 괜찮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type checker가 ref에 null이 들어있을 수 있다고 판단하는 것을 피하기 위해 다음과 같은 패턴을 사용할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Video() {
  const playerRef = useRef(null);

  function getPlayer() {
    if (playerRef.current !== null) {
      return playerRef.current;
    }
    const player = new VideoPlayer();
    playerRef.current = player;
    return player;
  }

  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;직접 만든 컴포넌트에 ref를 사용할 수 없어요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 직접 만든 컴포넌트에 &lt;code&gt;ref&lt;/code&gt; 를 넣으려고 하면,&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const inputRef = useRef(null);

return &amp;lt;MyInput ref={inputRef} /&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 에러를 만나게 될 것이다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 컴포넌트는 내부에 있는 DOM node에 대한 ref를 노출하지 않는다. 이를 해결하기 위해서는, &lt;code&gt;ref&lt;/code&gt; 를 쓰고 싶은 컴포넌트를 &lt;code&gt;forwardRef&lt;/code&gt; 로 감싸야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) =&amp;gt; {
  return (
    &amp;lt;input
      value={value}
      onChange={onChange}
      ref={ref}
    /&amp;gt;
  );
});

export default MyInput;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 부모 컴포넌트가 ref를 사용할 수 있게 된다.&lt;/p&gt;</description>
      <category>개발 공부 기록하기/07. react.js &amp;amp; vue.js</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/241</guid>
      <comments>https://lannstark.tistory.com/241#entry241comment</comments>
      <pubDate>Fri, 27 Oct 2023 00:47:05 +0900</pubDate>
    </item>
    <item>
      <title>코루틴이란 무엇인가? (루틴과 코루틴)</title>
      <link>https://lannstark.tistory.com/240</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &amp;lsquo;코루틴&amp;rsquo;을 간단히 사용해 보면서 코루틴이 무엇인지~ 감을 잡아보도록 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 코루틴이란 무엇일까? 코루틴을 영어로 살펴보면 &lt;code&gt;co-routine&lt;/code&gt; 으로 여기서 접미어 &lt;code&gt;co&lt;/code&gt; 는 &amp;lsquo;협력하는&amp;rsquo; 이라는 의미가 있다. 뒤에 있는 &lt;code&gt;routine&lt;/code&gt; 은 컴퓨터 공학에서 이야기하는 루틴으로 간단히 &amp;lsquo;함수&amp;rsquo;라고 생각해도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;code&gt;co-routine&lt;/code&gt; 은 협력하는 루틴, 협력하는 함수라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐, 우리가 생각하는 그냥 루틴(함수)도 서로 호출과 반환을 주고받으며 협력을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 협력하는 루틴이라니 도대체 그냥 루틴과 협력하는 루틴은 어떤 차이가 있길래 &amp;lsquo;협력하는&amp;rsquo; 이라는 의미가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  println(&quot;START&quot;)
  newRoutine()
  println(&quot;END&quot;)
}

fun newRoutine() {
  val num1 = 1
  val num2 = 2
  println(&quot;${num1 + num2}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 직관적인 이 코드는 2개의 루틴이 있다. main 루틴과 new 루틴이다. 또한 이 코드를 실행하면 쉽게 예상할 수 있는 것처럼 다음과 같은 결과물이 나온다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;START
3
END&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 결과물이 나오는 과정은 이렇다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;main 루틴이 &lt;code&gt;START&lt;/code&gt;를 출력한 이후 new 루틴을 호출한다.&lt;/li&gt;
&lt;li&gt;new 루틴은 1과 2를 계산해 &lt;code&gt;3&lt;/code&gt;을 출력한다&lt;/li&gt;
&lt;li&gt;그 이후, new 루틴은 종료되고 main 루틴으로 돌아온다.&lt;/li&gt;
&lt;li&gt;main 루틴은 &lt;code&gt;END&lt;/code&gt; 를 출력하고 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 그림으로 나타내면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXhnm/btssqwk5uiM/3aTExCfTMwJkHUJMPuuHxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXhnm/btssqwk5uiM/3aTExCfTMwJkHUJMPuuHxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXhnm/btssqwk5uiM/3aTExCfTMwJkHUJMPuuHxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXhnm%2Fbtssqwk5uiM%2F3aTExCfTMwJkHUJMPuuHxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;372&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이때, new 루틴이 종료된 이후에는 new 루틴에서 사용했던 &lt;code&gt;num1&lt;/code&gt; 과 &lt;code&gt;num2&lt;/code&gt; 에는 다시 접근할 수 없게 되고, 따라서 메모리에서도 정보가 사라질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 정리해 보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;루틴은 진입하는 곳이 한 곳이고&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;루틴이 종료되면 그 루틴에서 사용했던 정보가 초기화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;된다고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 루틴을 사용하는 코드를 살펴보았으니, 이번에는 협력하는 루틴, 코루틴을 살펴보자. 코루틴을 사용하려면 의존성을 한 가지 추가해 주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;dependencies {
    implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 보는 코드도 많을 텐데 우선 타이핑 해보고 각 함수의 의미를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main(): Unit = runBlocking {
  println(&quot;START&quot;)
  launch {
    newRoutine()
  }
  yield()  
  println(&quot;END&quot;)
}

suspend fun newRoutine() {
  val num1 = 1
  val num2 = 2
  yield()
  println(&quot;${num1 + num2}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 처음 보는 함수는 &lt;code&gt;runBlocking&lt;/code&gt; 이다. &lt;code&gt;runBlocking&lt;/code&gt; 함수는 일반 루틴 세계와 코루틴 세계를 연결하는 함수이다. 이 함수 자체로 새로운 코루틴을 만들게 되고, &lt;code&gt;runBlocking&lt;/code&gt; 에 넣어준 람다가 새로운 코루틴 안에 들어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;code&gt;launch&lt;/code&gt; 라는 함수도 보인다. &lt;code&gt;launch&lt;/code&gt; 라는 함수 역시 새로운 코루틴을 만드는 함수이다. 주로 반환 값이 없는 코루틴을 만드는데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우리는 &lt;code&gt;runBlocking { }&lt;/code&gt; 과 &lt;code&gt;launch { }&lt;/code&gt; 를 사용해 2개의 코루틴을 만든 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;code&gt;yield()&lt;/code&gt; 라는 함수가 보인다. yield 라는 단어는 양보하다라는 의미가 있는데, 이 의미에 걸맞게 &lt;code&gt;**yield()&lt;/code&gt; 라는 함수는 지금 코루틴의 실행을 잠시 멈추고 다른 코루틴이 실행되도록 양보**한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;code&gt;suspend fun&lt;/code&gt; 이라는 키워드가 보인다. 코틀린에서 일반적인 함수는 &lt;code&gt;fun&lt;/code&gt; 으로 만드는 것과 다르게 &lt;code&gt;suspend&lt;/code&gt; 라는 키워드가 붙었는데 &lt;code&gt;**suspend&lt;/code&gt; 라는 키워드가 붙으면 다른 &lt;code&gt;suspend fun&lt;/code&gt; 을 호출하는 특수 능력을 갖게 된다.** 우리가 사용한 &lt;code&gt;yield()&lt;/code&gt;가 바로 suspend fun 이기 때문에 함수 &lt;code&gt;newRoutine&lt;/code&gt; 에 &lt;code&gt;suspend&lt;/code&gt; 를 붙여 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이렇게 새로운 기능들의 의미를 모두 살펴보았다. 이제 두 개의 코루틴을 사용하는 코드를 실행시켜보자. 실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;START
END
3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗..! 일반-루틴을 코-루틴으로 변경했을 뿐인데 실행 결과가 달라졌다! &lt;code&gt;3&lt;/code&gt; 출력이 &lt;code&gt;END&lt;/code&gt; 뒤에 나온 것이다! 혹시 양보하는 기능인 &lt;code&gt;yield()&lt;/code&gt; 때문에 그럴까?! 2개의 &lt;code&gt;yield()&lt;/code&gt; 를 지우더라도 여전히 동일하게 위의 출력이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력이 나오는 과정을 살펴보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;main 코루틴이 &lt;code&gt;runBlocking&lt;/code&gt; 에 의해 시작되고 &lt;code&gt;START&lt;/code&gt;가 출력된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;launch&lt;/code&gt; 에 의해 새로운 코루틴이 생긴다. 하지만, newRoutine의 실행은 바로 일어나지 않는다.&lt;/li&gt;
&lt;li&gt;main 코루틴 안에 있는 &lt;code&gt;yield()&lt;/code&gt;가 되면 main 코루틴은 new 코루틴에게 실행을 양보한다. 따라서 &lt;code&gt;launch&lt;/code&gt; 가 만든 새로운 코루틴이 실행되고, newRoutine 함수가 실행된다.&lt;/li&gt;
&lt;li&gt;newRoutine 함수는 다시 &lt;code&gt;yield()&lt;/code&gt;를 호출하고 main 코루틴으로 되돌아온다.&lt;/li&gt;
&lt;li&gt;main 루틴은 &lt;code&gt;END&lt;/code&gt; 를 출력하고 종료된다.&lt;/li&gt;
&lt;li&gt;아직 newRoutine 함수가 끝나지 않았으니 newRoutine 함수로 되돌아가 &lt;code&gt;3&lt;/code&gt; 이 출력되고 프로그램이 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 루틴 세계에 비해 복잡하고 직관적이지 않다..   이 과정을 그림으로 나타내면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWKNmD/btssqB7Oz04/foyz0w75B3pGgLH6EYPe41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWKNmD/btssqB7Oz04/foyz0w75B3pGgLH6EYPe41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWKNmD/btssqB7Oz04/foyz0w75B3pGgLH6EYPe41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWKNmD%2FbtssqB7Oz04%2Ffoyz0w75B3pGgLH6EYPe41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;319&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;961&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다! 루틴과 코루틴의 가장 큰 차이는 &lt;b&gt;중단&lt;/b&gt;과 &lt;b&gt;재개&lt;/b&gt;이다. 루틴은 한 번 시작되면 종료될 때까지 멈추지 않지만, 코루틴은 상황에 따라 잠시 중단이 되었다가 다시 시작되기도 한다. 때문에 완전히 종료되기 전까지는 &lt;code&gt;newRoutine&lt;/code&gt; 함수 안에 있는 &lt;code&gt;num1&lt;/code&gt; &lt;code&gt;num2&lt;/code&gt; 변수가 메모리에서 제거되지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 만약 출력이 일어날 때 어떤 스레드에서 출력이 실행되는지 확인하려면, 다음과 같은 유틸성 함수를 만들 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun printWithThread(str: Any) {
  println(&quot;[${Thread.currentThread().name}] $str&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, IntelliJ IDE에서 vm option으로 &lt;code&gt;-Dkotlinx.coroutines.debug&lt;/code&gt; 를 주게 되면 어떤 코루틴에서 출력이 일어났는지 확인할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEKZtc/btssDQWrU6k/9zPaQwOzGW2CDv0kqM7MRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEKZtc/btssDQWrU6k/9zPaQwOzGW2CDv0kqM7MRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEKZtc/btssDQWrU6k/9zPaQwOzGW2CDv0kqM7MRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEKZtc%2FbtssDQWrU6k%2F9zPaQwOzGW2CDv0kqM7MRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;465&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 옵션을 사용하면, 출력 결과가 다음과 같이 변경된다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;[main @coroutine#1] START
[main @coroutine#1] END
[main @coroutine#2] 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 좋다~!   이번 시간에는 루틴과 코루틴의 차이에 대해 살펴보았다. 우리가 항상 직관적으로 사용했던 루틴과 다르게, 코루틴은 코드가 중간에서 멈추었다가 다시 시작할 수 있다는 특징이 핵심이다. 그런데 아직 코루틴을 완벽하게 파악한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 코루틴과 자주 비교되는 존재가 있는데 바로 &amp;lsquo;스레드&amp;rsquo;이다. 다음 시간에는 코루틴과 스레드가 어떤 차이를 갖는지 살펴보도록 하자.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴에 대해 더 알아보고 싶다면 아래 내용을 참고해주세요!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용 출처 : &lt;a href=&quot;https://inf.run/dLNp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://inf.run/dLNp&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693378507894&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;2시간으로 끝내는 코루틴 - 인프런 | 강의&quot; data-og-description=&quot;비동기 프로그래밍의 필수 라이브러리 코루틴! 코루틴의 개념, 사용법, 그리고 내부 원리까지 한 번에 얻어가세요!, 코틀린 비동기 프로그래밍 필수템!&amp;nbsp;코루틴, 2시간에 개념부터 실습까지 ⏰ [&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://inf.run/dLNp&quot; data-og-url=&quot;https://www.inflearn.com/course/2%EC%8B%9C%EA%B0%84%EC%9C%BC%EB%A1%9C-%EB%81%9D%EB%82%B4%EB%8A%94-%EC%BD%94%EB%A3%A8%ED%8B%B4?inst=b5a34442&amp;amp;utm_source=instructor&amp;amp;utm_medium=referral&amp;amp;utm_campaign=inflearn_%ED%8A%B8%EB%9E%98%ED%94%BD_promotion-link&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xvNd9/hyTMb7JxaX/5hHWWkiJ61vUzx0rzXAStK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ptyCo/hyTMcyMzWu/P9tXpJb62CSqCSAcG6calk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/fNWc9/hyTL32VNrH/1QwzxtK31vHh0VAyOWITVK/img.png?width=1890&amp;amp;height=698&amp;amp;face=0_0_1890_698&quot;&gt;&lt;a href=&quot;https://inf.run/dLNp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inf.run/dLNp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xvNd9/hyTMb7JxaX/5hHWWkiJ61vUzx0rzXAStK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ptyCo/hyTMcyMzWu/P9tXpJb62CSqCSAcG6calk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/fNWc9/hyTL32VNrH/1QwzxtK31vHh0VAyOWITVK/img.png?width=1890&amp;amp;height=698&amp;amp;face=0_0_1890_698');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;2시간으로 끝내는 코루틴 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;비동기 프로그래밍의 필수 라이브러리 코루틴! 코루틴의 개념, 사용법, 그리고 내부 원리까지 한 번에 얻어가세요!, 코틀린 비동기 프로그래밍 필수템!&amp;nbsp;코루틴, 2시간에 개념부터 실습까지 ⏰ [&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부 기록하기/01. JAVA &amp;amp; Kotlin</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/240</guid>
      <comments>https://lannstark.tistory.com/240#entry240comment</comments>
      <pubDate>Wed, 30 Aug 2023 15:55:31 +0900</pubDate>
    </item>
    <item>
      <title>코틀린 제네릭 - 배열과 리스트, 제네릭과 무공변</title>
      <link>https://lannstark.tistory.com/239</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간의 문제를 정리해 보면, 우리는 금붕어 Cage에서 금붕어를 꺼내 물고기 Cage에 옮길 수 없었다. 하지만 사실은, 그냥 금붕어를 물고기 Cage에 넣는 것은 아무 문제가 없다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val cage = Cage2&amp;lt;Fish&amp;gt;()
cage.put(GoldFish(&quot;금붕어&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물고기 Cage에 금붕어가 들어가는 것은 문제가 아니라는 의미이다. 그렇다면 도대체 왜 이런 차이가 발생하는 것일까??! 근본적인 이유를 알기 위해 우리는 &lt;b&gt;상속관계의 의미&lt;/b&gt;를 살펴보아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 상속 관계인 두 클래스가 있다. &lt;code&gt;Number&lt;/code&gt;와 &lt;code&gt;Int&lt;/code&gt;라고 해보자. &lt;code&gt;Number&lt;/code&gt;는 &lt;code&gt;Int&lt;/code&gt;의 상위 타입이고 &lt;code&gt;Int&lt;/code&gt;는 &lt;code&gt;Number&lt;/code&gt;의 하위 타입이다. 이때 다음과 같은 코드는 정상 동작할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun doSomething(num: Number) {
  // ...
}

val a: Int = 3
doSomething(a) // Number 타입에 Int 타입이 들어갔다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Number&lt;/code&gt;를 파라미터로 받는 함수에 &lt;code&gt;Int&lt;/code&gt;가 들어간 것이다! 즉, 두 클래스가 상속관계에 있을 때 하위 클래스는 상위 클래스 대신 함수 파라미터에 들어갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예시도 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;val intNum: Int = 5
val num: Number = intNum // Number 타입 변수에 Int 타입 변수가 들어갔다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 마찬가지로 &lt;code&gt;Number&lt;/code&gt; 타입 변수에 &lt;code&gt;Int&lt;/code&gt; 타입 변수가 들어갔는데, &lt;code&gt;Number&lt;/code&gt;가 상위 타입이고 &lt;code&gt;Int&lt;/code&gt;가 하위 타입이기에 가능하다. 즉 상속관계는 다음과 같은 의미를 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 타입이 들어가는 자리에 하위 타입이 대신 위치할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 &lt;code&gt;Cage2&lt;/code&gt; 코드를 찬찬히 뜯어보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val goldFishCage = Cage2&amp;lt;GoldFish&amp;gt;()
  goldFishCage.put(GoldFish(&quot;금붕어&quot;))

  val cage = Cage2&amp;lt;Fish&amp;gt;()
  cage.moveFrom(goldFishCage) // Error: Type Mismatch
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cage&lt;/code&gt;의 타입 파라미터는 &lt;code&gt;Fish&lt;/code&gt; 이므로 &lt;code&gt;cage.moveFrom(otherCage: Cage2&amp;lt;Fish&amp;gt;)&lt;/code&gt; 가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 함수에 넣으려는 &lt;code&gt;goldFishCage&lt;/code&gt;는 &lt;code&gt;Cage2&amp;lt;GoldFish&amp;gt;&lt;/code&gt; 타입을 가지고 있고, Type Mismatch 에러가 발생하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리는 &lt;code&gt;Fish&lt;/code&gt; 와 &lt;code&gt;GoldFish&lt;/code&gt; 는 상속관계이지만, &lt;code&gt;Cage2&amp;lt;Fish&amp;gt;&lt;/code&gt; 와 &lt;code&gt;Cage2&amp;lt;GoldFish&amp;gt;&lt;/code&gt; 는 아무 관계가 아니라는 것을 추측할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Cage2&amp;lt;Fish&amp;gt;&lt;/code&gt; 와 &lt;code&gt;Cage2&amp;lt;GoldFish&amp;gt;&lt;/code&gt; 가 아무 관계가 아니어서, Type Mismatch가 발생한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1MQ5e/btssqBGGVNK/jQc9QCDJNaLMKYmxmkPrbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1MQ5e/btssqBGGVNK/jQc9QCDJNaLMKYmxmkPrbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1MQ5e/btssqBGGVNK/jQc9QCDJNaLMKYmxmkPrbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1MQ5e%2FbtssqBGGVNK%2FjQc9QCDJNaLMKYmxmkPrbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;302&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 어려운 말로 가리켜 &amp;ldquo;Cage2는 무공변 (in-variant, 불공변)하다&amp;rdquo;고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 &lt;code&gt;Fish&lt;/code&gt;와 &lt;code&gt;GoldFish&lt;/code&gt;간의 상속관계가 제네릭 클래스에 유지되지 않는 것일까? 즉, 왜 제네릭 클래스는 타입 파라미터 간의 상속관계가 있더라도, 무공변할까? 그 근본적인 이유를 알기 위해서는 잠시 Cage를 잊고, Java 코드를 살펴보아야 한다. Java의 배열과 리스트를 비교해 보자. 먼저 Java의 배열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 배열은 제네릭과 다르다. Java의 배열에서는 A 객체가 B 객체의 하위 타입일 때, A 배열이 B 배열의 하위 타입으로 간주된다. 즉, &lt;code&gt;String&lt;/code&gt; 은 &lt;code&gt;Object&lt;/code&gt; 의 하위 타입이므로 &lt;code&gt;String[]&lt;/code&gt; 도 &lt;code&gt;Object[]&lt;/code&gt; 의 하위 타입으로 여겨진다. 이를 어려운 말로 &amp;ldquo;배열은 공변 (co-variant) 하다&amp;rdquo;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 공변하기 때문에 아래와 같은 코드가 가능하다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
  String[] strs = new String[]{&quot;A&quot;, &quot;B&quot;, &quot;C&quot;};
  // Object가 String의 상위 타입이므로 Object[]가 String[]의 상위 타입으로 간주된다.
  // 따라서 objs에 strs를 대입할 수 있다.
  Object[] objs = strs; 
  objs[0] = 1; // java.lang.ArrayStoreException: java.lang.Integer
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Object[]&lt;/code&gt; 는 사실 &lt;code&gt;String[]&lt;/code&gt; 인데 숫자를 넣을 수 있는 것이다! 이러한 동작은 런타임 때 예외를 낼 수 있기 때문에 굉장히 위험하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;code&gt;List&lt;/code&gt;는 다르다. &lt;code&gt;List&lt;/code&gt;는 제네릭을 사용하고 있고, 불공변 하기 때문에 컴파일 때 오류를 바로 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
  List&amp;lt;String&amp;gt; strs = List.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);
  List&amp;lt;Object&amp;gt; objs = strs; // Type Mismatch
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 제네릭도 배열과 같은 원리로 동작했다면, List 역시 배열과 같은 결함을 가지게 되고 타입-안전하지 않은 코딩을 해야 했을 것이다. 제네릭은 이런 결함 자체를 막기 위해 무공변하도록 만들어졌다. 또한 이러한 차이가 바로, 배열보다 리스트를 사용하라는 격언의 배경이기도 하다. (Effective Java #3 Item28)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 좋다~!   우리는 왜 금붕어 Cage에서 물고기 Cage로 금붕어를 옮기지 못하는지 이해했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 문제가 해결된 것은 아니다. 분명 물고기 Cage에 금붕어를 넣을 수 있는데도 금붕어 Cage에서 물고기 Cage로 금붕어를 옮기지 못하는 것은 이상하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 하면 이 문제를 해결할 수 있을까?! 바로 &lt;code&gt;moveFrom&lt;/code&gt; 함수를 호출할 때 &lt;code&gt;Fish&lt;/code&gt; 와 &lt;code&gt;GoldFish&lt;/code&gt; 의 상속관계를 &lt;code&gt;Cage2&amp;lt;Fish&amp;gt;&lt;/code&gt; 와 &lt;code&gt;Cage2&amp;lt;GoldFish&amp;gt;&lt;/code&gt; 에도 이어주는 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 어려운 말로, &amp;ldquo;공변(co-variant)하도록 만든다&amp;rdquo; 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변에 대해 더 궁금하시다면 아래 링크를 확인해보세요~!! &lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용 출처 : &lt;a href=&quot;https://inf.run/F9gX&quot;&gt;https://inf.run/F9gX&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693378299492&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코틀린 고급편 - 인프런 | 강의&quot; data-og-description=&quot;코틀린의 모든 언어적 특성을 이해할 수 있습니다. 강의를 들으신 후 제네릭, 위임과 지연, DSL과 리플렉션 등 코틀린 고급 기술을 활용해 마음껏 프로그래밍하실 수 있습니다., 남들보다 깊은 코&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://inf.run/F9gX&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B3%A0%EA%B8%89%ED%8E%B8?inst=099d3070&amp;amp;utm_source=instructor&amp;amp;utm_medium=referral&amp;amp;utm_campaign=inflearn_%ED%8A%B8%EB%9E%98%ED%94%BD_promotion-link&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JURvY/hyTMcS6tUh/fvmy5rYFv979lzSTqiw8C0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dI7R7F/hyTMeciMz4/mQydk38kilEryhbHmAsZVk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bifZ80/hyTL6FkMrQ/FZq6qF0IpQreeZrmK2Q88k/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=0_0_1440_810&quot;&gt;&lt;a href=&quot;https://inf.run/F9gX&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inf.run/F9gX&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JURvY/hyTMcS6tUh/fvmy5rYFv979lzSTqiw8C0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dI7R7F/hyTMeciMz4/mQydk38kilEryhbHmAsZVk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bifZ80/hyTL6FkMrQ/FZq6qF0IpQreeZrmK2Q88k/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=0_0_1440_810');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코틀린 고급편 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코틀린의 모든 언어적 특성을 이해할 수 있습니다. 강의를 들으신 후 제네릭, 위임과 지연, DSL과 리플렉션 등 코틀린 고급 기술을 활용해 마음껏 프로그래밍하실 수 있습니다., 남들보다 깊은 코&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부 기록하기/01. JAVA &amp;amp; Kotlin</category>
      <category>코틀린</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/239</guid>
      <comments>https://lannstark.tistory.com/239#entry239comment</comments>
      <pubDate>Wed, 30 Aug 2023 15:52:35 +0900</pubDate>
    </item>
    <item>
      <title>코틀린 제네릭 - 제네릭과 타입 파라미터</title>
      <link>https://lannstark.tistory.com/238</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 추상화를 하는데 있어 굉장히 필수적인 기능이고, 정말 잘 활용되는 언어적 특징이다. 제네릭을 이해하기 위해 Cage 클래스를 만들어 보자. Cage 에는 여러 동물을 넣거나 꺼낼 수 있다. 가장 간단한 Cage 클래스는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class Cage {
  private val animals: MutableList&amp;lt;Animal&amp;gt; = mutableListOf()

  fun getFirst(): Animal {
    return animals.first()
  }

  fun put(animal: Animal) {
    this.animals.add(animal)
  }

  fun moveFrom(cage: Cage) {
    this.animals.addAll(cage.animals)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 &lt;code&gt;Animal&lt;/code&gt; 클래스도 필요하니 간단하게 다음과 같이 금붕어, 잉어를 만들어 주자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;abstract class Animal(
  val name: String,
)

abstract class Fish(name: String) : Animal(name)

// 금붕어
class GoldFish(name: String) : Fish(name)

// 잉어
class Carp(name: String) : Fish(name)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 그림으로 나타내면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1073&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MACZi/btssDLHABX9/LLZLnQsKCZSAKlf2XF9AG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MACZi/btssDLHABX9/LLZLnQsKCZSAKlf2XF9AG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MACZi/btssDLHABX9/LLZLnQsKCZSAKlf2XF9AG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMACZi%2FbtssDLHABX9%2FLLZLnQsKCZSAKlf2XF9AG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;310&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1073&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Cage&lt;/code&gt; 에 잉어를 넣었다 잉어를 빼보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val cage = Cage()
  cage.put(Carp(&quot;잉어&quot;))
  val carp: Carp = cage.getFirst() // Error: Type Mismatch
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앗! &lt;code&gt;Error: Type Mismatch&lt;/code&gt; 가 발생한다. 생각해 보면 당연하다. 우리는 Cage를 만들어 잉어를 넣었다가 바로 가져오려 했는데, &lt;code&gt;Cage&lt;/code&gt; 클래스의 &lt;code&gt;getFirst()&lt;/code&gt; 함수 반환 타입은 &lt;code&gt;Animal&lt;/code&gt; 이기 때문에 바로 &lt;code&gt;Carp&lt;/code&gt; 타입을 가져올 수 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러를 해결하기 위해서는 어떻게 해야 할까?! 가장 간단한 방법은 타입 캐스팅(type casting)을 하는 것이다. Kotlin에서는 &lt;code&gt;as&lt;/code&gt; 키워드를 이용해 타입 캐스팅을 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val cage = Cage()
  cage.put(Carp(&quot;잉어&quot;))
  val carp: Carp = cage.getFirst() as Carp
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 바로 잉어 타입을 가져올 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그런데 사실 위 코드는 매우 위험한 코드이다. 지금 당장은 잉어로 타입 캐스팅을 해도 문제가 없어 보이지만, 누군가 &lt;code&gt;Cage&lt;/code&gt;에 &lt;code&gt;GoldFish&lt;/code&gt; 를 넣을 수도 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val cage = Cage()
  cage.put(GoldFish(&quot;사실은 금붕어&quot;))
  val carp: Carp = cage.getFirst() as Carp // 에러가 나지 않는다!!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 두 번째 줄을 &lt;code&gt;cage.put(GoldFish(&quot;금붕어&quot;))&lt;/code&gt; 라고 바꾸었을 때 실제 오류는 런타임이 돼서야 발견하게 된다. 지금은 한 함수 내에서 금붕어를 넣고 잉어를 빼니 금방 발견할 수 있지만, 실제로는 복잡한 코드 내에서 이런 버그를 찾기란 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떻게 타입 안전하게 잉어를 가져올 수 있을까??! 한 가지 방법은 Kotlin의 안전한 타입 캐스팅(safe type casting)과 엘비스 연산자(elivs operator)를 활용하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val cage = Cage()
  cage.put(Carp(&quot;잉어&quot;))
  val carp: Carp = cage.getFirst() as? Carp
    ?: throw IllegalArgumentException()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법 역시 &lt;code&gt;as&lt;/code&gt; 를 사용했을 때와 마찬가지로 여전히 실수할 여지가 있고, 실수로 금붕어가 Cage에 들어가면 에러가 발생한다. 우리는 아예 에러가 발생하지 않았으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면, 동일한 Cage 클래스이지만 잉어만 넣을 수 있는 Cage, 금붕어만 넣을 수 있는 Cage를 구분하는 방법은 어떨까? 이 방법을 사용하면 타입 안전하게 잉어를 Cage에 넣었다가 잉어 타입으로 가져올 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 바로 &lt;b&gt;제네릭&lt;/b&gt;을 사용할 수 있다. &lt;code&gt;Cage&lt;/code&gt; 클래스를 다시 한 번 만들어 보자. 이때 특정 타입만 Cage가 받을 수 있도록 처리하기 위해 &lt;b&gt;타입 파라미터&lt;/b&gt;를 사용할 것이다. 타입 파라미터를 클래스에 적용하는 방법은 간단하다. 클래스 뒤에 &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt; 를 이용해 대문자를 적어주면 된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Cage2&amp;lt;T&amp;gt; {

  private val animals: MutableList&amp;lt;T&amp;gt; = mutableListOf()

  fun getFirst(): T {
    return animals.first()
  }

  fun put(animal: T) {
    this.animals.add(animal)
  }

  fun moveFrom(cage: Cage2&amp;lt;T&amp;gt;) {
    this.animals.addAll(cage.animals)
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;Cage2&lt;/code&gt; 처럼 타입 파라미터가 적용된 클래스를 제네릭 클래스라 부르고, &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt; 에 들어간 &lt;code&gt;T&lt;/code&gt; 를 타입 파라미터라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Cage2&lt;/code&gt; 를 제네릭 클래스로 만들며, &lt;code&gt;getFirst()&lt;/code&gt;, &lt;code&gt;put()&lt;/code&gt;, &lt;code&gt;moveFrom()&lt;/code&gt; 함수에도 모두 &lt;code&gt;T&lt;/code&gt; 가 들어가게 되었다. 이렇게 되면, &lt;code&gt;Cage2&lt;/code&gt; 클래스를 인스턴스화 할 때 타입 정보를 넣어주어야 하고 그때 넣어준 타입 정보가 모두 T를 대체한다는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qEDpP/btssvwLW4Gx/5oWP2cRoOKRPqrSsXsXjTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qEDpP/btssvwLW4Gx/5oWP2cRoOKRPqrSsXsXjTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qEDpP/btssvwLW4Gx/5oWP2cRoOKRPqrSsXsXjTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqEDpP%2FbtssvwLW4Gx%2F5oWP2cRoOKRPqrSsXsXjTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;283&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 살펴보았던 예제에 제네릭 클래스인 Cage2를 바로 적용해 보자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val cage = Cage2&amp;lt;Carp&amp;gt;()
  cage.put(Carp(&quot;잉어&quot;))
  // 이제 as Carp 없이도 getFirst 메소드를 호출하면 바로 Carp가 나온다!!!
  val carp: Carp = cage.getFirst()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭을 활용한 덕분에 타입 안전하게 잉어를 가져올 수 있게 되었다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이런 제네릭 클래스는 List나 Collection 같은 자바 표준 라이브러리, 코틀린 표준 라이브러리에서 활발하게 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 다음 요구사항을 생각해 보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금붕어 Cage에 금붕어 한 마리를 넣고, 물고기 Cage에 &lt;code&gt;moveFrom&lt;/code&gt; 메소드를 사용해 금붕어를 옮기자!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 요구사항을 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
  val goldFishCage = Cage2&amp;lt;GoldFish&amp;gt;()
  goldFishCage.put(GoldFish(&quot;금붕어&quot;))

  val cage = Cage2&amp;lt;Fish&amp;gt;()
  cage.moveFrom(goldFishCage) // Error: Type Mismatch
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그런데! 분명, 금붕어는 물고기 Cage에 들어갈 수 있을 것 같음에도 &lt;code&gt;cage.moveFrom(goldFishCage)&lt;/code&gt; 는 Type Mismatch 에러가 발생한다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에 왜 이런 에러가 발생하는지, 어떻게 해결할 수 있는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용 출처: &lt;a href=&quot;https://inf.run/F9gX&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://inf.run/F9gX&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693378212511&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코틀린 고급편 - 인프런 | 강의&quot; data-og-description=&quot;코틀린의 모든 언어적 특성을 이해할 수 있습니다. 강의를 들으신 후 제네릭, 위임과 지연, DSL과 리플렉션 등 코틀린 고급 기술을 활용해 마음껏 프로그래밍하실 수 있습니다., 남들보다 깊은 코&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://inf.run/F9gX&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B3%A0%EA%B8%89%ED%8E%B8?inst=099d3070&amp;amp;utm_source=instructor&amp;amp;utm_medium=referral&amp;amp;utm_campaign=inflearn_%ED%8A%B8%EB%9E%98%ED%94%BD_promotion-link&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JURvY/hyTMcS6tUh/fvmy5rYFv979lzSTqiw8C0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dI7R7F/hyTMeciMz4/mQydk38kilEryhbHmAsZVk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bifZ80/hyTL6FkMrQ/FZq6qF0IpQreeZrmK2Q88k/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=0_0_1440_810&quot;&gt;&lt;a href=&quot;https://inf.run/F9gX&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inf.run/F9gX&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JURvY/hyTMcS6tUh/fvmy5rYFv979lzSTqiw8C0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dI7R7F/hyTMeciMz4/mQydk38kilEryhbHmAsZVk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bifZ80/hyTL6FkMrQ/FZq6qF0IpQreeZrmK2Q88k/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=0_0_1440_810');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코틀린 고급편 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코틀린의 모든 언어적 특성을 이해할 수 있습니다. 강의를 들으신 후 제네릭, 위임과 지연, DSL과 리플렉션 등 코틀린 고급 기술을 활용해 마음껏 프로그래밍하실 수 있습니다., 남들보다 깊은 코&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부 기록하기/01. JAVA &amp;amp; Kotlin</category>
      <category>코틀린</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/238</guid>
      <comments>https://lannstark.tistory.com/238#entry238comment</comments>
      <pubDate>Wed, 30 Aug 2023 15:50:30 +0900</pubDate>
    </item>
    <item>
      <title>[css] 넘쳐 흐르는 텍스트 처리 주요 속성 정리</title>
      <link>https://lannstark.tistory.com/237</link>
      <description>&lt;h3&gt;overflow&lt;/h3&gt;
&lt;p&gt;자식이 부모의 범위를 벗어날 경우 어떻게 할 것인지 제어하는 css&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;overlfow: visible | hidden | scroll | auto&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;visible : 그냥 그대로 둔다. 넘치면 넘치는 대로 존재한다.&lt;/li&gt;
&lt;li&gt;hidden : 넘친 부분은 보이지 않는다.&lt;/li&gt;
&lt;li&gt;scroll : 스크롤이 생긴다. (가로 / 세로 모두 생긴다.)&lt;/li&gt;
&lt;li&gt;auto : 넘친 경우, 자동으로 스크롤이 생긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;text-overflow&lt;/h3&gt;
&lt;p&gt;넘치는 텍스트 표기를 제어하는 css&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;text-overflow: clip | ellipsis;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;text-overflow 속성은 overflow 속성이 hidden / scroll / auto 이면서 white-space: nowrap일 경우만 적용된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clip : 넘치는 텍스트를 자른다.&lt;/li&gt;
&lt;li&gt;ellipsis : 말 줄임표로 잘린 텍스트가 있다고 표시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;white-space&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;white-space: normal | nowrap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기본값은 &lt;code&gt;normal&lt;/code&gt; 이다. &lt;code&gt;normal&lt;/code&gt; 로 설정되면, 연속한 띄어쓰기, 들여쓰기, 그리고 줄바꿈 문자가 모두 무시된다. 또한, 텍스트가 길어 부모 요소의 가로 폭을 넘어가면 자동으로 줄바꿈을 해준다. 따라서 수동으로 줄바꿈을 할 수 있는 방법이 없다. (&lt;code&gt;&amp;amp;nbsp;&lt;/code&gt; 같은 HTML 엔티티 편법을 사용해야 함)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nowrap&lt;/code&gt; : 가로폭을 넘어가도 줄바꿈이 일어나지 않는다. 다만, 그 외에는 모두 &lt;code&gt;normal&lt;/code&gt; 과 동일하다. 연속된 띄어쓰기, 들여쓰기, 줄바꿈 문자를 모두 무시한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pre&lt;/code&gt; : 텍스트에 포함된 연속된 띄어쓰기, 들여쓰기, 줄바꿈과 같은 공백 문자들을 HTML 안에 그대로 보여지게 하는 옵션.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pre-wrap&lt;/code&gt; : &lt;code&gt;pre&lt;/code&gt; 와 동일하지만, 긴 행이 있다면 자동으로 줄바꿈을 해준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pre-line&lt;/code&gt; : 줄바꿈만 유지하면서 연속된 띄어쓰기와 들여쓰기를 무시하고 싶을 때 사용&lt;/p&gt;
&lt;h3&gt;자주 사용되는 조합&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;텍스트가 빠져나가는 경우 말줄임 기호 보여주기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;white-space: nowrap;
overflow: hidden;
text-overflow: elipsis;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;가로 스크롤바를 사용할 수 있게 해주기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;white-space: nowrap;
overflow: auto;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발 공부 기록하기/07. react.js &amp;amp; vue.js</category>
      <category>CSS</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/237</guid>
      <comments>https://lannstark.tistory.com/237#entry237comment</comments>
      <pubDate>Wed, 30 Aug 2023 15:10:50 +0900</pubDate>
    </item>
    <item>
      <title>R2DBC pooling yml로 설정하기</title>
      <link>https://lannstark.tistory.com/236</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;의존성&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;implementation(&quot;io.asyncer:r2dbc-mysql:1.0.2&quot;) // R2DBC MySQL Driver
implementation(&quot;io.r2dbc:r2dbc-h2:1.0.0.RELEASE&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 tricky 하게 yml로 설정한 auto configuration을 이용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url 부분에 &quot;pool&quot;이 들어가지 않는 것이 핵심!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;r2dbc pooling 공식문서에서 얘기하는 &quot;r2dbc:pool:mysql&quot;을 사용하는 방법은 Configuration을 (yml을 사용하지 않고) 직접 해줄 때 사용하는 방법이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;spring:
    r2dbc:
    url: 'r2dbc:mysql://localhost:3306/database?option1=value1'
    username: 'root'
    password: ''
    pool:
      enabled: true
      initial-size: 10
      max-acquire-time: 5s
      max-create-connection-time: 5s
      max-idle-time: 60m # max-wait-time 이랑 같이 챙겨 줘야 함.
      max-life-time: 60m
      max-size: 10

logging:
  level:
    io.r2dbc.pool: DEBUG
    reactor.pool: DEBUG&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;io.r2dbc.pool.ConenctionPool&lt;/code&gt;은 &lt;code&gt;ConnectionFactory&lt;/code&gt; 를 구현했고 내부적으로 다른 &lt;code&gt;ConnectionFactory&lt;/code&gt; 를 갖고 있음. 일종의 Decorator pattern&lt;/li&gt;
&lt;li&gt;그리고 내부적으로 &lt;code&gt;reactor.pool.InstrumentedPool&lt;/code&gt;를 갖고 있는데 (구현체는 같은 패키지의 &lt;code&gt;SimpleDequePool&lt;/code&gt;) 실제 connection 취득은 다른 factory를 / pooling은 이 InstrumentedPool을 사용하고 있다.&lt;/li&gt;
&lt;li&gt;SQL &lt;code&gt;show status like 'Threads_connected';&lt;/code&gt; 을 통해 정상적으로 10개의 connection pooling이 되는 것을 확인할 수 있음.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 공부 기록하기/02. DB &amp;amp; SQL</category>
      <category>r2dbc</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/236</guid>
      <comments>https://lannstark.tistory.com/236#entry236comment</comments>
      <pubDate>Mon, 28 Aug 2023 23:26:47 +0900</pubDate>
    </item>
    <item>
      <title>코틀린 + 스프링 ArgumentResolver 주의할 점</title>
      <link>https://lannstark.tistory.com/235</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 타입의 nullable 여부를 확실하게 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;Long&lt;/code&gt; 타입과 &lt;code&gt;Long?&lt;/code&gt; 타입은 java의 (primitive type) &lt;code&gt;long&lt;/code&gt; 과 (reference type) &lt;code&gt;Long&lt;/code&gt; 으로 컴파일 된다. 이런 이유로 스프링 ArgumentResolver에서 &lt;code&gt;supportsParameter&lt;/code&gt; 를 구현할 때 다음과 같은 주의를 기울여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 우리가 resolving 하려는 타입이 코틀린의 &lt;code&gt;Long&lt;/code&gt; 인지 확인한다. ArgumentResolver를 활용할 때 흔히 작성하는 코드다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;override fun supportsParameter(parameter: MethodParameter): Boolean { 
  return parameter.getParameterAnnotation(커스텀어노테이션::class.java) != null &amp;amp;&amp;amp;
    Long::class.java.isAssignableFrom(parameter.parameterType)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 코드는 다음 경우에만 동작한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun controllerMethod(@커스텀어노테이션 value: Long) {
  println(value) // 값이 정상적으로 들어 있다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 value의 타입이 &lt;code&gt;Long&lt;/code&gt; 이 아닌 &lt;code&gt;Long?&lt;/code&gt; 으로 된다면, 동작하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun controllerMethod(@커스텀어노테이션 value: Long?) {
  println(value) // 값이 정상적으로 들어 있지 않다!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는, &lt;code&gt;Long::class.java.isAssignableFrom(parameter.parameterType)&lt;/code&gt; 코드에서 &lt;code&gt;parameter.parameterType&lt;/code&gt; 이 &lt;code&gt;Long&lt;/code&gt; 인지 &lt;code&gt;Long?&lt;/code&gt;인지에 따라 다르게 true / false를 반환하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Long?&lt;/code&gt; 타입에서도 ArgumentResolver를 활용하고 싶다면 코틀린의 Long (&lt;code&gt;Long::class.java&lt;/code&gt;) 대신 자바의 Long을 사용하도록 변경하면 된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;override fun supportsParameter(parameter: MethodParameter): Boolean { 
  return parameter.getParameterAnnotation(커스텀어노테이션::class.java) != null &amp;amp;&amp;amp;
    Class.forName(&quot;java.lang.Long&quot;).isAssignableFrom(parameter.parameterType)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, &lt;code&gt;Long?&lt;/code&gt; 타입에도 값이 정상적으로 들어온다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun controllerMethod(@커스텀어노테이션 value: Long?) {
  println(value) // 값이 잘 들어온다~~!  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1줄 요약 : Kotlin + Spring에서 ArgumentResolver는 argument의 nullable 여부에 따라 적절한 파라미터 타입 비교가 필요하다.&lt;/p&gt;</description>
      <category>개발 공부 기록하기/01. JAVA &amp;amp; Kotlin</category>
      <category>react</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/235</guid>
      <comments>https://lannstark.tistory.com/235#entry235comment</comments>
      <pubDate>Sat, 26 Aug 2023 08:05:42 +0900</pubDate>
    </item>
    <item>
      <title>[React-Router-Dom v6] useParam undefined 번거로움 해결</title>
      <link>https://lannstark.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;useParam() 훅을 사용할 때 반환되는 값은 &amp;lt;string | undefined&amp;gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 입장에서는 URI 경로에 있는 값을 가져올 때 존재하지 않을 수도 있으니, 당연히 undefined로 반환해야 하지만, 라이브러리를 사용하는 입장에서는 확실히 존재함을 보장할 수 있음에도 계속해서 undefined 아님 단언 (&lt;code&gt;!&lt;/code&gt;)을 사용하는 것이 번거롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 다음과 같은 방법을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;const {key} = useParams() as { key: string };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, key가 &amp;lt;string | undefined&amp;gt;가 아닌 string 임을 확실히 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물런 매번 &lt;code&gt;!&lt;/code&gt; 를 사용할 것인지 아니면 &lt;code&gt;as&lt;/code&gt; 라는 위험할 수 있는(?) 형 변환을 사용할 것인지의 trade-off가 있을 것 같다.&lt;/p&gt;</description>
      <category>개발 공부 기록하기/07. react.js &amp;amp; vue.js</category>
      <category>react</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/234</guid>
      <comments>https://lannstark.tistory.com/234#entry234comment</comments>
      <pubDate>Thu, 24 Aug 2023 18:25:03 +0900</pubDate>
    </item>
    <item>
      <title>[HTML] 숫자 타입 input 스크롤 이슈 해결</title>
      <link>https://lannstark.tistory.com/233</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blVwan/btsr4ZTyDsX/TK1F9WKCEwInSGqYOLhwu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blVwan/btsr4ZTyDsX/TK1F9WKCEwInSGqYOLhwu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blVwan/btsr4ZTyDsX/TK1F9WKCEwInSGqYOLhwu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblVwan%2Fbtsr4ZTyDsX%2FTK1F9WKCEwInSGqYOLhwu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;100&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자 타입 input에 자동으로 생기는 화살표를 제거하고 싶다면. 아래 css를 추가하면 된다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input[type=number] 부분은 적절히 다른 select로 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number type을 클릭하고 커서를 올린 채 wheel 이벤트를 발생시키면 숫자가 오르락 내리락 하는데, 이를 막고 싶다면 TypeScript + React에서는 다음과 같이 하면 된다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;NumberInput
  style={{marginTop: &quot;16px&quot;}}
  type={&quot;number&quot;}
  value={state}
  onChange={onChanged}
  onWheel={event =&amp;gt; (event.target as HTMLElement).blur()}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 onWheel 이벤트가 발생하는 경우, blur 시키는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부 기록하기/07. react.js &amp;amp; vue.js</category>
      <category>HTML</category>
      <author>lannstark</author>
      <guid isPermaLink="true">https://lannstark.tistory.com/233</guid>
      <comments>https://lannstark.tistory.com/233#entry233comment</comments>
      <pubDate>Wed, 23 Aug 2023 16:06:28 +0900</pubDate>
    </item>
  </channel>
</rss>