<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ogoons.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ogoons.github.io/" rel="alternate" type="text/html" hreflang="ko" /><updated>2026-10-14T09:42:09+00:00</updated><id>https://ogoons.github.io/feed.xml</id><title type="html">담백로그</title><subtitle>안드로이드 개발 노트와 직접 만든 앱 소개
</subtitle><author><name>OGOONS</name></author><entry><title type="html">[Apps] 직접 만든 앱, 알고와 HALO 를 소개합니다.</title><link href="https://ogoons.github.io/apps-intro" rel="alternate" type="text/html" title="[Apps] 직접 만든 앱, 알고와 HALO 를 소개합니다." /><published>2026-10-14T00:00:00+00:00</published><updated>2026-10-14T00:00:00+00:00</updated><id>https://ogoons.github.io/apps-intro</id><content type="html" xml:base="https://ogoons.github.io/apps-intro"><![CDATA[<p>안드로이드 앱 두 개를 만들어 나가고 있습니다.
대 AI 시대인 요즘 봇물 터지듯 많은 앱들이 양산되고 있지만, 
제가 만들어 나가는 앱들은 코드뿐 아니라 깔끔한 디자인, 기능의 배치와 동선 등 저의 많은 고민을 녹였습니다.</p>

<hr />

<div class="app-header">
<img class="app-icon" src="/assets/images/algo-icon.png" alt="알고 앱 아이콘" width="72" height="72" />
<h2>알고 · 자동화 앱</h2>
</div>

<blockquote>
  <p>“이럴 땐 이렇게 해줘”를 직접 만들어 볼 수 있습니다.</p>
</blockquote>

<p>알고는 시간, 장소, Wi-Fi, 블루투스 같은 <strong>상황(When / If)</strong> 을 감지해서, 화면 밝기나 볼륨, 진동, 알림 같은 <strong>동작(Then)</strong> 을 자동으로 실행합니다.
블록을 쌓듯 루틴을 만들 수 있어서 코딩 지식은 필요 없어요.</p>

<h3 id="핵심-기능">핵심 기능</h3>

<ul>
  <li><strong>다양한 상황 감지</strong>: 시간, 요일과 시간, 날짜, 타이머, 장소, Wi-Fi, 블루투스, NFC 태그, 배터리 잔량</li>
  <li><strong>다양한 동작 실행</strong>: 화면 밝기, 화면 꺼짐 시간, 볼륨, 진동, 알림 띄우기, 음성으로 읽어주기</li>
  <li><strong>조건 분기</strong>: “If 조건이 맞으면 Then, 아니면 Else”처럼 조건에 따라 다르게 움직이는 루틴</li>
  <li><strong>루틴끼리 연결</strong>: 다른 루틴을 켜고 끄는 것도 가능</li>
  <li><span class="hue-badge"><img class="hue-white" src="/assets/images/philips-hue-light.svg" alt="필립스 휴 로고" /><img class="hue-black" src="/assets/images/philips-hue-dark.svg" alt="필립스 휴 로고" /></span> <strong>스마트 조명 연동</strong>: Philips Hue 조명의 밝기·장면 제어</li>
  <li><strong>일출·일몰 계산</strong>: 해가 뜨고 지는 시간에 맞춰 동작</li>
</ul>

<h3 id="이럴-때-써-보세요">이럴 때 써 보세요</h3>

<ul>
  <li><strong>집에 도착하면</strong>: 집 Wi-Fi에 연결되면 볼륨은 줄이고 화면 밝기는 낮춰요.</li>
  <li><strong>출근 준비</strong>: 평일 아침 7시에 진동으로 깨우고, 음성으로 오늘 할 일을 읽어줘요.</li>
  <li><strong>해 질 무렵</strong>: 해가 지면 화면 밝기를 낮추거나 HALO 를 통해 화면 색온도를 조절하고, 집안에 스마트 조명을 켭니다.</li>
</ul>

<p><a class="download-button" href="https://play.google.com/store/apps/details?id=com.ogoons.algo" target="_blank">다운로드 하러가기</a></p>

<hr />

<div class="app-header">
<img class="app-icon" src="/assets/images/halo-icon.png" alt="HALO 앱 아이콘" width="72" height="72" />
<h2>HALO · 밤에 쉬는 눈</h2>
</div>

<blockquote>
  <p>자기 전에 폰을 보는 습관이 있다면, HALO가 눈을 편하게 해 줍니다.</p>
</blockquote>

<p>HALO는 화면 위에 달빛을 연상 시키는 따뜻한 색의 필터를 얹어서 눈부심을 줄여 줍니다.
정해 둔 시간에 자동으로 켜지게 할 수도 있어요. 현재 누적 다운로드 80만 이상의 검증된 앱 입니다.</p>

<h3 id="핵심-기능-1">핵심 기능</h3>

<ul>
  <li><strong>야간 모드 예약</strong>: 시작 시간과 종료 시간을 정하면 자동으로 켜지고 꺼져요.</li>
  <li><strong>필터 강도와 밝기 조절</strong>: 내 눈에 맞게 필터를 진하게 또는 연하게 맞춰요.</li>
  <li><strong>색온도 조절</strong>: 따뜻한 정도를 켈빈(K) 단위로 조절해요.</li>
  <li><strong>알림창에서 빠르게 켜고 끄기</strong>: 알림창 버튼 하나로 야간 모드를 바로 바꿔요.</li>
  <li><strong>상태바와 잠금 화면에도 적용</strong>: 원하면 알림창과 잠금 화면 위에도 필터를 씌울 수 있어요.</li>
  <li><strong>집에서만 켜기</strong>: 지정한 장소에 있을 때만 필터가 켜지도록 설정할 수 있어요.</li>
</ul>

<h3 id="이럴-때-써-보세요-1">이럴 때 써 보세요</h3>

<ul>
  <li><strong>잠들기 전</strong>: 밤 11시부터 아침 7시까지만 자동으로 켜 두면 신경 쓸 게 없어요.</li>
  <li><strong>밤늦게 침대에서</strong>: 알림창에서 한 번 눌러 필터를 켜고, 밝기만 살짝 낮춰요.</li>
  <li><strong>집에서만 편하게</strong>: 집에 있을 때만 필터가 켜지도록 해서 밖에서는 원래 화면 그대로 써요.</li>
</ul>

<p><a class="download-button" href="https://play.google.com/store/apps/details?id=com.ogoons.halo" target="_blank">다운로드 하러가기</a></p>

<hr />

<h2 id="알고-x-halo--함께-쓰면-더-편해요">알고 X HALO · 함께 쓰면 더 편해요</h2>

<blockquote>
  <p>알고 루틴에서 HALO의 야간 모드를 직접 제어할 수 있어요.</p>
</blockquote>

<p>알고의 동작 목록에서 <strong>HALO 야간 모드</strong>를 찾아서 추가해 보세요. 시간이나 특정 루틴을 구성해서 HALO를 자동으로 켜고 끌 수 있습니다.</p>

<h3 id="알고에서-제어-가능한-기능">알고에서 제어 가능한 기능</h3>

<ul>
  <li><strong>켜기·끄기</strong>: 야간 모드를 켜고 끌 수 있어요.</li>
  <li><strong>필터 세기</strong>: 필터를 얼마나 진하게 할지 정해요.</li>
  <li><strong>화면 밝기</strong>: 화면 밝기를 정해요.</li>
  <li><strong>필터 색</strong>: HALO의 색 목록에서 골라요.</li>
</ul>

<p>HALO 제어 기능은 모두 무료입니다.</p>

<h3 id="이럴-때-써-보세요-2">이럴 때 써 보세요</h3>

<ul>
  <li><strong>화면이 꺼지면</strong>: 화면이 꺼질 때 HALO 야간 모드를 자동으로 켜요.</li>
  <li><strong>아침 7시</strong>: 매일 아침 7시에 야간 모드를 자동으로 꺼요.</li>
  <li><strong>유튜브 앱을 켰을 때</strong>: 영상을 볼 때만 HALO 야간 모드로 전활할 수 있어요.</li>
</ul>

<hr />

<p>앱을 써 보시고 불편한 점이나 아이디어가 있다면 언제든 알려 주세요. 🙌</p>]]></content><author><name>OGOONS</name></author><category term="Apps" /><summary type="html"><![CDATA[안드로이드 앱 두 개를 만들어 나가고 있습니다. 대 AI 시대인 요즘 봇물 터지듯 많은 앱들이 양산되고 있지만, 제가 만들어 나가는 앱들은 코드뿐 아니라 깔끔한 디자인, 기능의 배치와 동선 등 저의 많은 고민을 녹였습니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/algo-icon.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/algo-icon.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Kotlin] Sealed Class and Sealed Interface</title><link href="https://ogoons.github.io/sealed-class-and-sealed-interface" rel="alternate" type="text/html" title="[Kotlin] Sealed Class and Sealed Interface" /><published>2023-10-19T00:00:00+00:00</published><updated>2023-10-19T00:00:00+00:00</updated><id>https://ogoons.github.io/sealed-class-and-sealed-interface</id><content type="html" xml:base="https://ogoons.github.io/sealed-class-and-sealed-interface"><![CDATA[<p>코틀린의 <code class="language-plaintext highlighter-rouge">sealed class</code> 와 코틀린 1.5에서 추가된 <code class="language-plaintext highlighter-rouge">sealed interface</code> 에 대해서 알아봅니다.</p>

<h2 id="기존-enum-class-의-단점">기존 enum class 의 단점</h2>

<p>Java 의 <code class="language-plaintext highlighter-rouge">enum</code>, Kotlin 의 <code class="language-plaintext highlighter-rouge">enum class</code> 는 내부 정의된 상수의 타입이 단일 인스턴스 하나로 한정되어 있습니다.</p>

<p><code class="language-plaintext highlighter-rouge">UiState</code> 를 열거한 <code class="language-plaintext highlighter-rouge">enum class</code> 를 예를 들어보겠습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="kd">class</span> <span class="nc">UiState</span> <span class="p">{</span>
    <span class="nc">LOADING</span><span class="p">,</span>
    <span class="nc">SUCCESS</span><span class="p">(</span><span class="kd">val</span> <span class="py">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">),</span> <span class="c1">// Error</span>
    <span class="nc">ERROR</span><span class="p">(</span><span class="kd">val</span> <span class="py">error</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="c1">// Error</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위 예제와 같이 상수의 타입을 별도로 정의할 수 있는 구조가 아니기 때문에, <code class="language-plaintext highlighter-rouge">LOADING</code> 과 달리 <code class="language-plaintext highlighter-rouge">SUCCESS</code>, <code class="language-plaintext highlighter-rouge">ERROR</code> 타입은 컴파일 에러가 발생합니다.</p>

<p>이와 같은 단점을 보완하게 위해 등장한 것이 <code class="language-plaintext highlighter-rouge">sealed class</code> 입니다.</p>

<h2 id="sealed-class">sealed class</h2>

<p><code class="language-plaintext highlighter-rouge">enum class</code> 와 기능이 유사하지만 다양한 하위 타입을 가질 수 있으며, 그 타입을 한정하여 컴파일러에게 알려주기 위한 기능을 추가적으로 가지고 있습니다.<br />
하위 타입은 일반 <code class="language-plaintext highlighter-rouge">class</code>, <code class="language-plaintext highlighter-rouge">data class</code>, <code class="language-plaintext highlighter-rouge">object</code>, <code class="language-plaintext highlighter-rouge">data object</code>(kotlin 1.9) 등을 가질 수 있습니다.</p>

<p>위에서 <code class="language-plaintext highlighter-rouge">enum class</code> 로 정의했던 <code class="language-plaintext highlighter-rouge">UiState</code> 타입을 <code class="language-plaintext highlighter-rouge">sealed class</code> 로 변경해보겠습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="kd">class</span> <span class="nc">UiState</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">Loading</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span>
    <span class="kd">data class</span> <span class="nc">Success</span><span class="p">(</span><span class="kd">val</span> <span class="py">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span> <span class="c1">// OK</span>
    <span class="kd">data class</span> <span class="nc">Error</span><span class="p">(</span><span class="kd">val</span> <span class="py">error</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span> <span class="c1">// OK</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이런 구조로 클래스 계층을 설정하면 상위 클래스 객체에서 하위 클래스 객체의 타입을 알 수 있고, 각기 다른 타입을 설정하여 다양한 Use Case 에 대응할 수 있다는 장점이 있습니다.</p>

<p>제약사항으로 하위 클래스의 정의는 같은 <strong>패키지나 파일 내부</strong>에 기술되어야 합니다.<br />
상위 클래스의 기본 생성자의 가시성이 protected(default) 와 private 만 설정이 가능하기 때문입니다.</p>

<h2 id="nested-sealed-class">Nested sealed class</h2>

<p><code class="language-plaintext highlighter-rouge">sealed class</code> 내부에 중첩하여 사용할 수도 있습니다.<br />
이 기능은 <code class="language-plaintext highlighter-rouge">UiState</code> 클래스 내부 <code class="language-plaintext highlighter-rouge">Error</code> 의 하위 타입을 좀 더 세분화 하여 구분할 필요가 있을 경우 적절합니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="kd">class</span> <span class="nc">UiState</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">Loading</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span>

    <span class="kd">data class</span> <span class="nc">Success</span><span class="p">(</span><span class="kd">val</span> <span class="py">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span>

    <span class="k">sealed</span> <span class="kd">class</span> <span class="nc">Error</span><span class="p">(</span><span class="k">open</span> <span class="kd">val</span> <span class="py">error</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">:</span> <span class="nc">UiState</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// 중첩된 sealed class 를 사용하여 Error 를 세분화</span>
        <span class="kd">data class</span> <span class="nc">UiError</span><span class="p">(</span><span class="k">override</span> <span class="kd">val</span> <span class="py">error</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Error</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
        <span class="kd">data class</span> <span class="nc">NetworkError</span><span class="p">(</span><span class="k">override</span> <span class="kd">val</span> <span class="py">error</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Error</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>하지만 이 방식은 상위 타입인 <code class="language-plaintext highlighter-rouge">Error</code> 를 인스턴스화 할 수 없다는 단점이 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">uiState</span> <span class="p">=</span> <span class="nc">UiState</span><span class="p">.</span><span class="nc">Error</span><span class="p">(</span><span class="nc">Exception</span><span class="p">())</span> <span class="c1">// Error</span>

<span class="n">uiState</span> <span class="p">=</span> <span class="nc">UiState</span><span class="p">.</span><span class="nc">Error</span><span class="p">.</span><span class="nc">NetworkError</span><span class="p">(</span><span class="nc">Exception</span><span class="p">())</span> <span class="c1">// OK</span>
</code></pre></div></div>

<h2 id="sealed-interface">sealed interface</h2>

<p><code class="language-plaintext highlighter-rouge">sealed interface</code> 는 코틀린 1.5에서 등장하였습니다.<br />
하나의 <code class="language-plaintext highlighter-rouge">sealed class</code> 만 상속할 수 있는 계층적 한계를 극복하기 위해서 추가되었습니다.</p>

<p>대표적인 예로 라이브러리에서 Error 를 모델링할 때 계층화하여 설계할 수 있습니다. (<a href="https://kotlinlang.org/docs/sealed-classes.html">코틀린 공식 문서</a>)</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="kd">interface</span> <span class="nc">Error</span>

<span class="k">sealed</span> <span class="kd">class</span> <span class="nc">IOError</span><span class="p">():</span> <span class="nc">Error</span>

<span class="kd">class</span> <span class="nc">FileReadError</span><span class="p">(</span><span class="kd">val</span> <span class="py">file</span><span class="p">:</span> <span class="nc">File</span><span class="p">):</span> <span class="nc">IOError</span><span class="p">()</span>
<span class="kd">class</span> <span class="nc">DatabaseError</span><span class="p">(</span><span class="kd">val</span> <span class="py">source</span><span class="p">:</span> <span class="nc">DataSource</span><span class="p">):</span> <span class="nc">IOError</span><span class="p">()</span>

<span class="kd">object</span> <span class="nc">RuntimeError</span> <span class="p">:</span> <span class="nc">Error</span>
</code></pre></div></div>

<p>또 다른 비슷한 사용 사례로 네트워크 통신 API에서 발생하는 Error 타입을 계층 구조로 설계하여 세분화 할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">sealed</span> <span class="kd">interface</span> <span class="nc">LoginError</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">IncorrectPasswordError</span> <span class="p">:</span> <span class="nc">LoginError</span>
    <span class="kd">object</span> <span class="nc">UserNotFoundError</span> <span class="p">:</span> <span class="nc">LoginError</span>
<span class="p">}</span>

<span class="k">sealed</span> <span class="kd">interface</span> <span class="nc">HttpError</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">GetUserListError</span> <span class="p">:</span> <span class="nc">HttpError</span>
    <span class="kd">object</span> <span class="nc">GetUserLikesError</span> <span class="p">:</span> <span class="nc">HttpError</span>
<span class="p">}</span>

<span class="k">sealed</span> <span class="kd">interface</span> <span class="nc">CommonError</span> <span class="p">:</span> <span class="nc">LoginError</span><span class="p">,</span> <span class="nc">HttpError</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">ServerError</span> <span class="p">:</span> <span class="nc">CommonError</span>
    <span class="kd">object</span> <span class="nc">FileReadError</span> <span class="p">:</span> <span class="nc">CommonError</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">fun</span> <span class="nf">handleLoginError</span><span class="p">(</span><span class="n">loginError</span><span class="p">:</span> <span class="nc">LoginError</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">when</span> <span class="p">(</span><span class="n">loginError</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">FileReadError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">ServerError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">LoginError</span><span class="p">.</span><span class="nc">IncorrectPasswordError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">LoginError</span><span class="p">.</span><span class="nc">UserNotFoundError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">fun</span> <span class="nf">handleHttpError</span><span class="p">(</span><span class="n">httpError</span><span class="p">:</span> <span class="nc">HttpError</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">when</span> <span class="p">(</span><span class="n">httpError</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">FileReadError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">ServerError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">HttpError</span><span class="p">.</span><span class="nc">GetUserLikesError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">HttpError</span><span class="p">.</span><span class="nc">GetUserListError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">fun</span> <span class="nf">handleCommonError</span><span class="p">(</span><span class="n">commonError</span><span class="p">:</span> <span class="nc">CommonError</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">when</span> <span class="p">(</span><span class="n">commonError</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">FileReadError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
        <span class="nc">CommonError</span><span class="p">.</span><span class="nc">ServerError</span> <span class="p">-&gt;</span> <span class="nc">TODO</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>말 그대로 인터페이스이기 때문에 다중 상속을 통한 타입의 다형성을 부여할 수 있습니다.<br />
다시 말해, 특정 타입의 성질을 하위 타입에게 전달하기가 쉽습니다.<br />
또한, <code class="language-plaintext highlighter-rouge">when</code> 문을 사용하여 타입을 참조하여 분기해야할 상황에서 장점이 명확해지는 것을 위 코드를 통해 확인할 수 있습니다.</p>]]></content><author><name>OGOONS</name></author><category term="Kotlin" /><summary type="html"><![CDATA[코틀린의 sealed class 와 코틀린 1.5에서 추가된 sealed interface 에 대해서 알아봅니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[ExoPlayer] Preloading and Caching Videos in ExoPlayer</title><link href="https://ogoons.github.io/exoplayer-pre-loading" rel="alternate" type="text/html" title="[ExoPlayer] Preloading and Caching Videos in ExoPlayer" /><published>2023-09-20T00:00:00+00:00</published><updated>2023-09-20T00:00:00+00:00</updated><id>https://ogoons.github.io/exoplayer-pre-loading</id><content type="html" xml:base="https://ogoons.github.io/exoplayer-pre-loading"><![CDATA[<p>ExoPlayer 라이브러리에 내장된 비디오 캐시 기능을 구현하고 사전 로드 하는 방법을 알아봅니다.<br />
라이브러리에 내장된 관련 API 를 사용하면, 다운로드나 캐시 기능을 추가적으로 구현하지 않고도 간단하게 목적을 달성할 수 있습니다.</p>

<blockquote>
  <p>이 글은 새로운 Media3 패키지가 아닌 독립 실행형 라이브러리인 <code class="language-plaintext highlighter-rouge">com.google.android.exoplayer:exoplayer:2.18.1</code> 를 기준으로 작성되었습니다.<br />
Media3 와는 패키지만 다른 동일한 라이브러리 임을 참고하시길 바랍니다.</p>
</blockquote>

<h2 id="다운로드한-비디오를-캐시할-simplecache-구성">다운로드한 비디오를 캐시할 SimpleCache 구성</h2>

<p><code class="language-plaintext highlighter-rouge">SimpleCache</code> 클래스를 사용하면 간단한 방법으로 비디오의 캐시를 구성할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">cacheMaxSize</span><span class="p">:</span> <span class="nc">Long</span> <span class="p">=</span> <span class="mi">90</span> <span class="p">*</span> <span class="mi">1024</span> <span class="p">*</span> <span class="mi">1024</span>
<span class="kd">val</span> <span class="py">cacheRoot</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="n">externalCacheDir</span> <span class="o">?:</span> <span class="n">context</span><span class="p">.</span><span class="n">cacheDir</span> <span class="c1">// 캐시된 파일이 저장될 디렉토리의 루트</span>
<span class="kd">val</span> <span class="py">cacheDir</span> <span class="p">=</span> <span class="nc">File</span><span class="p">(</span><span class="n">cacheRoot</span><span class="p">,</span> <span class="s">"media"</span><span class="p">)</span> <span class="c1">// 캐시된 파일이 저장될 최종 디렉토리(media)</span>

<span class="kd">val</span> <span class="py">simpleCache</span> <span class="p">=</span> <span class="nc">SimpleCache</span><span class="p">(</span><span class="n">cacheDir</span><span class="p">,</span> <span class="nc">LeastRecentlyUsedCacheEvictor</span><span class="p">(</span><span class="n">cacheMaxSize</span><span class="p">),</span> <span class="nc">StandaloneDatabaseProvider</span><span class="p">(</span><span class="n">context</span><span class="p">))</span> <span class="c1">// 하나의 인스턴스만 하용하므로 싱글톤 패턴의 Wrapper를 구성하는 것을 권장</span>
</code></pre></div></div>

<h3 id="simplecache">SimpleCache</h3>

<p>캐시 관리자 클래스, 캐시된 파일을 삭제해야 한다면 디렉토리에서 직접 삭제하지 말고 <code class="language-plaintext highlighter-rouge">delete()</code> 메소드 호출을 통해서 삭제해야 합니다.</p>

<h3 id="leastrecentlyusedcacheevictor">LeastRecentlyUsedCacheEvictor</h3>

<p>LRU 알고리즘으로 구현한 캐시 축출자 입니다.<br />
오랫동안 사용하지 않은 비디오 캐시를 라이브러리에게 알려주고 제거하는 역할을 합니다.</p>

<h3 id="standalonedatabaseprovider">StandaloneDatabaseProvider</h3>

<p>캐시된 파일을 관리할 데이터베이스가 필요하므로, <code class="language-plaintext highlighter-rouge">SQLiteOpenHelper</code> 를 독립적으로 사용할 수 있는 인스턴스를 제공합니다.</p>

<h2 id="cachedatasource-구성">CacheDataSource 구성</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">httpDataSourceFactory</span> <span class="p">=</span> <span class="nc">DefaultHttpDataSource</span><span class="p">.</span><span class="nc">Factory</span><span class="p">()</span>

<span class="kd">val</span> <span class="py">cacheDataSourceFactory</span> <span class="p">=</span> <span class="nc">CacheDataSource</span><span class="p">.</span><span class="nc">Factory</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">setCache</span><span class="p">(</span><span class="n">simpleCache</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setUpstreamDataSourceFactory</span><span class="p">(</span><span class="n">httpDataSourceFactory</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setFlags</span><span class="p">(</span><span class="nc">CacheDataSource</span><span class="p">.</span><span class="nc">FLAG_IGNORE_CACHE_ON_ERROR</span><span class="p">)</span> <span class="c1">// 캐시 관련 오류 발생 후 캐시에 대한 무시 허용</span>
</code></pre></div></div>

<h3 id="defaulthttpdatasource">DefaultHttpDataSource</h3>

<p>HTTP 기반의 업스트림에서 비디오를 내려받기 위한 클래스 입니다.</p>

<p>기본적으로 프로토콜 간에 리디렉션을 지원하지 않습니다.<br />
지원하려면 <code class="language-plaintext highlighter-rouge">DefaultHttpDataSource.Factory.setAllowCrossProtocolRedirects(boolean)</code> true 로 설정합니다.</p>

<h3 id="cachedatasource">CacheDataSource</h3>

<p>DataSource를 읽고 쓰기 위한 클래스 입니다.</p>

<p>캐시가 설정이 되어있는 경우 파일에 대한 요청은 캐시에서 Read/Write 됩니다.<br />
캐시되지 않으면 업스트림에서 요청되어 DataSource 캐시에 기록됩니다.</p>

<h2 id="이제-비디오를-재생해보자">이제 비디오를 재생해보자</h2>

<p>비디오를 재생하고 캐시하기 위한 모든 준비가 끝났습니다.<br />
아래 코드는 비디오 재생을 위한 코드 조각입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">mediaSourceFactory</span> <span class="p">=</span> <span class="nc">ProgressiveMediaSource</span><span class="p">.</span><span class="nc">Factory</span><span class="p">(</span><span class="n">cacheDataSourceFactory</span><span class="p">)</span>

<span class="kd">val</span> <span class="py">loadControl</span> <span class="p">=</span> <span class="nc">DefaultLoadControl</span><span class="p">.</span><span class="nc">Builder</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">setBufferDurationsMs</span><span class="p">(</span><span class="mi">8</span> <span class="p">*</span> <span class="mi">1024</span><span class="p">,</span> <span class="mi">64</span> <span class="p">*</span> <span class="mi">1024</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">1024</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">build</span><span class="p">()</span>
    
<span class="kd">val</span> <span class="py">player</span> <span class="p">=</span> <span class="nc">ExoPlayer</span><span class="p">.</span><span class="nc">Builder</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setMediaSourceFactory</span><span class="p">(</span><span class="n">mediaSourceFactory</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">setLoadControl</span><span class="p">(</span><span class="n">loadControl</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">build</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span>
        <span class="n">playWhenReady</span> <span class="p">=</span> <span class="k">false</span>
    <span class="p">}</span>

<span class="n">player</span><span class="p">.</span><span class="nf">prepare</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="nf">play</span><span class="p">()</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">prepare()</code> 호출과 동시에 비디오는 앱 내 cache 디렉토리에 자동으로 캐시되고 오랫동안 사용하지 않으면 삭제됩니다.</p>

<p>여기서 <code class="language-plaintext highlighter-rouge">play()</code> 를 호출하면 비디오를 내려받고 재생이 가능한 상태로 만들기 위해 약간의 시간이 필요합니다.<br />
사용자에겐 이 적은 시간이 답답함으로 느껴질 수 있습니다.</p>

<h2 id="progressivedownloader-를-통한-사전-캐싱">ProgressiveDownloader 를 통한 사전 캐싱</h2>

<p>이를 개선하기 위해 ProgressiveDownloader 클래스를 사용하여 미리 비디오를 다운로드 받아놓고 플레이어에서 즉각적인 재생을 준비할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">mediaItem</span> <span class="p">=</span> <span class="nc">MediaItem</span><span class="p">.</span><span class="nf">fromUri</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">downloader</span> <span class="p">=</span> <span class="nc">ProgressiveDownloader</span><span class="p">(</span><span class="n">mediaItem</span><span class="p">,</span> <span class="n">cacheDataSourceFactory</span><span class="p">)</span>

<span class="n">downloader</span><span class="p">.</span><span class="nf">download</span> <span class="p">{</span> <span class="n">contentLength</span><span class="p">,</span> <span class="n">bytesDownloaded</span><span class="p">,</span> <span class="n">percentDownloaded</span> <span class="p">-&gt;</span>
    <span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="nc">TAG</span><span class="p">,</span>
        <span class="s">"contentLength: $contentLength, bytesDownloaded: $bytesDownloaded, percentDownloaded: $percentDownloaded"</span><span class="p">)</span>
    <span class="c1">// 다운로드 상태</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="progressivedownloader">ProgressiveDownloader</h3>

<p>스트리밍이 아닌 파일 다운로드로 비디오를 가져오기 위한 클래스 입니다.</p>

<p>이외에 <code class="language-plaintext highlighter-rouge">SsDownloader</code>, <code class="language-plaintext highlighter-rouge">DashDownloader</code>, <code class="language-plaintext highlighter-rouge">HlsDownloader</code> 등 다양한 형식의 다운로드를 지원하고 있습니다.</p>

<h2 id="conclusion">Conclusion</h2>

<p><code class="language-plaintext highlighter-rouge">ProgressiveDownloader</code> 를 사용한 <strong>Pre-loading/cashing</strong> 기법은 개발자가 적절한 전략을 세우고 구현하지 않으면, 오히려 앱의 자원낭비와 전반적인 성능저하를 불러 일으킬 수 있습니다.<br />
사용자가 반드시 해당 비디오를 플레이 할 것이라는 보장이 없다면 무조건 적인 다운로드는 통신 데이터 등의 불필요한 자원소비가 발생할 수 있습니다.<br />
때문에 어떻게 비디오를 미리 캐싱할 것인지에 대한 충분한 전략을 수립하는 것이 무엇보다 중요합니다.</p>

<h2 id="references">References</h2>

<p>https://developer.android.com/guide/topics/media/exoplayer/downloading-media</p>]]></content><author><name>OGOONS</name></author><category term="ExoPlayer" /><category term="Android" /><summary type="html"><![CDATA[ExoPlayer 라이브러리에 내장된 비디오 캐시 기능을 구현하고 사전 로드 하는 방법을 알아봅니다. 라이브러리에 내장된 관련 API 를 사용하면, 다운로드나 캐시 기능을 추가적으로 구현하지 않고도 간단하게 목적을 달성할 수 있습니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Android] JitPack 에서 안드로이드 라이브러리를 빠르게 배포해보자</title><link href="https://ogoons.github.io/android-jitpack-library-publishing" rel="alternate" type="text/html" title="[Android] JitPack 에서 안드로이드 라이브러리를 빠르게 배포해보자" /><published>2023-09-13T00:00:00+00:00</published><updated>2023-09-13T00:00:00+00:00</updated><id>https://ogoons.github.io/android-jitpack-library-publishing</id><content type="html" xml:base="https://ogoons.github.io/android-jitpack-library-publishing"><![CDATA[<p>이 글은 Maven 이 아닌 JitPack 를 통한 안드로이드 라이브러리(aar)를 배포하는 방법을 설명합니다.<br />
더불어 이 글을 작성하는 시점(23.09.13) 최신 버전인 <strong>Gradle 8.3</strong> 과 <strong>AGP 8.1.1</strong> 에서 Jitpack 빌드 실패에 대한 트러블 슈팅 후기도 함께합니다.</p>

<h2 id="1-안드로이드-라이브러리-만들기">1. 안드로이드 라이브러리 만들기</h2>

<p>일반 안드로이드 프로젝트를 생성 후 <code class="language-plaintext highlighter-rouge">New &gt; New Module</code> 메뉴를 통하여 생성합니다.<br />
자세한 내용은 아래 안드로이드 공식 문서에 모두 설명이 되어있으므로
이 글에서는 다루지 않겠습니다.</p>
<blockquote>
  <p>안드로이드 공식 문서: https://developer.android.com/studio/projects/android-library#CreateLibrary</p>
</blockquote>

<blockquote>
  <p>예제 소스코드 참고: https://github.com/ogoons/mylibrary</p>
</blockquote>

<h2 id="2-github-에-라이브러리-업로드-후-태깅">2. GitHub 에 라이브러리 업로드 후 태깅</h2>

<ol>
  <li>임의의 GitHub 레포지토리(<code class="language-plaintext highlighter-rouge">mylibrary</code>) 생성</li>
  <li>생성한 라이브러리 소스 코드를 푸시</li>
  <li>푸시 완료한 GitHub 레포지토리 페이지 이동 후 우측 <code class="language-plaintext highlighter-rouge">Release</code> 메뉴 클릭</li>
  <li><code class="language-plaintext highlighter-rouge">Draft a new release</code> 버튼 클릭</li>
  <li>Choose a tag 에서 새로운 버전명의 태그(1.0.0) 생성</li>
  <li>하단 섹션 입력은 필수가 아니므로 바로 하단 <code class="language-plaintext highlighter-rouge">Publish release</code> 클릭</li>
  <li>릴리스와 태그 생성 완료</li>
</ol>

<h2 id="3-jitpackio-에-트리거-된-새-버전의-라이브러리-확인">3. jitpack.io 에 트리거 된 새 버전의 라이브러리 확인</h2>

<ol>
  <li>https://jitpack.io/ 이동</li>
  <li>
    <p>JitPack 에서 GitHub 로그인 후 다음과 같이 <code class="language-plaintext highlighter-rouge">UserName/RepositoryName</code> 입력 후 <code class="language-plaintext highlighter-rouge">Look up</code> 클릭<br />
<img src="/assets/images/2023-09-13-android-jitpack-library-publishing-01.png" width="50%" height="50%" /></p>
  </li>
  <li>클릭 후 <code class="language-plaintext highlighter-rouge">Get it</code> 버튼을 누르거나 프로그래스 바가 진행이 되면 빌드를 시작합니다.</li>
  <li>
    <p>빌드가 성공하면 아래와 같은 녹색 로그 아이콘을 볼 수 있습니다.<br />
<img src="/assets/images/2023-09-13-android-jitpack-library-publishing-02.png" width="50%" height="50%" /><br />
실패한다면 로그를 확인하여 문제를 해결한 후 2-3번 항목부터 반복합니다.</p>
  </li>
  <li>하단에 라이브러리 통합을 가이드하는 섹션이 생성됩니다.<br />
안내에 따라 추가하고자 하는 프로젝트 <code class="language-plaintext highlighter-rouge">build.gradle</code> 파일에 추가해주면 됩니다.<br />
<img src="/assets/images/2023-09-13-android-jitpack-library-publishing-03.png" width="50%" height="50%" /></li>
</ol>

<h2 id="gradle-agp-최신-버전에서의-빌드-실패-이슈">Gradle, AGP 최신 버전에서의 빌드 실패 이슈</h2>

<ul>
  <li><strong>Gradle 8.3</strong></li>
  <li><strong>AGP 8.1.1</strong></li>
</ul>

<p>위와 같은 환경의 빌드 시스템을 구성 후 JitPack 을 트리거 하면, AGP 8.1.1 과 일치하는 변형을 찾을 수 없다면서 빌드가 더 이상 진행이 되지 않는 문제가 있습니다.</p>

<blockquote>
  <p>No matching variant of com.android.tools.build:gradle:8.1.1 was found. the consumer was configured to find a library for use during runtime, compatible with java 8, packaged as a jar, and its dependencies declared externally, as well as attribute ‘org.gradle.plugin.api-version’ with value ‘8.3’
…</p>
</blockquote>

<p>JitPack 에서 아직 최신 버전의 Gradle, AGP 를 지원하지 않는 것 처럼 보입니다.</p>

<p>일단, 이럴 경우는 프로젝트 루트 경로에 <code class="language-plaintext highlighter-rouge">jitpack.yml</code> 환경설정 파일을 필수로 추가해주어야 JitPack 에서 정상적인 빌드가 가능합니다.</p>

<p><img src="/assets/images/2023-09-13-android-jitpack-library-publishing-04.png" width="25%" height="25%" /></p>

<p>파일 내용은 다음과 같습니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jdk:
  - openjdk17
</code></pre></div></div>

<p>필자는 OpenJDK 17 환경에서 빌드가 되도록 설정하였습니다.<br />
이 부분은 각자 개발된 환경에 맞게 설정하면 됩니다.</p>

<p>추가로 이 환경의 빌드 시스템으로 구성된 라이브러리를 성공적으로 배포하였다 해도, 같은 환경의 프로젝트에 통합 후 빌드 시에 Duplicate class 오류가 발생하는 이슈가 있습니다.</p>

<p>External Libraries 를 살펴보니 중복으로 aar 이 로딩되는 것으로 확인이 되는데 7.x 버전으로 다운그레이드 하여 시도해보면 문제가 없었습니다.<br />
아래는 다운그레이드를 시도한 버전명 입니다.</p>

<ul>
  <li><strong>Gradle 7.5.1</strong></li>
  <li><strong>AGP 7.4.2</strong></li>
</ul>

<p>아무래도 8.x 버전의 버그로 생각이 됩니다.<br />
같은 문제로 힘들어하는 안드로이드 개발자들을 위해 공유합니다.<br />
아직 이슈 트래커나 온라인 상에 공유된 자료가 없어서, 이 문제가 해결이 되면 본문에 업데이트 하도록 하겠습니다.</p>]]></content><author><name>OGOONS</name></author><category term="Android" /><summary type="html"><![CDATA[이 글은 Maven 이 아닌 JitPack 를 통한 안드로이드 라이브러리(aar)를 배포하는 방법을 설명합니다. 더불어 이 글을 작성하는 시점(23.09.13) 최신 버전인 Gradle 8.3 과 AGP 8.1.1 에서 Jitpack 빌드 실패에 대한 트러블 슈팅 후기도 함께합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Coroutine] 코루틴의 Continuation 과 동작 원리</title><link href="https://ogoons.github.io/coroutine-continuation" rel="alternate" type="text/html" title="[Coroutine] 코루틴의 Continuation 과 동작 원리" /><published>2023-09-07T00:00:00+00:00</published><updated>2023-09-07T00:00:00+00:00</updated><id>https://ogoons.github.io/coroutine-continuation</id><content type="html" xml:base="https://ogoons.github.io/coroutine-continuation"><![CDATA[<p>코루틴의 Continuation 은 무엇이며 suspend 함수는 내부적으로 어떻게 동작하는 지 알아보자</p>

<h2 id="continuation-passing-style">Continuation Passing Style</h2>

<p>코틀린 코드와 마찬가지도 코루틴 코드도 내부적으로 JVM 기반의 바이트코드로 변환되어 작동합니다.<br />
그렇다면 Callback 을 사용하지 않았는데도 불구하고, 코루틴의 비동기 루틴은 어떻게 작업을 순차적으로 처리할까요?</p>

<p>다음은 <a href="https://youtu.be/YrrUCSi72E8?si=PztyuDV5nLI156P4">KotlinConf 2017 — Deep Dive into Coroutines on JVM</a> 에 소개된 예제입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">suspend</span> <span class="k">fun</span> <span class="nf">postItem</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="nc">Item</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">token</span> <span class="p">=</span> <span class="nf">requestToken</span><span class="p">()</span>
    <span class="kd">val</span> <span class="py">post</span> <span class="p">=</span> <span class="nf">createPost</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">tiem</span><span class="p">)</span>
    <span class="nf">processPost</span><span class="p">(</span><span class="n">post</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>하나의 suspend 함수 내부에서 3개의 하위 루틴을 호출하고 있습니다.<br />
코루틴은 이 코드를 내부적으로 <strong>Continuation Passing Style</strong> 로 변환해서 사용하고 있습니다.</p>

<p>코드는 다음과 같습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">postItem</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="nc">Item</span><span class="p">,</span> <span class="n">cont</span><span class="p">:</span> <span class="nc">Continuation</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">sm</span> <span class="p">=</span> <span class="n">cont</span> <span class="k">as</span><span class="p">?</span> <span class="nc">ThisSM</span> <span class="o">?:</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">ThisSM</span> <span class="p">{</span> <span class="c1">// State machine</span>
        <span class="k">fun</span> <span class="nf">resume</span><span class="p">()</span> <span class="p">{</span>
            <span class="nf">postItem</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nf">switch</span><span class="p">(</span><span class="n">sm</span><span class="p">.</span><span class="n">label</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">case</span> <span class="mi">0</span><span class="p">:</span>
            <span class="c1">// Save state</span>
            <span class="n">sm</span><span class="p">.</span><span class="n">item</span> <span class="p">=</span> <span class="n">item</span>
            <span class="n">sm</span><span class="p">.</span><span class="n">label</span> <span class="p">=</span> <span class="mi">1</span>

            <span class="c1">// Continue</span>
            <span class="kd">val</span> <span class="py">token</span> <span class="p">=</span> <span class="nf">requestToken</span><span class="p">(</span><span class="n">sm</span><span class="p">)</span>
        <span class="n">case</span> <span class="mi">1</span><span class="p">:</span>
            <span class="c1">// Restore state</span>
            <span class="kd">val</span> <span class="py">item</span> <span class="p">=</span> <span class="n">sm</span><span class="p">.</span><span class="n">item</span>
            <span class="kd">val</span> <span class="py">token</span> <span class="p">=</span> <span class="n">sm</span><span class="p">.</span><span class="n">result</span> <span class="k">as</span> <span class="nc">Token</span>

            <span class="c1">// Continue</span>
            <span class="n">sm</span><span class="p">.</span><span class="n">label</span> <span class="p">=</span> <span class="mi">2</span>
            <span class="kd">val</span> <span class="py">post</span> <span class="p">=</span> <span class="nf">createPost</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">sm</span><span class="p">)</span>
        <span class="o">..</span><span class="p">.</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>중간에 <strong>State Machine</strong> 인 <code class="language-plaintext highlighter-rouge">sm</code> 객체를 통해서 연산된 결과를 현재 label 에 해당하는 하위 함수를 호출하고 전달하며 호출이 끝나면 다시 <code class="language-plaintext highlighter-rouge">resume()</code> 의 호출을 통해 <code class="language-plaintext highlighter-rouge">postItem()</code> 을 재귀호출하고, 다음 label 의 하위 함수를 호출하는 식으로 하나의 함수 내부에서 연속성을 발생시키고 있는 것을 볼 수 있습니다.</p>

<p>이러한 방식을 <strong>Continuation Passing Style (CPS)</strong> 라고 합니다.</p>

<p><a href="https://developer.android.com/kotlin/coroutines/coroutines-adv">안드로이드 공식 문서</a>에서는 다음과 같이 설명하고 있습니다.</p>

<blockquote>
  <p>Kotlin은 스택 프레임을 사용하여 로컬 변수와 함께 실행되는 함수를 관리합니다. 코루틴을 일시 중단하면 현재 스택 프레임이 복사되어 나중에 저장됩니다. 다시 시작하면 스택 프레임이 저장된 위치에서 다시 복사되고 함수가 다시 실행되기 시작합니다. 코드가 일반적인 순차 차단 요청처럼 보일지라도 코루틴은 네트워크 요청이 기본 스레드 차단을 방지하도록 보장합니다.</p>
</blockquote>

<p>여기서 스택 프레임은 <strong>State Machine</strong> 과 같은 개념입니다.</p>

<p>정리하면 함수의 호출 결과를 호출자에게 직접 넘기는 것이 아니라 <code class="language-plaintext highlighter-rouge">Continuation</code> 객체와 같은 <strong>State Machine</strong> 에게 넘기는 동시에 호출 순서를 관리하고, 하위 함수를 순차적으로 호출하는 방식이라고 할 수 있겠습니다.</p>

<h2 id="continuation-의-명시적인-사용">Continuation 의 명시적인 사용</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kd">interface</span> <span class="nc">Continuation</span><span class="p">&lt;</span><span class="k">in</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="cm">/**
     * The context of the coroutine that corresponds to this continuation.
     */</span>
    <span class="k">public</span> <span class="kd">val</span> <span class="py">context</span><span class="p">:</span> <span class="nc">CoroutineContext</span>

    <span class="cm">/**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */</span>
    <span class="k">public</span> <span class="k">fun</span> <span class="nf">resumeWith</span><span class="p">(</span><span class="n">result</span><span class="p">:</span> <span class="nc">Result</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>일반적으로 <code class="language-plaintext highlighter-rouge">Continuation</code> 을 명시적으로 사용할 일이 많지 않을 겁니다.<br />
하지만 안드로이드에서 이를 명시적으로 사용하고, Callback 루틴을 감싸 코루틴 내부에서 간편하게 사용할 수 있는 몇 가지 예시가 있습니다.</p>

<h3 id="viewsetonclicklistener">View#setOnClickListener</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">suspend</span> <span class="k">fun</span> <span class="nf">syncClick</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="nf">suspendCoroutine</span> <span class="p">{</span> <span class="n">cont</span> <span class="p">-&gt;</span>
    <span class="n">btnEnter</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span>
        <span class="n">cont</span><span class="p">.</span><span class="nf">resumeWith</span><span class="p">(</span><span class="nc">Result</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="s">"Clicked"</span><span class="p">))</span>
        <span class="n">btnEnter</span><span class="p">.</span><span class="nf">setOnClickListener</span><span class="p">(</span><span class="k">null</span><span class="p">)</span> <span class="c1">// 빠르고 반복적인 클릭 작업에 대응</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">lifecycleScope</span><span class="p">.</span><span class="nf">launch</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="nf">syncClick</span><span class="p">()</span>
    <span class="n">result</span> <span class="c1">// Clicked</span>
<span class="p">}</span>
</code></pre></div></div>

<p>클릭 리스너를 <strong>suspendCoroutine</strong> 으로 감싸 코루틴 내부에서 간결하고 순차적인 표현이 가능해집니다.</p>

<p><code class="language-plaintext highlighter-rouge">Continuation</code> 의 확장 함수를 이용하면, Callback 루틴 내부에서 실패가 발생해도 취소가 가능한 방법이 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">inline</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">Continuation</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="nf">resume</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">Unit</span> <span class="p">=</span> 
    <span class="nf">resumeWith</span><span class="p">(</span><span class="nc">Result</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="n">value</span><span class="p">))</span>

<span class="k">public</span> <span class="k">inline</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">Continuation</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="nf">resumeWithException</span><span class="p">(</span><span class="n">exception</span><span class="p">:</span> <span class="nc">Throwable</span><span class="p">):</span> <span class="nc">Unit</span> <span class="p">=</span> 
    <span class="nf">resumeWith</span><span class="p">(</span><span class="nc">Result</span><span class="p">.</span><span class="nf">failure</span><span class="p">(</span><span class="n">exception</span><span class="p">))</span>
</code></pre></div></div>

<p>이 함수들은 <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code> 를 사용하여 구현이 가능합니다.<br />
아래 Chris Banes 의 코드가 가장 설명하기 적합한 예제인 것 같습니다.</p>

<h3 id="viewonlayoutchangelistener-chris-banes-example">View#OnLayoutChangeListener (Chris Banes example)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">suspend</span> <span class="k">fun</span> <span class="nc">View</span><span class="p">.</span><span class="nf">awaitNextLayout</span><span class="p">()</span> <span class="p">=</span> <span class="n">suspendCancellableCoroutine</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span> <span class="n">cont</span> <span class="p">-&gt;</span>
    <span class="c1">// This lambda is invoked immediately, allowing us to create</span>
    <span class="c1">// a callback/listener</span>

    <span class="kd">val</span> <span class="py">listener</span> <span class="p">=</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">View</span><span class="p">.</span><span class="nc">OnLayoutChangeListener</span> <span class="p">{</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">onLayoutChange</span><span class="p">(</span><span class="o">..</span><span class="p">.)</span> <span class="p">{</span>
            <span class="c1">// The next layout has happened!</span>
            <span class="c1">// First remove the listener to not leak the coroutine</span>
            <span class="n">view</span><span class="o">?.</span><span class="nf">removeOnLayoutChangeListener</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
            <span class="c1">// Finally resume the continuation, and</span>
            <span class="c1">// wake the coroutine up</span>
            <span class="n">cont</span><span class="p">.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// If the coroutine is cancelled, remove the listener</span>
    <span class="n">cont</span><span class="p">.</span><span class="nf">invokeOnCancellation</span> <span class="p">{</span> <span class="nf">removeOnLayoutChangeListener</span><span class="p">(</span><span class="n">listener</span><span class="p">)</span> <span class="p">}</span>
    <span class="c1">// And finally add the listener to view</span>
    <span class="nf">addOnLayoutChangeListener</span><span class="p">(</span><span class="n">listener</span><span class="p">)</span>

    <span class="c1">// The coroutine will now be suspended. It will only be resumed</span>
    <span class="c1">// when calling cont.resume() in the listener above</span>
<span class="p">}</span>

<span class="n">viewLifecycleOwner</span><span class="p">.</span><span class="n">lifecycleScope</span><span class="p">.</span><span class="nf">launch</span> <span class="p">{</span>

    <span class="n">titleView</span><span class="p">.</span><span class="nf">awaitNextLayout</span><span class="p">()</span>

    <span class="c1">// 여기서 뷰의 크기가 확정이 되면 작업을 수행...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>뷰의 크기 확정 시점을 알기 위한 콜백 루틴을 <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code> 으로 감싸서 간결해졌고, 내부에서 취소가 발생했을 때 <code class="language-plaintext highlighter-rouge">OnLayoutChangeListener</code> 의 해제 루틴도 동작하도록 대응이 되었습니다.</p>

<h2 id="conclusion">Conclusion</h2>

<p>개인적으로 <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code> 을 자주 활용하는 편이며, 콜백 스타일로 설계된 API 를 코루틴 내부에서 사용할 때 <code class="language-plaintext highlighter-rouge">Continuation</code> 을 직접 핸들링 한다면, 간결한 코드에 많은 도움이 될 것 입니다.</p>]]></content><author><name>OGOONS</name></author><category term="Coroutine" /><summary type="html"><![CDATA[코루틴의 Continuation 과 suspend 함수가 내부에서 동작하는 원리를 알아봅니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Kotlin] Generics 파헤치기 Part 2 - 변성(Variance)과 in, out 한정자</title><link href="https://ogoons.github.io/kotlin-generic-part-2" rel="alternate" type="text/html" title="[Kotlin] Generics 파헤치기 Part 2 - 변성(Variance)과 in, out 한정자" /><published>2023-09-05T00:00:00+00:00</published><updated>2023-09-05T00:00:00+00:00</updated><id>https://ogoons.github.io/kotlin-generic-part-2</id><content type="html" xml:base="https://ogoons.github.io/kotlin-generic-part-2"><![CDATA[<p>코틀린의 제네릭 프로그래밍 방식에 대해서 파헤쳐보기 두 번째 시간입니다.</p>

<h2 id="변성-그리고-variance-한정자">변성 그리고 Variance 한정자</h2>

<p><strong>변성</strong>이란, <strong>기저타입(Base Type)</strong>이 같고 <strong>타입 아규먼트(Type Argument)</strong> 가 다른 경우에 대한 관계를 설명하는 개념입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="p">&lt;</span><span class="nc">Any</span><span class="p">&gt;</span>
<span class="nc">List</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">List</code> 는 기저타입, <code class="language-plaintext highlighter-rouge">Any</code> 와 <code class="language-plaintext highlighter-rouge">String</code> 은 타입 아규먼트가 됩니다.<br />
코틀린은 이 두 개의 타입에 대한 관계를 설정할 수 있는 강력한 제네릭 기능을 제공합니다.</p>

<p>관련 용어로는 <strong>공변성, 반공변성, 무공변성</strong>이 있습니다.<br />
아래에서 더 자세히 다루어 보겠습니다.</p>

<h2 id="무공변성-invariance">무공변성 (Invariance)</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">var</span> <span class="py">liquid</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">setLiquid</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// OK</span>
        <span class="k">this</span><span class="p">.</span><span class="n">liquid</span> <span class="p">=</span> <span class="n">value</span>
    <span class="p">}</span>
    <span class="k">fun</span> <span class="nf">getLiquid</span><span class="p">():</span> <span class="nc">T</span> <span class="p">{</span> <span class="c1">// OK</span>
        <span class="k">return</span> <span class="n">liquid</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">val</span> <span class="py">a1</span> <span class="p">=</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Water</span><span class="p">&gt;(</span><span class="nc">Water</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">b1</span><span class="p">:</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Liquid</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">a1</span> <span class="c1">// 오류</span>

<span class="kd">val</span> <span class="py">a2</span> <span class="p">=</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Liquid</span><span class="p">&gt;(</span><span class="nc">Liquid</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">b2</span><span class="p">:</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Water</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">a2</span> <span class="c1">// 오류</span>
</code></pre></div></div>

<p>무공변성은 두 클래스간에 아무런 관계가 설정되지 않은 성질을 말합니다.<br />
위 코드와 같이 <code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 와 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 는 상하위 관계가 존재하지 않으므로, 서로에게 할당하는 코드에서 타입 불일치 오류가 발생합니다.</p>

<ul>
  <li>별도의 키워드 지정 없음</li>
  <li><code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 와 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 는 아무런 관계가 없다.</li>
  <li><code class="language-plaintext highlighter-rouge">T</code> 를 생산자 위치 또는 소비자 위치 모두에서 사용 가능</li>
</ul>

<h2 id="공변성-covariance">공변성 (Covariance)</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">liquid</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// out 한정자를 사용하여 공변성으로 만듬</span>
    <span class="k">fun</span> <span class="nf">setLiquid</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 오류</span>
        <span class="k">this</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="n">value</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">getLiquid</span><span class="p">():</span> <span class="nc">T</span> <span class="p">{</span> <span class="c1">// OK, out 위치에서 타입 매개변수 사용</span>
        <span class="k">return</span> <span class="n">liquid</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">open</span> <span class="kd">class</span> <span class="nc">Liquid</span> <span class="c1">// B</span>
<span class="kd">class</span> <span class="nc">Water</span> <span class="p">:</span> <span class="nc">Liquid</span><span class="p">()</span> <span class="c1">// A</span>

<span class="kd">val</span> <span class="py">a</span> <span class="p">=</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Water</span><span class="p">&gt;(</span><span class="nc">Water</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">b</span><span class="p">:</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Liquid</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">a</span> <span class="c1">// OK</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Water</code> 가 <code class="language-plaintext highlighter-rouge">Liquid</code>의 하위 타입이고, 마찬가지로 <code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 가 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 의 하위 타입인 경우에는 <strong>공변성</strong>을 갖는다 할 수 있습니다.</p>

<p><code class="language-plaintext highlighter-rouge">out</code> 키워드를 사용하여 타입 매개변수를 생산자 역할로만 사용하도록 한정할 수 있습니다.</p>

<p>여기서 <code class="language-plaintext highlighter-rouge">a</code> 객체는 상위 타입인 <code class="language-plaintext highlighter-rouge">b</code> 객체에 할당이 가능합니다.<br />
<code class="language-plaintext highlighter-rouge">out</code> 한정자를 통해 <code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 는 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 의 하위 타입으로 설정되어, 묵시적으로 업캐스팅이 이루어졌기 때문입니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">out</code> 키워드를 사용</li>
  <li><code class="language-plaintext highlighter-rouge">Water</code> 는 <code class="language-plaintext highlighter-rouge">Liquid</code> 의 하위 타입이다.</li>
  <li><code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 는 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 의 하위 타입이다.</li>
  <li><code class="language-plaintext highlighter-rouge">T</code> 를 생산하는 위치에만 사용하도록 제한 (함수의 반환 타입)</li>
</ul>

<p>추가로, 위 코드에서는 타입 매개변수 <code class="language-plaintext highlighter-rouge">T</code> 를 <code class="language-plaintext highlighter-rouge">out</code> 한정자로 설정해놓았습니다.<br />
그리고 <code class="language-plaintext highlighter-rouge">setLiquid()</code> 함수의 <code class="language-plaintext highlighter-rouge">value</code> 매개변수가 public <code class="language-plaintext highlighter-rouge">in</code> 한정자의 위치에 해당되어, 컴파일러가 오류를 발생시킵니다.</p>

<h2 id="반공변성-contravariance">반공변성 (Contravariance)</h2>

<p>공변성에 반대되는 개념입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="k">in</span> <span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">liquid</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// in 한정자를 사용하여 반공변성으로 만듬</span>
    <span class="k">fun</span> <span class="nf">setLiquid</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// OK, in 위치에서 타입 매개변수 사용</span>
        <span class="k">this</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="n">value</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">getLiquid</span><span class="p">():</span> <span class="nc">T</span> <span class="p">{</span> <span class="c1">// 오류</span>
        <span class="k">return</span> <span class="n">liquid</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">open</span> <span class="kd">class</span> <span class="nc">Liquid</span>
<span class="kd">class</span> <span class="nc">Water</span> <span class="p">:</span> <span class="nc">Liquid</span><span class="p">()</span>

<span class="kd">val</span> <span class="py">a1</span> <span class="p">=</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Water</span><span class="p">&gt;(</span><span class="nc">Water</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">b1</span><span class="p">:</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Liquid</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">a1</span> <span class="c1">// 오류</span>

<span class="kd">val</span> <span class="py">a2</span> <span class="p">=</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Liquid</span><span class="p">&gt;(</span><span class="nc">Liquid</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">b2</span><span class="p">:</span> <span class="nc">Cup</span><span class="p">&lt;</span><span class="nc">Water</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">a2</span> <span class="c1">// OK</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Liquid</code> 는 <code class="language-plaintext highlighter-rouge">Water</code> 의 상위 타입이지만 <code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 는 <code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 의 하위 타입인 경우와 같이 반대되는 경우를 반공변성을 갖는다 할 수 있습니다.</p>

<p>이와 같은 성질은 <code class="language-plaintext highlighter-rouge">in</code> 한정자의 타입 매개변수를 사용하여 제네릭 클래스를 소비자의 역할로만 사용하도록 제한할 수 있습니다.</p>

<p><code class="language-plaintext highlighter-rouge">out</code> 한정자를 사용할 때와는 다르게 <code class="language-plaintext highlighter-rouge">T</code> 를 매개변수로 받는 <code class="language-plaintext highlighter-rouge">setLiquid()</code> 함수의 <code class="language-plaintext highlighter-rouge">value</code> 매개변수에서 오류가 발생하지 않습니다.<br />
반대로 <code class="language-plaintext highlighter-rouge">T</code> 를 반환하는 생산자 위치의 <code class="language-plaintext highlighter-rouge">getLiquid()</code> 함수에서는 오류가 발생하는 것을 확인할 수 있습니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">in</code> 키워드를 사용</li>
  <li><code class="language-plaintext highlighter-rouge">Water</code> 는 <code class="language-plaintext highlighter-rouge">Liquid</code> 의 하위 타입이다.</li>
  <li><code class="language-plaintext highlighter-rouge">Cup&lt;Liquid&gt;</code> 는 <code class="language-plaintext highlighter-rouge">Cup&lt;Water&gt;</code> 의 하위 타입이다.</li>
  <li><code class="language-plaintext highlighter-rouge">T</code> 를 소비하는 위치에만 사용하도록 제한 (함수의 매개변수)</li>
</ul>]]></content><author><name>OGOONS</name></author><category term="Kotlin" /><summary type="html"><![CDATA[제네릭 변성(Variance)과 in, out 한정자의 의미를 정리합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Kotlin] Generics 파헤치기 Part 1 - 정의, 타입 매개변수 제약</title><link href="https://ogoons.github.io/kotlin-generic-part-1" rel="alternate" type="text/html" title="[Kotlin] Generics 파헤치기 Part 1 - 정의, 타입 매개변수 제약" /><published>2023-09-04T00:00:00+00:00</published><updated>2023-09-04T00:00:00+00:00</updated><id>https://ogoons.github.io/kotlin-generic-part-1</id><content type="html" xml:base="https://ogoons.github.io/kotlin-generic-part-1"><![CDATA[<p>코틀린의 제네릭 프로그래밍 방식에 대해서 파헤쳐보기 첫 번째 시간입니다.</p>

<h2 id="generics-이란-무엇일까">Generics 이란 무엇일까?</h2>

<p>다른 객체지향 프로그래밍 언어와 마찬가지로 코틀린만의 특색있는 제네릭 프로그래밍을 지원하고 있습니다.<br />
제네릭을 한국어로 해석하면 일반적인, 포괄적인 뜻을 함유하고 있습니다.<br />
프로그래밍에서의 제네릭은 프로그래머가 <strong>클래스에서 사용하는 타입을 특정하지 않고 로직을 재사용하여 결과를 도출하는 방식</strong>의 프로그래밍이라 할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">students</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span>

    <span class="k">fun</span> <span class="nf">addStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">students</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">student</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>여기서 <code class="language-plaintext highlighter-rouge">&lt;T&gt;</code> 로 명시된 타입을 타입 매개변수(Type Parameter)라고 합니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">school</span> <span class="p">=</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">Student</span><span class="p">&gt;()</span>
    <span class="n">school</span><span class="p">.</span><span class="nf">addStudent</span><span class="p">(</span><span class="nc">Student</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>외부에서 클래스를 사용할 때 꺽쇠괄호 <code class="language-plaintext highlighter-rouge">&lt;&gt;</code> 안에 타입(Student)을 지정해서 타입 매개변수에 전달했는데, 이를 타입 아규먼트(Type Argument)라고 합니다.</p>

<h2 id="타입-매개변수의-관례">타입 매개변수의 관례</h2>

<p>타입 매개변수로 사용하는 명명규칙(Naming Convention)은 다음과 같습니다.</p>

<table>
  <thead>
    <tr>
      <th>타입</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;T&gt;</code></td>
      <td>Type</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;E&gt;</code></td>
      <td>Element</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;K&gt;</code></td>
      <td>Key</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;V&gt;</code></td>
      <td>Value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;N&gt;</code></td>
      <td>Number</td>
    </tr>
  </tbody>
</table>

<p>다만, 의무가 아닌 관례일 뿐이라 꼭 대문자나 한 글자가 아니어도 되고, 프로젝트 성격에 맞는 다른 이름을 지정해서 구분하는 것도 좋은 방법입니다.</p>

<h2 id="다양한-제네릭-프로그래밍-방식">다양한 제네릭 프로그래밍 방식</h2>

<p>일반 클래스 외에 <code class="language-plaintext highlighter-rouge">data class</code>, <code class="language-plaintext highlighter-rouge">abstract class</code>, <code class="language-plaintext highlighter-rouge">sealed class</code>, <code class="language-plaintext highlighter-rouge">interface</code>, 그리고 <strong>함수</strong>나 <strong>확장된 프로퍼티</strong>에도 제네릭하게 사용할 수 있습니다.<br />
(<code class="language-plaintext highlighter-rouge">enum class</code> 는 지원하지 않습니다.)</p>

<p>아래는 함수에서의 사용방법입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">getStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">student</span>
<span class="p">}</span>
</code></pre></div></div>

<p>프로퍼티는 확장된 프로퍼티에서만 제네릭하게 사용할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="py">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="n">credit</span><span class="p">:</span> <span class="nc">Int</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">10</span>
</code></pre></div></div>

<h2 id="타입-매개변수-제약-type-parameter-constraint">타입 매개변수 제약 (Type Parameter Constraint)</h2>

<p>타입 매개변수의 상한 타입에 제한을 둘 수 있습니다.<br />
타입 매개변수 뒤에 콜론(:)을 붙인 후 상한 타입을 기입해주면 됩니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">students</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span>

    <span class="k">fun</span> <span class="nf">addStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">students</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">student</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 타입 매개변수 <code class="language-plaintext highlighter-rouge">T</code> 는 <code class="language-plaintext highlighter-rouge">Student</code> 를 상위 클래스로 하는 타입 아규먼트만 받을 수 있습니다.</p>

<p><code class="language-plaintext highlighter-rouge">where</code> 키워드를 사용하면 타입 매개변수를 하나는 물론 여러 개 지정하는 것도 가능합니다.</p>

<p>다음은 사용사례 별로 타입 매개변수 제약을 하나 또는 여러 개 지정한 예제입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Human</span>

<span class="kd">interface</span> <span class="nc">Student</span> <span class="p">:</span> <span class="nc">Human</span>

<span class="kd">interface</span> <span class="nc">Teacher</span>

<span class="kd">interface</span> <span class="nc">Boy</span>

<span class="kd">interface</span> <span class="nc">Girl</span>

<span class="kd">interface</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">addStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="클래스의-타입-매개변수-제약">클래스의 타입 매개변수 제약</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1개의 타입 제약</span>
<span class="kd">class</span> <span class="nc">HighSchool</span><span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">students</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">addStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">students</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">student</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 2개 이상의 타입 제약</span>
<span class="kd">class</span> <span class="nc">BoysSchool</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="k">where</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">,</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">Boy</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">students</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">addStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">students</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">student</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="함수의-타입-매개변수-제약">함수의 타입 매개변수 제약</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1개의 타입 제약</span>
<span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">&gt;</span> <span class="nf">getStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">student</span>
<span class="p">}</span>

<span class="c1">// 2개 이상의 타입 제약</span>
<span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">getBoyStudent</span><span class="p">(</span><span class="n">student</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">T</span> <span class="k">where</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">,</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">Boy</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">student</span>
<span class="p">}</span>

</code></pre></div></div>

<h4 id="확장된-프로퍼티의-타입-매개변수-제약">확장된 프로퍼티의 타입 매개변수 제약</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1개의 타입 제약</span>
<span class="kd">val</span> <span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">Student</span><span class="p">&gt;</span> <span class="py">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="n">credit</span><span class="p">:</span> <span class="nc">Int</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">10</span>

<span class="c1">// 2개 이상의 타입 제약</span>
<span class="kd">val</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="py">School</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="n">age</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">where</span> <span class="nc">T</span><span class="p">:</span> <span class="nc">Student</span><span class="p">,</span> <span class="nc">T</span> <span class="p">:</span> <span class="nc">Boy</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">15</span>
</code></pre></div></div>

<p>다음 포스팅에서는 코틀린 제네릭의 변성(Variance)을 비롯한 in, out 등의 관련 키워드도 같이 다루어 보겠습니다.</p>]]></content><author><name>OGOONS</name></author><category term="Kotlin" /><summary type="html"><![CDATA[Kotlin 제네릭의 개념과 클래스에서 타입을 일반화해 쓰는 방법을 알아봅니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Kotlin] Delegation 사용설명서</title><link href="https://ogoons.github.io/kotlin-delegation" rel="alternate" type="text/html" title="[Kotlin] Delegation 사용설명서" /><published>2023-08-30T00:00:00+00:00</published><updated>2023-08-30T00:00:00+00:00</updated><id>https://ogoons.github.io/kotlin-delegation</id><content type="html" xml:base="https://ogoons.github.io/kotlin-delegation"><![CDATA[<p>코틀린에서는 Delegation 디자인 패턴을 언어 수준에서 지원하는 기능을 가지고 있습니다.<br />
이에 관하여 자세히 알아보고 사용법을 살펴보도록 하겠습니다.</p>

<h2 id="delegation">Delegation</h2>

<p>앞서 언급한 <strong>Delegation Pattern(위임 패턴)</strong> 에 대해서 간단하게 짚고 넘어갈 필요가 있습니다.
위임 패턴은 클래스를 상속받지 않고, 다른 클래스의 재사용할 수 있도록 해주는 디자인 패턴 중에 하나 입니다.</p>

<p>우선 소프트웨어 공학에서 다른 클래스 기능의 재사용을 위해 사용하는 방법은 다음과 같습니다.</p>

<ul>
  <li>상속(Inheritance)</li>
  <li>포함(Composition)</li>
</ul>

<p>상속은 상위 클래스의 기능을 하위 클래스에 가져와 사용할 수 있지만, 상위 클래스에 의존적이게 되고 캡슐화를 무너뜨리는 단점이 있습니다.<br />
하지만 포함은 다른 클래스의 객체를 내 클래스에 <strong>포함</strong>하여 다른 클래스의 기능을 그대로 가져올 수 있으며, 상위 클래스에 의존하지 않아도 캡슐화를 유지할 수 있는 장점이 있습니다.</p>
<blockquote>
  <p>Composition 이란 단어는 ‘포함’을 비롯해 ‘구성’, ‘조합’ 등의 단어로 번역할 수 있지만, 본문에서는 ‘포함’이라는 단어를 사용하도록 하겠습니다.</p>
</blockquote>

<p>그렇다고 포함이 무조건 옳은 것은 아니며 사용 사례에 따라 적절히 선택하여 사용해야 할 것 입니다.</p>

<h3 id="상속과-포함은-어떤-상황에서-선택하면-좋을까">상속과 포함은 어떤 상황에서 선택하면 좋을까?</h3>

<ul>
  <li>상속을 고려해야 하는 경우
    <ul>
      <li>상위 클래스와 하위 클래스의 관계가 <strong>is-a</strong> 관계일 때</li>
      <li>예) 개발자(하위클래스)는 사람(상위클래스)이다.</li>
    </ul>
  </li>
  <li>포함을 고려해야하는 경우
    <ul>
      <li>상위 클래스가 하위 클래스를 포함 <strong>has-a</strong> 하는 관계일 때</li>
      <li>예) 개발자(상위클래스)는 키보드(하위클래스)를 가지고 있다.</li>
    </ul>
  </li>
</ul>

<p>포함을 사용해야 하는 상황이라면 코틀린의 위임을 이용하여, 상용구 코드 없이 단순하게 구현할 수 있습니다.<br />
다음은 Delegation 을 구현하는 코틀린 코드의 일반적인 예제입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Base</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">print</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">Example</span><span class="p">(</span><span class="kd">val</span> <span class="py">x</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Base</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">print</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">MyDelegate</span><span class="p">(</span><span class="n">b</span><span class="p">:</span> <span class="nc">Base</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Base</span> <span class="k">by</span> <span class="n">b</span> <span class="c1">// by 예약어를 사용하여 print() 메소드를 직접 구현하지 않고 b 객체의 이미 구현된 메소드를 사용할 수 있습니다.</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">e</span> <span class="p">=</span> <span class="nc">Example</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span>
    <span class="nc">MyDelegate</span><span class="p">(</span><span class="n">e</span><span class="p">).</span><span class="nf">print</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="delegated-properites">Delegated properites</h2>

<p>메소드 뿐만 아니라 프로퍼티 차원에서도 Delegation 을 지원하는 기능이 있습니다.<br />
일단 예제코드를 보겠습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Delegate</span> <span class="p">{</span>
    <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s">"Hello, getValue()"</span>
    <span class="p">}</span>

    <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">Example</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">delegatedProperty</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="c1">// by 키워드를 통해 String 의 Read, Write 기능을 Delegate 클래스에 위임</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">e</span> <span class="p">=</span> <span class="nc">Example</span><span class="p">()</span>
    <span class="n">e</span><span class="p">.</span><span class="n">delegatedProperty</span> <span class="p">=</span> <span class="s">"Hello, setValue()"</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">delegatedProperty</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>핵심은 <code class="language-plaintext highlighter-rouge">by</code> 키워드 입니다.<br />
<code class="language-plaintext highlighter-rouge">Example</code> 클래스 내에 String 타입의 프로퍼티를 선언하고 <code class="language-plaintext highlighter-rouge">by</code> 키워드를 통해 프로퍼티에 대한 기능을 <code class="language-plaintext highlighter-rouge">Delegate</code> 클래스에 위임하였습니다.
Delegate 클래스를 살펴보면 <code class="language-plaintext highlighter-rouge">getValue()</code>, <code class="language-plaintext highlighter-rouge">setValue()</code> 메소드가 <code class="language-plaintext highlighter-rouge">operator</code> 키워드를 통해 연산자 오버로딩 되어 있는 것을 확인할 수 있습니다.<br />
여기서 눈치챌 수 있는 건, <code class="language-plaintext highlighter-rouge">Delegate</code> 클래스에서 <code class="language-plaintext highlighter-rouge">String</code> 클래스의 읽기와 쓰기 기능을 새로 구현하겠다는 것을 알 수 있습니다.</p>

<p>코드를 실행해보면 <code class="language-plaintext highlighter-rouge">delegatedProperty</code> 에 값을 할당하는 경우 <code class="language-plaintext highlighter-rouge">Delegate</code> 클래스의 <code class="language-plaintext highlighter-rouge">setValue()</code> 메소드가 호출되고, 값을 읽어들이면 <code class="language-plaintext highlighter-rouge">getValue()</code> 메소드가 호출되는 것을 확인할 수 있습니다.</p>

<p><strong>결과</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello, setValue()
Hello, getValue()
</code></pre></div></div>

<h2 id="stardard-delegates">Stardard delegates</h2>

<p>코틀린 표준 라이브러리에서 제공하는 Delegation 기능 집합을 소개합니다.</p>

<h3 id="lazy">Lazy</h3>

<p><code class="language-plaintext highlighter-rouge">lazy()</code> 메소드를 사용하여 프로퍼티의 초기화를 위임할 수 있으며, 프로퍼티의 최초 참조 시 람다식에 구현된 코드를 실행하여 결과 값을 기억한 뒤 추후 참조 시에는 기억된 값만 가져오는 식으로 동작합니다.<br />
<code class="language-plaintext highlighter-rouge">lazy()</code> 메소드는 프로퍼티의 초기화를 최초 참조하는 시점으로 지연시켜 효율적인 메모리 사용을 도와줍니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">lazyValue</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"computed!"</span><span class="p">)</span>
    <span class="s">"Hello"</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">lazyValue</span><span class="p">)</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">lazyValue</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>결과</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>computed!       // 최초 참조의 결과 값
Hello           // 최초 참조의 결과 값
Hello           // 두 번째 참조의 결과 값
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">lazy()</code> 메소드는 <code class="language-plaintext highlighter-rouge">Lazy&lt;&gt;</code> 인터페이스를 구현한 클래스의 객체를 반환하고 있는데, <code class="language-plaintext highlighter-rouge">Lazy&lt;&gt;</code> 인터페이스에 대해서 알아보겠습니다.</p>

<p>안드로이드 개발에서 우리는 Fragment 내부 특정 프로퍼티에 ViewModel 초기화를 위한 아래와 같은 코드를 많이 접해보았을 것입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kd">val</span> <span class="py">viewModel</span><span class="p">:</span> <span class="nc">MyViewModel</span> <span class="k">by</span> <span class="nf">viewModels</span><span class="p">()</span>
</code></pre></div></div>

<p>Fragment ktx를 사용하면 <code class="language-plaintext highlighter-rouge">viewModels()</code> 확장 함수를 사용하여 지연 초기화의 목적을 달성할 수 있습니다.<br />
<code class="language-plaintext highlighter-rouge">viewModels()</code> 의 내부 구현은 어떠한 지 살펴보도록 하겠습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@MainThread</span>
<span class="k">public</span> <span class="k">inline</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="k">reified</span> <span class="nc">VM</span> <span class="p">:</span> <span class="nc">ViewModel</span><span class="p">&gt;</span> <span class="nc">Fragment</span><span class="p">.</span><span class="nf">viewModels</span><span class="p">(</span>
    <span class="k">noinline</span> <span class="n">ownerProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">ViewModelStoreOwner</span> <span class="p">=</span> <span class="p">{</span> <span class="k">this</span> <span class="p">},</span>
    <span class="k">noinline</span> <span class="n">extrasProducer</span><span class="p">:</span> <span class="p">(()</span> <span class="p">-&gt;</span> <span class="nc">CreationExtras</span><span class="p">)?</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span>
    <span class="k">noinline</span> <span class="n">factoryProducer</span><span class="p">:</span> <span class="p">(()</span> <span class="p">-&gt;</span> <span class="nc">Factory</span><span class="p">)?</span> <span class="p">=</span> <span class="k">null</span>
<span class="p">):</span> <span class="nc">Lazy</span><span class="p">&lt;</span><span class="nc">VM</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">owner</span> <span class="k">by</span> <span class="nf">lazy</span><span class="p">(</span><span class="nc">LazyThreadSafetyMode</span><span class="p">.</span><span class="nc">NONE</span><span class="p">)</span> <span class="p">{</span> <span class="nf">ownerProducer</span><span class="p">()</span> <span class="p">}</span>
    <span class="k">return</span> <span class="nf">createViewModelLazy</span><span class="p">(</span>
        <span class="nc">VM</span><span class="o">::</span><span class="k">class</span><span class="p">,</span>
        <span class="p">{</span> <span class="n">owner</span><span class="p">.</span><span class="n">viewModelStore</span> <span class="p">},</span>
        <span class="p">{</span>
            <span class="n">extrasProducer</span><span class="o">?.</span><span class="nf">invoke</span><span class="p">()</span>
            <span class="o">?:</span> <span class="p">(</span><span class="n">owner</span> <span class="k">as</span><span class="p">?</span> <span class="nc">HasDefaultViewModelProviderFactory</span><span class="p">)</span><span class="o">?.</span><span class="n">defaultViewModelCreationExtras</span>
            <span class="o">?:</span> <span class="nc">CreationExtras</span><span class="p">.</span><span class="nc">Empty</span>
        <span class="p">},</span>
        <span class="n">factoryProducer</span> <span class="o">?:</span> <span class="p">{</span>
            <span class="p">(</span><span class="n">owner</span> <span class="k">as</span><span class="p">?</span> <span class="nc">HasDefaultViewModelProviderFactory</span><span class="p">)</span><span class="o">?.</span><span class="n">defaultViewModelProviderFactory</span>
                <span class="o">?:</span> <span class="n">defaultViewModelProviderFactory</span>
        <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>최종적으로 <code class="language-plaintext highlighter-rouge">createViewModelLazy()</code> 를 호출하고 있음을 확인할 수 있습니다.<br />
<code class="language-plaintext highlighter-rouge">createViewModelLazy()</code> 메소드의 구현을 들어가 봅니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@MainThread</span>
<span class="k">public</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">VM</span> <span class="p">:</span> <span class="nc">ViewModel</span><span class="p">&gt;</span> <span class="nc">Fragment</span><span class="p">.</span><span class="nf">createViewModelLazy</span><span class="p">(</span>
    <span class="n">viewModelClass</span><span class="p">:</span> <span class="nc">KClass</span><span class="p">&lt;</span><span class="nc">VM</span><span class="p">&gt;,</span>
    <span class="n">storeProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">ViewModelStore</span><span class="p">,</span>
    <span class="n">extrasProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">CreationExtras</span> <span class="p">=</span> <span class="p">{</span> <span class="n">defaultViewModelCreationExtras</span> <span class="p">},</span>
    <span class="n">factoryProducer</span><span class="p">:</span> <span class="p">(()</span> <span class="p">-&gt;</span> <span class="nc">Factory</span><span class="p">)?</span> <span class="p">=</span> <span class="k">null</span>

<span class="p">):</span> <span class="nc">Lazy</span><span class="p">&lt;</span><span class="nc">VM</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">factoryPromise</span> <span class="p">=</span> <span class="n">factoryProducer</span> <span class="o">?:</span> <span class="p">{</span>
        <span class="n">defaultViewModelProviderFactory</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nc">ViewModelLazy</span><span class="p">(</span><span class="n">viewModelClass</span><span class="p">,</span> <span class="n">storeProducer</span><span class="p">,</span> <span class="n">factoryPromise</span><span class="p">,</span> <span class="n">extrasProducer</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>단순히 <code class="language-plaintext highlighter-rouge">Lazy&lt;&gt;</code> 인터페이스를 구현한 <code class="language-plaintext highlighter-rouge">ViewModelLazy</code> 클래스의 객체를 최종 반환해주는 것을 확인할 수 있습니다.<br />
여기까지 보았을 때, 지연 초기화의 목적을 달성하기엔 힘들어 보입니다.<br />
그렇다면 <code class="language-plaintext highlighter-rouge">ViewModelLazy</code> 구현도 살펴봅니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kd">class</span> <span class="nc">ViewModelLazy</span><span class="p">&lt;</span><span class="nc">VM</span> <span class="p">:</span> <span class="nc">ViewModel</span><span class="p">&gt;</span> <span class="nd">@JvmOverloads</span> <span class="k">constructor</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">viewModelClass</span><span class="p">:</span> <span class="nc">KClass</span><span class="p">&lt;</span><span class="nc">VM</span><span class="p">&gt;,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">storeProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">ViewModelStore</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">factoryProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">ViewModelProvider</span><span class="p">.</span><span class="nc">Factory</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">extrasProducer</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">CreationExtras</span> <span class="p">=</span> <span class="p">{</span> <span class="nc">CreationExtras</span><span class="p">.</span><span class="nc">Empty</span> <span class="p">}</span>
<span class="p">)</span> <span class="p">:</span> <span class="nc">Lazy</span><span class="p">&lt;</span><span class="nc">VM</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">cached</span><span class="p">:</span> <span class="nc">VM</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>

    <span class="k">override</span> <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">VM</span>
        <span class="k">get</span><span class="p">()</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">viewModel</span> <span class="p">=</span> <span class="n">cached</span>
            <span class="k">return</span> <span class="k">if</span> <span class="p">(</span><span class="n">viewModel</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> 
                <span class="c1">// 최초 참조 시 ViewModelProvider 를 통해 인스턴스 생성 후 캐시</span>
                <span class="kd">val</span> <span class="py">factory</span> <span class="p">=</span> <span class="nf">factoryProducer</span><span class="p">()</span>
                <span class="kd">val</span> <span class="py">store</span> <span class="p">=</span> <span class="nf">storeProducer</span><span class="p">()</span>
                <span class="nc">ViewModelProvider</span><span class="p">(</span>
                    <span class="n">store</span><span class="p">,</span>
                    <span class="n">factory</span><span class="p">,</span>
                    <span class="nf">extrasProducer</span><span class="p">()</span>
                <span class="p">).</span><span class="k">get</span><span class="p">(</span><span class="n">viewModelClass</span><span class="p">.</span><span class="n">java</span><span class="p">).</span><span class="nf">also</span> <span class="p">{</span>
                    <span class="n">cached</span> <span class="p">=</span> <span class="n">it</span>
                <span class="p">}</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// 두 번째 참조부터 캐시된 객체 반환</span>
                <span class="n">viewModel</span>
            <span class="p">}</span>
        <span class="p">}</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">isInitialized</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">cached</span> <span class="p">!=</span> <span class="k">null</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">value</code> 의 getter 를 호출하면 <code class="language-plaintext highlighter-rouge">ViewModelProvider</code> 를 통하여 <code class="language-plaintext highlighter-rouge">ViewModel</code> 의 인스턴스를 제공받고, <code class="language-plaintext highlighter-rouge">cached</code> 프로퍼티에 캐시하고 있음을 알 수 있습니다.<br />
싱글톤 패턴의 그것과 비슷하게 구성되어 있으며, <code class="language-plaintext highlighter-rouge">Lazy&lt;&gt;</code> 인터페이스를 상속하여 초기화 동작을 직접 구현하는 방식입니다.</p>

<p>앞서 설명한 <code class="language-plaintext highlighter-rouge">lazy()</code> 메소드도 구현부를 보면 <code class="language-plaintext highlighter-rouge">Lazy&lt;&gt;</code> 인터페이스를 상속한 <code class="language-plaintext highlighter-rouge">SynchronizedLazyImpl</code> 객체를 반환하는 방식으로 비슷하게 초기화를 구현하고 있습니다.<br />
내부에는 멀티스레딩 환경에 대응할 수 있도록, <code class="language-plaintext highlighter-rouge">synchronized</code> 키워드를 사용한 다소 긴 코드를 구현하고 있습니다.</p>

<p>결론은 이와 같은 패턴으로 객체 초기화에 대한 상용구 코드를 줄일 수 있으며, 복잡한 초기화 동작을 특정 클래스(<code class="language-plaintext highlighter-rouge">ViewModelLazy</code>, <code class="language-plaintext highlighter-rouge">SynchronizedLazyImpl</code> 등)에게 위임할 수 있는 멋진 설계라 할 수 있습니다.</p>

<h3 id="observable">Observable</h3>

<p><code class="language-plaintext highlighter-rouge">Delegates.observable()</code> 메소드를 사용하면, 프로퍼티에 할당이 발생할 때 핸들러를 호출하도록 구현할 수 있습니다.<br />
Jetpack 의 LiveData 와 비슷한 동작을 합니다.
초기값을 할당할 수 있으며, 핸들러 내부에서 <code class="language-plaintext highlighter-rouge">old</code>, <code class="language-plaintext highlighter-rouge">new</code> 파라미터를 넘겨받아 원하는 동작을 수행할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">User</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegates</span><span class="p">.</span><span class="nf">observable</span><span class="p">(</span><span class="s">"first"</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">prop</span><span class="p">,</span> <span class="n">old</span><span class="p">,</span> <span class="n">new</span> <span class="p">-&gt;</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"$old -&gt; $new"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">user</span> <span class="p">=</span> <span class="nc">User</span><span class="p">()</span>
    <span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"second"</span>
    <span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"third"</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>결과</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>first -&gt; second
second -&gt; third
</code></pre></div></div>

<h3 id="vetoable">Vetoable</h3>

<p><code class="language-plaintext highlighter-rouge">observable()</code> 과 비슷한 <code class="language-plaintext highlighter-rouge">vetoable()</code> 메소드를 사용하면 핸들러에서 반환되는 Boolean 값을 기준으로 값이 할당되었을 때, 저장할 것인지 말지를 결정할 수 있습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">max</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">by</span> <span class="nc">Delegates</span><span class="p">.</span><span class="nf">vetoable</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">property</span><span class="p">,</span> <span class="n">oldValue</span><span class="p">,</span> <span class="n">newValue</span> <span class="p">-&gt;</span>
    <span class="n">newValue</span> <span class="p">&gt;</span> <span class="n">oldValue</span> <span class="c1">// 새로운 값이 더 클 경우에만 값을 할당</span>
<span class="p">}</span>

<span class="nf">println</span><span class="p">(</span><span class="n">max</span><span class="p">)</span> <span class="c1">// 0</span>

<span class="n">max</span> <span class="p">=</span> <span class="mi">10</span>     <span class="c1">// 새로운 값이 더 커서 조건을 만족함으로 10은 정상적으로 할당됨</span>
<span class="nf">println</span><span class="p">(</span><span class="n">max</span><span class="p">)</span> <span class="c1">// 10</span>

<span class="n">max</span> <span class="p">=</span> <span class="mi">5</span>      <span class="c1">// 새로운 값이 더 작아서 조건을 불만족함으로 5는 할당되지 않고 이전 값 10을 유지함</span>
<span class="nf">println</span><span class="p">(</span><span class="n">max</span><span class="p">)</span> <span class="c1">// 10</span>
</code></pre></div></div>

<h3 id="storing-properties-in-a-map">Storing properties in a map</h3>

<p>프로퍼티 값을 맵에 저장하는 기능입니다.
일반적인 사용 사례로 파싱된 JSON 데이터를 가공하기에 적절할 것 입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">map</span><span class="p">:</span> <span class="nc">Map</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Any</span><span class="p">?&gt;)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="n">map</span>
    <span class="kd">val</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">by</span> <span class="n">map</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">user</span> <span class="p">=</span> <span class="nc">User</span><span class="p">(</span>
        <span class="nf">mapOf</span><span class="p">(</span>
            <span class="s">"name"</span> <span class="n">to</span> <span class="s">"Mickey"</span><span class="p">,</span>
            <span class="s">"age"</span> <span class="n">to</span> <span class="mi">95</span>
        <span class="p">)</span>
    <span class="p">)</span>

    <span class="nf">println</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">name</span><span class="p">)</span> <span class="c1">// Mickey</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">age</span><span class="p">)</span>  <span class="c1">// 95</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위임된 프로퍼티에서 값을 읽으면 <code class="language-plaintext highlighter-rouge">map</code> 에서 값을 가져오게 됩니다.</p>

<p>가변 프로퍼티에 위임하고 싶다면 프로퍼티를 가변으로 설정 후 <code class="language-plaintext highlighter-rouge">MutableMap</code> 으로 선언하여 사용하면 가능합니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">map</span><span class="p">:</span> <span class="nc">MutableMap</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Any</span><span class="p">?&gt;)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="n">map</span>
    <span class="kd">var</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">by</span> <span class="n">map</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">user</span> <span class="p">=</span> <span class="nc">User</span><span class="p">(</span>
        <span class="nf">mutableMapOf</span><span class="p">(</span>
            <span class="s">"name"</span> <span class="n">to</span> <span class="s">"Mickey"</span><span class="p">,</span>
            <span class="s">"age"</span> <span class="n">to</span> <span class="mi">95</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="o">..</span><span class="p">.</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>OGOONS</name></author><category term="Kotlin" /><summary type="html"><![CDATA[코틀린에서는 Delegation 디자인 패턴을 언어 수준에서 지원하는 기능을 가지고 있습니다. 이에 관하여 자세히 알아보고 사용법을 살펴보도록 하겠습니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Kotlin] Properties 와 Backing Fields 에 대하여</title><link href="https://ogoons.github.io/kotlin-properties-backing-fields" rel="alternate" type="text/html" title="[Kotlin] Properties 와 Backing Fields 에 대하여" /><published>2023-08-28T00:00:00+00:00</published><updated>2023-08-28T00:00:00+00:00</updated><id>https://ogoons.github.io/kotlin-properties-backing-fields</id><content type="html" xml:base="https://ogoons.github.io/kotlin-properties-backing-fields"><![CDATA[<p>코틀린의 Properties 와 Backing Fields 에 대해서 알아보고, 추가로 자바와의 차이점과 Backing Properties 에 대해서도 다뤄봅니다.</p>

<h2 id="kotlin-의-property">Kotlin 의 Property</h2>

<p>살펴보기 앞서, Property 는 Field 와 접근자인 Getter, Setter 를 모두 묶어서 부르는 개념이라 할 수 있습니다.<br />
프로퍼티 개념이 생긴 이유는 클래스의 가장 큰 목적인 데이터 캡슐화와 연관되어 있습니다.</p>

<p>자바에서는 프로퍼티가 없고, 필드라는 개념만 있습니다.<br />
코틀린에서의 프로퍼티는 자바의 필드에 더해 기본 접근자 메소드(Getter, Setter)를 자동으로 생성해주기 때문에 이를 묶어서 Property 라고 부릅니다.</p>

<p>물론 프로그래머가 원한다면 자동 생성되는 접근자 메소드 말고 아래 코드와 같이 직접 명시적으로 선언하는 것도 가능합니다.<br />
다만, 프로퍼티의 가변과 불변 타입에 따라서 커스텀 가능한 접근자 메소드가 달라집니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Person</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">age</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="n">name</span> <span class="c1">// name 프로퍼티, 불변 값이므로 Getter 만 커스텀 가능</span>
        <span class="k">get</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// 여기서 데이터 가공 후 반환, 가공이 필요없다면 Getter 를 굳이 커스텀할 필요는 없다.</span>
            <span class="k">return</span> <span class="n">field</span>
        <span class="p">}</span>

    <span class="kd">var</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">age</span> <span class="c1">// age 프로퍼티, 가변 값이므로 Getter, Setter 모두 커스텀 가능</span>
        <span class="k">get</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// 여기서 데이터 가공 후 반환</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">field</span> <span class="p">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="k">throw</span> <span class="nc">AssertionError</span><span class="p">(</span><span class="s">"Must be greater than or equal to 0"</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">field</span>
        <span class="p">}</span>
        <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Assign 한 Value 의 값이 value 매개변수로 들어온다.</span>
            <span class="c1">// 여기서 데이터 가공 후 field 에 보관</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">field</span> <span class="p">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">field</span> <span class="p">=</span> <span class="mi">0</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">field</span> <span class="p">=</span> <span class="n">value</span>
            <span class="p">}</span>
        <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>원칙적으로 <strong>프로퍼티는 상태를 나타내거나 설정하기 위한 목적</strong>으로만 사용하는 것이 가장 좋고, 연산이나 비즈니스 로직이 들어가는 것을 권장하지 않습니다.<br />
그럴 일이 필요하다면, 별도의 함수로 정의하는 것이 좋습니다.</p>

<p>반대로 프로퍼티를 읽거나 쓰고 싶다면, 기본 접근자를 사용해야 합니다.
자바처럼 Getter, Setter 를 함수로 별도 정의해서 사용하는 것은 좋지 않습니다.</p>

<h2 id="backing-fields">Backing Fields</h2>

<p>위 예제 코드를 다시 살펴보면 자바에서의 필드(멤버변수)처럼 접근 메소드 본문에서 <strong>field</strong> 라는 식별자는 데이터 보관을 위한 레퍼런스 임을 눈치챌 수 있습니다.<br />
이 필드를 <strong>Backing Field (지원 필드)</strong> 라고 합니다.</p>

<p>코틀린 클래스에서는 필드는 직접 선언할 수 없고, 프로퍼티를 선언하면 자동으로 생성되며 프로퍼티의 일부로만 사용됩니다.<br />
그리고 접근자 본문에서만 field 식별자를 이용하여 참조 및 조작할 수 있습니다.</p>

<p>Backing Field 의 구체적인 생성 조건은 다음과 같습니다.</p>

<ul>
  <li>접근자(Getter, Setter) 중에 하나 이상의 기본 구현을 사용하는 경우</li>
  <li>접근자(Getter, Setter) 를 재정의하여 field 식별자를 참조하는 경우</li>
</ul>

<p>Getter 에서 field 의 조작은 금지되어 읽기만 가능하며, Setter 에서는 조작과 읽기 모두 가능합니다.</p>

<p>그리고 아래와 같은 경우는 Backing Field 를 생성하지 않습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">isEmpty</span><span class="p">:</span> <span class="nc">Boolean</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">size</span> <span class="p">==</span> <span class="mi">0</span>
</code></pre></div></div>

<h2 id="backing-properties">Backing Properties</h2>

<p>Backing Field 를 사용하면 Getter 와 Setter 범위 내에서만 <code class="language-plaintext highlighter-rouge">field</code> 에 접근이 가능합니다.<br />
하지만 Backing Field 와 접근자 메소드를 사용하지 않는 사례도 있을 수 있습니다.</p>

<p>이러한 경우에 아래 코드와 같이 Backing Property 를 사용하여 원본 데이터를 저장하는 필드(<code class="language-plaintext highlighter-rouge">_age</code>)를 별도로 만들고 해당 필드에 직접 접근하도록 해야합니다.<br />
자동으로 생성되는 Backing Field 가 굳이 불필요하다면 말입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Person</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">_age</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="kd">var</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span>
        <span class="k">get</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">_age</span>
        <span class="p">}</span>
        <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">_age</span> <span class="p">=</span> <span class="n">value</span>
        <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이러한 방식은 기본 Getter, Setter 를 사용하며 <code class="language-plaintext highlighter-rouge">private</code> 프로퍼티인 <code class="language-plaintext highlighter-rouge">_age</code> 에 대한 액세스가 최적화 되어 함수 호출에 대한 오버헤드가 발생하지 않으며, Java 와 같은 방식으로 동작하게 됩니다.</p>

<p>하지만!</p>

<p>굳이 이렇게 할 이유가 없습니다.
위 예제코드는 아래와 같은 방식으로 구현할 수 있기 때문입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Person</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span>
        <span class="k">private</span> <span class="k">set</span>
<span class="p">}</span>
</code></pre></div></div>

<p>더 간결해졌고, 동일하게 동작합니다.</p>

<p>그럼에도 불구하고 Backing Property 를 사용하는 이유는 무엇일까요?
아마도 가장 보편적인 이유로는 public 프로퍼티에서 상위 타입을 반환하기 위함일 것 입니다.</p>

<p>가장 좋은 예는 안드로이드 Jetpack 의 <code class="language-plaintext highlighter-rouge">LiveData</code> 를 <code class="language-plaintext highlighter-rouge">ViewModel</code> 내부에 정의하는 경우입니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="p">:</span> <span class="nc">ViewModel</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">_result</span> <span class="p">=</span> <span class="nc">MutableLiveData</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;()</span>
    <span class="kd">val</span> <span class="py">result</span><span class="p">:</span> <span class="nc">LiveData</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span> <span class="p">=</span> <span class="n">_result</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="explicit-backing-fields">Explicit Backing Fields</h2>

<p>위와 같은 코드는 클래스 외부에서 이미 처리된 결과를 변경하는 것을 방지하는데 효과적입니다.<br />
하지만 매번 두 개의 프로퍼티를 작성해주어야 하는 번거로움이 있습니다.<br />
별로 우아하지도 않고요…</p>

<p>이 부분을 다음 버전의 코틀린에서 해결된 기능이 나왔으면 하는 바램이 있었는데, 안 그래도 꽤 오래전부터(2016년 11월 7일) 논란(<a href="https://youtrack.jetbrains.com/issue/KT-14663">KT-14663</a>) 이 되었던 문제인가 봅니다.</p>

<p>코틀린 이슈 트래커에서 해당 티켓을 살펴보면 이 문제를 해결하기 위해 Kotlin 1.7.0 부터 <strong>Explicit Backing Fields</strong> 라는 개념을 도입하게 되었다는 소식을 맞이할 수 있습니다.<br />
코드는 다음과 같습니다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">C</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">myList</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span>
        <span class="n">field</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>하지만 시험판 코드로서만 사용해볼 수 있고 안정적이지 않다고 합니다. (<a href="https://github.com/Kotlin/KEEP/blob/explicit-backing-fields-re/proposals/explicit-backing-fields.md">레퍼런스 문서</a>)<br />
게다가 막상 사용하자니 아래 경고를 띄우는 것을 보니 아직도 IDE 에서는 지원하지 않는 것 같습니다.</p>

<blockquote>
  <p>Explicit backing field declarations are not supported in FE 1.0</p>
</blockquote>

<p>사용해보려면 앱 수준의 gradle 파일에 <a href="https://gist.github.com/dellisd/a1e2ae1a7e6b61590bef4b2542a555a0">코틀린 옵션</a>을 별도로 추가해야 합니다.<br />
아직도 대중적으로 사용되지는 않으며, 안정화 될때까지 기다려야 할 것 같습니다.</p>

<p>차후에 업데이트 되는 내용이 있다면 추후에 더 글을 작성해보기로 하면서 글을 마칩니다.</p>

<p><strong>Reference</strong><br />
https://kotlinlang.org/docs/properties.html</p>]]></content><author><name>OGOONS</name></author><category term="Kotlin" /><summary type="html"><![CDATA[코틀린의 Properties 와 Backing Fields 에 대해서 알아보고, 추가로 자바와의 차이점과 Backing Properties 에 대해서도 다뤄봅니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[Java] equals() 와 hashCode() 메소드에 대한 고찰</title><link href="https://ogoons.github.io/java-hashcode" rel="alternate" type="text/html" title="[Java] equals() 와 hashCode() 메소드에 대한 고찰" /><published>2023-08-25T00:00:00+00:00</published><updated>2023-08-25T00:00:00+00:00</updated><id>https://ogoons.github.io/java-hashcode</id><content type="html" xml:base="https://ogoons.github.io/java-hashcode"><![CDATA[<p>모르고 넘어갈 수 있는 자바의 <code class="language-plaintext highlighter-rouge">equals()</code> 와 <code class="language-plaintext highlighter-rouge">hashCode()</code> 메소드에 대해서 짚고 넘어가는 글을 작성해 봅니다.</p>

<h2 id="equals">equals()</h2>

<p>자바의 최상위 클래스인 <code class="language-plaintext highlighter-rouge">Object</code> 클래스의 <code class="language-plaintext highlighter-rouge">equals()</code> 메소드를 살펴보면 아래와 같이 객체의 주소를 비교합니다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">obj</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>주소 값을 비교하는 <code class="language-plaintext highlighter-rouge">==</code> 연산자를 사용하여 비교하고 있습니다.</p>

<p>하지만 <code class="language-plaintext highlighter-rouge">String</code> 클래스 객체의 값을 서로 비교하면 보통 주소가 아니라 값이 같으면 <code class="language-plaintext highlighter-rouge">true</code> 를 반환하고 있습니다.<br />
이는 <code class="language-plaintext highlighter-rouge">equals()</code> 메소드를 오버라이딩해서 사용하고 있기 때문입니다.</p>

<h4 id="string-클래스의-equals">String 클래스의 equals()</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">anObject</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">aString</span><span class="o">)</span>
            <span class="o">&amp;&amp;</span> <span class="o">(!</span><span class="no">COMPACT_STRINGS</span> <span class="o">||</span> <span class="k">this</span><span class="o">.</span><span class="na">coder</span> <span class="o">==</span> <span class="n">aString</span><span class="o">.</span><span class="na">coder</span><span class="o">)</span>
            <span class="o">&amp;&amp;</span> <span class="nc">StringLatin1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">value</span><span class="o">,</span> <span class="n">aString</span><span class="o">.</span><span class="na">value</span><span class="o">);</span> <span class="c1">// 여기서 값을 비교</span>
<span class="o">}</span>
</code></pre></div></div>

<p>이와 같이 내부 값이 동일한 경우 오버라이딩 하여 자주 사용하는 메소드 입니다.</p>

<p>하지만, <code class="language-plaintext highlighter-rouge">Hash</code> 를 사용하는 Collection 에서 이러한 객체를 다루게 되면 문제가 됩니다.<br />
예를 들어 두 개의 동물(호랑이)을 의미하는 객체를 생성한 후 <code class="language-plaintext highlighter-rouge">equals()</code> 를 오버라이딩 한 후, 이를 중복을 허용하지 않는 Collection 인 <code class="language-plaintext highlighter-rouge">HashSet</code> 에 추가해 보겠습니다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Animal</span> <span class="o">{</span>
    <span class="nc">Animal</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>

    <span class="c1">// IDE 의 Generate 기능 사용하여 정의</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">o</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">o</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">getClass</span><span class="o">()</span> <span class="o">!=</span> <span class="n">o</span><span class="o">.</span><span class="na">getClass</span><span class="o">())</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
        <span class="nc">Animal</span> <span class="n">animal</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Animal</span><span class="o">)</span> <span class="n">o</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">name</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">animal</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">Animal</span> <span class="n">animal1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>
        <span class="nc">Animal</span> <span class="n">animal2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>

        <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Animal</span><span class="o">&gt;</span> <span class="n">animalSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;();</span>
        <span class="n">animalSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">animal1</span><span class="o">);</span>
        <span class="n">animalSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">animal2</span><span class="o">);</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">animalSet</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>결과</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">equals()</code> 를 재정의하여 값이 같은 경우 <code class="language-plaintext highlighter-rouge">true</code> 를 반환하도록 정의했지만, <code class="language-plaintext highlighter-rouge">HashSet</code> 내부에서는 이를 동일한 객체로 취급하지 않아 2개의 아이템이 추가된 것을 확인할 수 있습니다.</p>

<p>이를 해결하려면 아래 코드와 같이 <code class="language-plaintext highlighter-rouge">hashCode()</code> 를 함께 오버라이딩하여 필드값을 기반으로 hash 값을 반환해주면 해결할 수 있습니다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Animal</span> <span class="o">{</span>
    <span class="nc">Animal</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>

    <span class="c1">// IDE 의 Generate 기능 사용하여 정의</span>
    <span class="nd">@Override</span> 
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">o</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">o</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">getClass</span><span class="o">()</span> <span class="o">!=</span> <span class="n">o</span><span class="o">.</span><span class="na">getClass</span><span class="o">())</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
        <span class="nc">Animal</span> <span class="n">animal</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Animal</span><span class="o">)</span> <span class="n">o</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">name</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">animal</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
        <span class="c1">// name 필드를 기반으로 Objects 클래스의 hash() static 메소드를 사용하여 hash 값을 생성하여 반환한다.</span>
        <span class="k">return</span> <span class="nc">Objects</span><span class="o">.</span><span class="na">hash</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> 
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">Animal</span> <span class="n">animal1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>
        <span class="nc">Animal</span> <span class="n">animal2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>

        <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Animal</span><span class="o">&gt;</span> <span class="n">animalSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;();</span>
        <span class="n">animalSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">animal1</span><span class="o">);</span>
        <span class="n">animalSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">animal2</span><span class="o">);</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">animalSet</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>결과</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1
</code></pre></div></div>

<p>여기까지 살펴보면 <code class="language-plaintext highlighter-rouge">Animal</code> 클래스의 <code class="language-plaintext highlighter-rouge">equals()</code> 와 <code class="language-plaintext highlighter-rouge">hashCode()</code> 메소드를 모두 오버라이딩 해야 문제없이 사용할 수 있는 것을 알 수 있습니다.<br />
이러한 이유는, Hash 기반의 Collection 에서는 <strong><code class="language-plaintext highlighter-rouge">hashCode()</code> 반환값이 우선 일치하고, <code class="language-plaintext highlighter-rouge">equals()</code> 반환값도 <code class="language-plaintext highlighter-rouge">true</code></strong> 여야 비로소 동등한 객체로 취급하기 때문입니다.</p>

<p>그렇다면, 아래에서 <code class="language-plaintext highlighter-rouge">hashCode()</code> 메소드에 대해 좀 더 다뤄보겠습니다.</p>

<h2 id="hashcode">hashCode()</h2>

<p>각각의 객체를 구별하기 위해 사용되며, 객체의 메모리 주소를 해싱 알고리즘을 사용한 32비트 정수로 변환한 값을 반환합니다.
직접 메모리 주소를 반환하지 않는 이유는, 메모리를 직접 다루지 못하게 하여 안정성을 추구하는 자바의 컨셉 때문이라고 여겨집니다.
이 값은 대체로 고유하지만, 특정 케이스에서는 중복될 수 있는 여지가 있습니다. (이 부분은 여기서는 다루지 않겠습니다.)</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Animal</span> <span class="o">{</span>
    <span class="nc">Animal</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">Animal</span> <span class="n">animal1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>
        <span class="nc">Animal</span> <span class="n">animal2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Animal</span><span class="o">(</span><span class="s">"Tiger"</span><span class="o">);</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">animal1</span><span class="o">.</span><span class="na">hashCode</span><span class="o">());</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">animal2</span><span class="o">.</span><span class="na">hashCode</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>결과</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1804094807
951007336
</code></pre></div></div>

<p>위 결과와 같이 같은 필드 값을 가지고 있는 두 객체의 hashCode 값은 서로 다름을 알 수 있습니다.
앞서 <code class="language-plaintext highlighter-rouge">equals()</code> 메소드 부분에서 다룬 것 처럼 서로 다른 객체를 동등하게 취급하고 싶다면 <code class="language-plaintext highlighter-rouge">hashCode()</code> 를 오버라이딩 하여 주소 기반이 아닌 값을 기반으로 hash 값을 생성해야 함을 반드시 명심해야 합니다.</p>]]></content><author><name>OGOONS</name></author><category term="Java" /><summary type="html"><![CDATA[모르고 넘어갈 수 있는 자바의 equals() 와 hashCode() 메소드에 대해서 짚고 넘어가는 글을 작성해 봅니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ogoons.github.io/assets/images/sns-share.png" /><media:content medium="image" url="https://ogoons.github.io/assets/images/sns-share.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>