<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-baik.log</title>
        <link>https://velog.io/</link>
        <description>필요한 내용을 공부하고 저장합니다.</description>
        <lastBuildDate>Fri, 23 Feb 2024 02:41:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-baik.log</title>
            <url>https://velog.velcdn.com/images/dev-baik/profile/226889e7-75ef-4db7-9e0a-b03598613396/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-baik.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-baik" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CI & CD]]></title>
            <link>https://velog.io/@dev-baik/CI-CD</link>
            <guid>https://velog.io/@dev-baik/CI-CD</guid>
            <pubDate>Fri, 23 Feb 2024 02:41:12 GMT</pubDate>
            <description><![CDATA[<h2 id="cicontinuous-integration">CI(Continuous Integration)</h2>
<ul>
<li>개발자들이 작성한 코드를 공유 저장소에 정기적으로 통합하는 접근 방식이다.</li>
<li>이 과정에서 코드는 자동화된 빌드와 테스트를 거쳐 통합되기 때문에 오류가 발생하면 즉시 알 수 있다.</li>
<li>예를 들어, 오랜 시간 동안 변경된 코드가 통합되지 않다가 올라가게 되면 conflict가 발생하므로 해결하는 데 오랜 시간이 소요된다.</li>
<li>장점 : 작은 변경 사항이 자주 통합되므로, merge conflict가 발생할 가능성이 줄어들고, 발생한 경우에도 작은 단위의 문제로 나누어 빠르게 해결할 수 있다. </li>
</ul>
<h2 id="cdcontinuous-deployment">CD(Continuous Deployment)</h2>
<ul>
<li>변경 사항이 저장소에 병합되면 자동으로 프로덕션 환경에 배포되는 것을 의미한다.</li>
</ul>
<h2 id="github-actions">Github Actions</h2>
<ul>
<li>CI/CD tool 중 하나로, YAML(야물) 언어를 사용하는 .yml 파일을 읽어 CI/CD를 수행한다.</li>
</ul>
<h2 id="용어">용어</h2>
<h3 id="workflow">Workflow</h3>
<ul>
<li>자동화된 전체 프로세스를 나타내며, 여러 Job으로 구성된다.</li>
<li>특정 Event에 의해 예약되거나 트리거된다.<ul>
<li>트리거 : 어느 특정한 동작에 반응해 자동으로 필요한 동작을 실행하는 것</li>
</ul>
</li>
<li>GitHub Actions에서는 .github/workflows/*.yml 파일에 정의된다.</li>
</ul>
<h3 id="event">Event</h3>
<ul>
<li>Workflow를 트리거하는 활동 또는 규칙</li>
<li>예를 들어, 특정 브랜치로의 push나 pull request 등이 해당된다.</li>
</ul>
<h3 id="jobs">Jobs</h3>
<ul>
<li>Workflow 내에서 실행되는 개별 작업 단위</li>
<li>VM(Virtual Machine) 환경에서 실행된다.<ul>
<li>VM 환경에서 실행되는 작업은 주로 빌드, 테스트, 배포 등과 같은 CI/CD 작업을 수행한다. 이를 통해 GitHub Actions는 다양한 환경에서 코드를 실행하고 테스트하여 안정성을 확인하고, 배포 준비를 하게된다.</li>
</ul>
</li>
<li>하나의 Job은 여러 개의 Step으로 구성되며, 각 Job은 병렬로 실행될 수 있다.</li>
</ul>
<h3 id="step">Step</h3>
<ul>
<li>Job 내에서 순차적으로 수행되는 프로세스 단위</li>
</ul>
<h3 id="action">Action</h3>
<ul>
<li>Workflow 내에서 수행되는 가장 작은 작업 단위</li>
<li>Step 내에서 수행되는 독립적인 명령</li>
<li>사용자가 직접 생성하거나, Github Marketplace에서 가져올 수 있다.</li>
</ul>
<h3 id="runner">Runner</h3>
<ul>
<li>Workflow가 실행될 인스턴스</li>
</ul>
<h2 id="기본-android-yml-파일">기본 Android yml 파일</h2>
<pre><code class="language-yml"># 해당 yml 파일의 이름
# 설정된 이름으로 Actions 탭의 workflows 항목에서 확인 가능
name: Android CI

# Event에 대해 작성하는 부분
# main으로 push나 pr 이벤트가 발생했을 때를 workflow를 실행시키는 트리거로 사용
on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]

# Job에 대해 작성하는 부분
jobs:
  build:

    # Job이 실행 될 OS : ubuntu, macos, window
    # -lastest : 제공하는 OS의 가장 최신 버전
    runs-on: ubuntu-latest

    # 하이픈(-)을 통해 구분되어 작성되어 있는 항목들이
    # Step 구문에 작성되어 있는 Job에서 수행되는 프로세스들을 의미한다.
    steps:
      - uses: actions/checkout@v3 # 해당 Step에서 수행할 Action
      - name: set up JDK 11 # 해당 Step의 이름
        uses: actions/setup-java@v3 # 해당 Step에서 실행될 커맨드 라인
        with: # 해당 Step에서 수행되는 Action에 정의되는 Parameter. Key-Value 형태
          java-version: &#39;11&#39;
          distribution: &#39;temurin&#39;
          cache: gradle

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Build with Gradle
        run: ./gradlew build
</code></pre>
<ul>
<li>actions/checkout@v3 : 소스코드를 가져온다.</li>
<li>actions/setup-java@v3 : 작업을 수행하기 위한 JDK를 설정한다.</li>
</ul>
<h2 id="localproperties를-사용하여-키-관리하기">local.properties를 사용하여 키 관리하기</h2>
<p><a href="https://sonseungha.tistory.com/623">https://sonseungha.tistory.com/623</a></p>
<h2 id="ktlint-check">ktlint check</h2>
<ul>
<li>CI를 할 때 ktlint도 확인해줄 수 있다.</li>
<li>단, project 단의 build.gradle 파일에 ktlint dependency를 추가해주어야 한다.</li>
<li>Gradle Groovy 설정 (build.gradle)<pre><code class="language-kotlin">buildscript {
  repositories {
      maven {
          url &#39;https://plugins.gradle.org/m2/&#39;
      }
  }
  dependencies {
      classpath &#39;org.jlleitschuh.gradle:ktlint-gradle:12.1.0&#39;
  }
}
</code></pre>
</li>
</ul>
<p>plugins {
    id &#39;org.jlleitschuh.gradle.ktlint&#39; version &#39;12.1.0&#39;
}</p>
<pre><code>- Gradle Kotlin 설정 (build.gradle.kts)
```kotlin
buildscript {
    repositories {
        maven(url = &quot;https://plugins.gradle.org/m2/&quot;)
    }
    dependencies {
        classpath(&quot;org.jlleitschuh.gradle:ktlint-gradle:9.1.0&quot;)
    }
}
plugins {
    id(&quot;org.jlleitschuh.gradle.ktlint&quot;) version &quot;9.1.0&quot;
}</code></pre><ul>
<li>yml 파일 추가
```yml<h1 id="steps-아래-작성">steps 아래 작성</h1>
<h1 id="ktlint-test">ktlint test</h1>
</li>
<li>name: Run ktlint
run: ./gradlew ktlintCheck <pre><code></code></pre></li>
</ul>
<h2 id="gradle-캐싱">Gradle 캐싱</h2>
<ul>
<li><a href="https://kotlinworld.com/399">https://kotlinworld.com/399</a>
```yml</li>
<li>name: Cache Gradle packages
  uses: actions/cache@v3
  with:<pre><code>path: |
  ~/.gradle/caches
  ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;, &#39;**/buildSrc/**/*.kt&#39;) }}
restore-keys: |
  ${{ runner.os }}-gradle-</code></pre><pre><code></code></pre></li>
</ul>
<h2 id="최종">최종</h2>
<pre><code class="language-yml">name: Android CI

on:
  # main, dev, feat branch pr 올리면 아래 jobs 수행
  pull_request:
    branches:
      - &#39;main&#39;
      - &#39;develop&#39;
      - &#39;feat/*&#39;

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      # timestamp
      - name: Print start time
        run: |
          echo &quot;Workflow started at $(date)&quot;

      # code branch checkout
      - uses: actions/checkout@v3
      - name: set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;
          cache: gradle

      # gradle 캐싱 작업
      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;, &#39;**/buildSrc/**/*.kt&#39;) }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build

      # ktlint test
      - name: Run ktlint
        run: ./gradlew ktlintCheck</code></pre>
<h4 id="참고한-사이트">참고한 사이트</h4>
<ul>
<li><a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions">https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions</a></li>
<li><a href="https://heegs.tistory.com/92">https://heegs.tistory.com/92</a></li>
<li><a href="https://zzsza.github.io/development/2020/06/06/github-action/">https://zzsza.github.io/development/2020/06/06/github-action/</a></li>
<li><a href="https://happy-kmc.tistory.com/43">https://happy-kmc.tistory.com/43</a></li>
<li><a href="https://blog.benelog.net/ktlint.html#gradle_%EB%B9%8C%EB%93%9C_%EC%84%A4%EC%A0%95">https://blog.benelog.net/ktlint.html#gradle_%EB%B9%8C%EB%93%9C_%EC%84%A4%EC%A0%95</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린 완벽 가이드 3장]]></title>
            <link>https://velog.io/@dev-baik/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-3%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-3%EC%9E%A5</guid>
            <pubDate>Fri, 15 Dec 2023 11:28:16 GMT</pubDate>
            <description><![CDATA[<h2 id="1-식이-본문인-함수란-무엇인가">1. 식이 본문인 함수란 무엇인가?</h2>
<p>return 키워드와 블록을 만드는 중괄호({})를 생략하는 단일 식 형태의 함수로, 컴파일러가 표현식의 타입을 추론할 수 있기 때문에 명시적인 반환 타입을 생략해도 된다.</p>
<pre><code class="language-kotlin">fun circleArea(radius: Double) = { PI * radius * radius }</code></pre>
<h3 id="블록이-본문인-함수-대신-식이-본문인-함수를-쓰면-어떤-경우에-더-좋을까">블록이 본문인 함수 대신 식이 본문인 함수를 쓰면 어떤 경우에 더 좋을까?</h3>
<p>로직이 단순하고 결과를 바로 반환하는 경우 식이 본문인 함수를 쓰는게 더 좋다.</p>
<p>블록이 본문인 함수는 복잡한 연산이나 여러 단계의 처리가 필요한 경우에 더 적절하다.</p>
<h2 id="2-디폴트-파라미터와-함수-오버로딩-중-어느-쪽을-써야-할지-어떻게-결정할-수-있을까">2. 디폴트 파라미터와 함수 오버로딩 중 어느 쪽을 써야 할지 어떻게 결정할 수 있을까?</h2>
<p>미리 정해진 디폴트 값을 사용할 수 있게 하고자 메서드를 오버로딩해야 하는 경우 디폴트 파라미터를 사용하고, 파라미터의 개수나 타입에 따라 다른 로직을 수행해야 하는 경우 오버로딩을 사용하는 것이 좋다.</p>
<h4 id="🙄-외전--반은-맞고-반은-틀리다">🙄 외전 : 반은 맞고, 반은 틀리다.</h4>
<ul>
<li>함수가 호출되는 런타임에서 좌에서 우로 해석하기 때문에 c는 디폴트 값으로 들어간(스택에 다시 쌓인) a와 b를 알고 있다. b는 a를 알고 있다.<pre><code class="language-kotlin">fun plus(a: Int, b: Int) = a + b
</code></pre>
</li>
</ul>
<p>fun c(a: Int, b: Int = plus(a, 4), c: Int = plus(a + b)) = a + b + c</p>
<pre><code>
## 3. 이름 붙은 인자를 사용할 경우의 장단점은 무엇인가?
**장점** : 인자의 순서를 바꿀 수 있다.
- **WHY)** : 인자의 위치가 아니라 파라미터의 이름을 명시하여 인자를 전달하는 방식이기 때문이다.
- **외전** : 두 인자의 타입이 같다면 컴파일 에러가 발생하지 않고 런타임 에러가 &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;발생할 수 있다.
  - **BUT)** 두 인자의 타입이 다르다면 컴파일 에러가 발생한다.
```kotlin
fun plus(a: Int, b: Int) = a + b

fun main() {
    val a = 3
    val b = 7
    print(plus(b, a)) // 10 
}</code></pre><p><strong>단점</strong> : 위치 기반 인자와 이름 붙은 인자를 함께 사용한 경우 원래 인자가 들어가야 할 위치에 이름 붙은 인자를 지정해야 정상 처리되며, 그렇지 않은 경우 위치 기반 인자의 타입이 어긋나거나 이미 할당된 인자를 재할당하기 때문에 컴파일 오류가 발생한다.</p>
<h2 id="4-인자-개수가-가변적인-함수를-정의하는-방법은-무엇인가">4. 인자 개수가 가변적인 함수를 정의하는 방법은 무엇인가?</h2>
<p>파라미터 정의 앞에 <code>vararg</code> 변경자(modifier)를 붙여 배열 타입으로 넘기거나 스프레드(spread) 연산자인 <code>*</code>를 사용하여 배열을 가변 인자 대신 넘길 수 있다.</p>
<h3 id="코틀린과-자바에서-vararg-함수는-어떻게-다른가">코틀린과 자바에서 vararg 함수는 어떻게 다른가?</h3>
<p>자바에서는 vararg 파라미터가 항상 마지막 파라미터이지만, 코틀린에서는 vararg 파라미터가 함수 파라미터 중 어디에나 올 수 있다. 다만, vararg 파라미터 이후의 파라미터는 이름 붙은 인자로만 전달할 수 있다.</p>
<pre><code class="language-kotlin">fun printSorted(vararg items: Int, prefix: String = &quot;&quot;) { }

fun main() {
    printSorted(6, 2, 10, 1, &quot;!&quot;) // Error

    printSorted(6, 2, 10, 1, prefix = &quot;!&quot;) // Success

    val numbers = intArrayOf(6, 2, 10, 1)
    printSorted(*numbers) // Success
}</code></pre>
<ul>
<li><strong>외전</strong> :  인자의 형태가 블록인 경우(람다) 명시적인 인자가 필요없다.<pre><code class="language-kotlin">fun printSorted(vararg items: Int, block: () -&gt; Unit = {}) { }
</code></pre>
</li>
</ul>
<p>fun main() {
    printSorted(1, 2, 3 , 5) { /<em>....</em>/ } // Success
} </p>
<pre><code>
## 5. Unit과 Nothing 타입을 어디에 사용하는가? (이들을 자바의 void와 비교해 설명하라.)
Unit은 함수가 의미 없는 반환값을 돌려줄 때 사용한다.

Nothing은 프로그램의 순차적 제어 흐름이 그 부분에서 끝나되 어떤 잘 정의된 값에 도달하지 못할 때 사용한다.

Unit은 자바 void에 해당하는 코틀린 타입으로, Nothing과 달리 한 가지 인스턴스가 존재하는데, 이 인스턴스는 보통 유용한 값이 없다는 사실을 표현한다. 반면 Nothing은 아예 값이 없다는 사실을 표현한다.

### Nothing이나 Unit이 타입인 함수를 정의해 사용할 수 있는가?
Nothing 타입은 return의 경우 이 문장을 둘러싼 함수가 끝난다. 모든 코틀린 타입의 하위 타입으로 간주되기 때문에 식이 필요한 위치에 return을 사용해도 타입 오류가 발생하지 않는다.
- `return e`의 e 값은 return 식의 값이 아니라 함수의 반환값이라는 점에 유의하자. return 식 자체는 아무 값이 없고 Nothing 타입에 속한다.

Unit 타입은 함수에서 결과가 항상 Unit으로 동일하기 때문에 결과를 지정하는 return 문을 쓸 필요가 없다. 하지만 함수 본문의 끝에 도달하기 전에 함수 실행을 마치려면 return 문을 사용해 함수를 끝내야 한다.

## 6. return 0과 같은 코드의 의미를 설명해보라.
해당 함수가 정수 0을 반환하는 것으로, 해당 함수의 반환 타입이 Int일 때 사용된다. 보통 함수의 반환 유형을 명시적으로 선언하고, 반환값이 필요 없는 경우에는 Unit을 사용한다. 따라서 return 0은 주로 함수가 특정 결과를 반환하는 상황에서 사용된다.

- 함수가 어떤 계산을 수행하고 그 결과를 반환하는데, 그 결과가 0이면 성공적으로 완료되었음을 나타낼 때 사용할 수 있다.
```kotlin
fun exampleFunction(): Int {
    // 어떤 계산을 수행한 후, 결과가 0이면 성공적으로 완료되었음을 나타냄
    return 0
}

fun main() {
    val result = exampleFunction()

    if (result == 0) {
        println(&quot;함수가 성공적으로 실행되었습니다.&quot;)
    } else {
        println(&quot;함수 실행 중 오류가 발생했습니다.&quot;)
    }
}</code></pre><h3 id="이런-코드가-올바르지만-불필요한-중복이-있는-것으로-여겨지는-이유는-무엇인가">이런 코드가 올바르지만 불필요한 중복이 있는 것으로 여겨지는 이유는 무엇인가?</h3>
<p>함수의 성공 또는 실패를 나타내는 값이 0 하나로 특정되어 있기 때문이다. 즉, 함수의 성공 여부를 판단하는 데 사용되는 값이 항상 0이라면, 매번 이 값을 확인하는 코드가 중복된 느낌을 줄 수 있다.</p>
<p>코틀린에서는 일반적으로 함수가 성공적으로 수행되면 Unit을 반환하거나, 실패한 경우 예외를 던지는 등의 방식을 사용한다. 이로써 성공 또는 실패를 판단하는 코드에서 중복을 최소화하고 가독성을 높일 수 있다.</p>
<pre><code class="language-kotlin">fun exampleFunction(): Unit {
    // 어떤 계산을 수행
    // 성공적으로 완료되면 아무것도 반환하지 않음 (Unit)
}

fun main() {
    try {
        exampleFunction()
        println(&quot;함수가 성공적으로 실행되었습니다.&quot;)
    } catch (e: Exception) {
        println(&quot;함수 실행 중 오류가 발생했습니다: $e&quot;)
    }
}</code></pre>
<h2 id="7-return-문을-사용하지-않는-함수를-정의할-수-있는가">7. return 문을 사용하지 않는 함수를 정의할 수 있는가?</h2>
<p>반환값이 필요 없는 함수(반환 타입이 Unit인 경우)를 정의할 때 return 문을 생략할 수 있다.</p>
<h2 id="8-지역-함수란-무엇인가">8. 지역 함수란 무엇인가?</h2>
<p>특정 함수 내에 정의된 함수로, 자신을 둘러싼 함수의 변수(args)나 함수에 접근할 수 있다. 지역 함수와 변수는 가시성 변경자를 붙일 수 없다.</p>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    fun swap(i: Int, j: Int): String {
        val chars = args[0].toCharArray()
        val tmp = chars[i]
        chars[i] = chars[j]
        chars[j] = tmp
        return chars.concatToString()
    }

    println(swap(0, args[0].lastIndex))
}</code></pre>
<h3 id="이런-함수를-자바에서는-어떻게-흉내-낼-수-있을까">이런 함수를 자바에서는 어떻게 흉내 낼 수 있을까?</h3>
<p>지역 함수를 둘러싼 영역의 변수나 파라미터 목록 등의 문맥을 포획해주는 특별한 클래스(익명 클래스, 람다 표현식)를 선언한다. 그래서 지역 함수를 호출할 때마다 이런 특별한 객체를 생성하는 부가 비용이 든다.</p>
<h2 id="9-공개public와-비공개private-최상위-함수는-어떤-차이가-있는가">9. 공개(public)와 비공개(private) 최상위 함수는 어떤 차이가 있는가?</h2>
<p>최상위 함수는 디폴트로 공개 가시성을 가지며, 함수가 정의된 파일 내부뿐 아니라 프로젝트 어디에서나 쓰일 수 있다. 반면에 최상위 함수를 비공개로 정의하면 함수가 정의된 파일 안에서만 해당 함수를 볼 수 있다.</p>
<h2 id="10-패키지를-사용해-코드를-어떻게-여러-그룹으로-나눌-수-있는가">10. 패키지를 사용해 코드를 어떻게 여러 그룹으로 나눌 수 있는가?</h2>
<p>맨 앞에 패키지 이름을 지정하면 파일에 있는 모든 최상위 선언(타입, 함수, 프로퍼티)을 지정한 패키지 내부에 넣을 수 있다. 패키지를 사용하지 않으면 컴파일러는 파일이 디폴트 최상위 패키지에 속한다고 가정한다. 디폴트 최상위 패키지는 이름이 없다.</p>
<pre><code class="language-kotlin">package foo.bar.util

fun readInt(radix: Int = 10) = readLine()!!.toInt(radix)

// 패키지 계층에서 루트 패키지 바로 아래에 있는 경우
package numberUtil

fun readDouble() = readLine()!!.toDouble()</code></pre>
<h3 id="자바와-코틀린-패키지의-가장-핵심적인-차이는-무엇인지-설명하라">자바와 코틀린 패키지의 가장 핵심적인 차이는 무엇인지 설명하라.</h3>
<p>자바에서는 패키지 구조와 컴파일 대상 루트에 있는 소스 트리 디렉터리 구조가 같아야 한다. 둘의 경로가 다르면 컴파일 오류가 발생한다.</p>
<p>코틀린의 패키지 계층 구조는 소스 파일에 있는 패키지 디렉티브로부터 구성된 별도의 구조다. 즉 소스 파일 트리와 패키지 계층 구조가 다를 수도 있다.</p>
<ul>
<li>예를 들어 소스 파일은 모두 한 디렉터리 아래에 있지만 각각이 서로 다른 패키지에 포함될 수도 있고, 한 패키지에 포함된 소스 파일들이 모두 서로 다른 디렉터리에 들어갈 수도 있다.</li>
</ul>
<h2 id="11-임포트-별명이란-무엇인가">11. 임포트 별명이란 무엇인가?</h2>
<p>임포트한 선언에 새 이름을 부여하는 것으로, 새 이름은 임포트 이렉티브가 있는 파일 전체 영역에서 유효하다. 또 다른 형태의 임포트로 어떤 영역에 속한 모든 선언을 한꺼번에 임포트할 수 있다. 전체 이름 뒤에 <code>*</code>를 붙이면 된다.</p>
<h3 id="자바의-정적-임포트와-비슷한-임포트를-코틀린에서는-어떻게-처리하는가">자바의 정적 임포트와 비슷한 임포트를 코틀린에서는 어떻게 처리하는가?</h3>
<p>자바에서는 임포트 별명을 줄 수 없기 때문에 이름이 같으면 풀패키지 밖에 명시할 수 없다.</p>
<pre><code class="language-kotlin">import foo.readInt as fooReadInt
import bar.readInt as barReadInt

fun main() {
    val n = fooReadInt()
    val m = barReadInt()
}</code></pre>
<p>코틀린에서는 <a href="https://velog.io/@kasania/Java-Static-import%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B4%80%EC%B0%B0">정적 임포트 구문</a> 없이 일반적인 임포트 디렉티브 구문을 사용해 클래스의 메소드나 속성을 임포트할 수 있다.</p>
<h2 id="12-if-문식은-어떤-일을-하는가-각각을-자바의-if-문-및-3항-조건-연산자와-비교해보라">12. if 문/식은 어떤 일을 하는가? 각각을 자바의 if 문 및 3항 조건 연산자(?:)와 비교해보라.</h2>
<p>if 문/식은 불(boolean) 식의 결과에 따라 두 가지 대안 중 하나를 선택할 수 있다. 코틀린 if는 자바 if 문과 비슷한 문법을 제공하지만, 3항 연산자(조건 ? 참일 때_식 : 거짓일 때_식)가 없다. 대신에 if 문을 식으로 쓸 수 있다는 점이 이 단점을 대부분 상쇄해준다. if 문을 식으로 사용할 때는 양 가지가 모두 있어야 한다. </p>
<h2 id="13-when-문을-처리하는-알고리즘을-설명하라">13. when 문을 처리하는 알고리즘을 설명하라.</h2>
<p>when 문은 코드에 쓰여져 있는 순서대로 조건을 검사해서 맨 처음으로 참으로 평가되는 조건을 찾고, 해당 조건에 대응하는 문을 실행한다. 만약 모든 조건이 거짓이라면 else 부분을 실행한다.  </p>
<h3 id="자바-switch와-코틀린-when은-어떤-차이가-있는가">자바 switch와 코틀린 when은 어떤 차이가 있는가?</h3>
<ol>
<li>when에서는 임의의 조건을 검사할 수 있지만 switch에서는 주어진 식의 여러 가지 값 중 하나만 선택할 수 있다. 게다가 자바의 switch 문은 폴스루(fall-through)라는 의미를 제공한다. 코틀린 when은 조건을 만족하는 가지만 실행하고 절대 폴스루를 하지 않는다.</li>
</ol>
<ul>
<li>폴스루 : 어떤 조건을 만족할 때 프로그램이 해당 조건에 대응하는 문을 실행하고 명시적으로 break를 만날 때까지 그 이후의 모든 가지를 실행한다.</li>
</ul>
<ol start="2">
<li>switch 식에는 범위 검사(코틀린의 in/!in)를 지원하지 않고 오직 정수, 이넘, 문자열 같은 몇 가지 타입에 대해서만 사용할 수 있다. when에서는 상수가 아닌 임의의 식을 사용해도 된다.</li>
</ol>
<h2 id="14-자바-for-int-i--0-i--100-i와-같이-수를-세는-루프를-코틀린에서는-어떻게-구현하는가">14. 자바 for (int i = 0; i &lt; 100; i++)와 같이 수를 세는 루프를 코틀린에서는 어떻게 구현하는가?</h2>
<pre><code class="language-kotlin">for (i in 0..99) or for (i in 0 until 100)</code></pre>
<h2 id="15-코틀린이-제공하는-루프-문에는-어떤-것이-있는가">15. 코틀린이 제공하는 루프 문에는 어떤 것이 있는가?</h2>
<ul>
<li>for, while, do-while</li>
</ul>
<h3 id="while과-dowhile의-차이는-무엇인가">while과 do...while의 차이는 무엇인가?</h3>
<p>while 문은 어떤 조건이 참인 동안 루프를 실행하지만 루프 몸통을 실행하기 전에 조건을 먼저 검사한다. 이 경우 처음부터 조건이 거짓이면 루프 몸통이 한 번도 실행되지 않는다.</p>
<p>do-while 문은 루프 몸통을 실행한 다음에 조건을 검사하므로 루프 몸통이 최소 한 번은 실행된다. </p>
<h2 id="16-break와-continue를-사용해-루프의-제어-흐름을-어떻게-변경할-수-있는가">16. break와 continue를 사용해 루프의 제어 흐름을 어떻게 변경할 수 있는가?</h2>
<p>break는 즉시 루프를 종료시키고, 실행 흐름이 루프 바로 다음 문으로 이동하게 만든다.</p>
<p>continue는 현재 루프 이터레이션(iteration)을 마치고 조건 검사로 바로 진행하게 만든다.</p>
<h2 id="17-예외-처리-과정을-전체적으로-설명하라">17. 예외 처리 과정을 전체적으로 설명하라.</h2>
<p>프로그램은 예외를 잡아내는 핸들러를 찾는다. 예외와 일치하는 핸들러가 있다면 예외 핸들러가 처리한다.
현재 함수 내부에서 핸들러를 찾을 수 없으면 함수 실행이 종료되고 함수가 스택에서 제거 된다. 그리고 호출한 쪽에서 핸들러 검색을 수행한다. 이런 경우를 호출자에게 전파했다고 말한다.
프로그램 진입접에 이를 때까지 예외를 잡아내지 못하면 현재 스레드가 종료된다.</p>
<h3 id="자바와의-차이점은-무엇인가">자바와의 차이점은 무엇인가?</h3>
<p>자바와 달리 코틀린에서는 클래스 인스턴스를 생성(여기서는 예외)할 때 new 같은 특별한 구문을 사용하지 않는다. 자바에서는 도착할 수 없는 코드를 금지하지만, 코틀린은 허용한다. 자바와 달리 코틀린에서는 검사 예외(checked exception)와 비검사 예외(unchecked exception)을 구분하지 않는다.</p>
<h3 id="자바와-코틀린에서-try-문이-어떻게-다른지-설명하라">자바와 코틀린에서 try 문이 어떻게 다른지 설명하라.</h3>
<p>코틀린에서는 try가 식으로 쓰일 수 있어 try 블록의 값이거나 예외를 처리한 catch 블록의 값이 된다.</p>
<hr>
<h2 id="외전">외전</h2>
<h3 id="1-식은-로직이-단순하고-결과를-바로-반환하는-경우에만-사용될까-no">1. 식은 로직이 단순하고 결과를 바로 반환하는 경우에만 사용될까? No</h3>
<h3 id="명시적인-반환타입-vs-암시적인-반환-타입">명시적인 반환타입 vs 암시적인 반환 타입</h3>
<p>명시적인 반환 타입은 먼저 반환 타입을 확정짓고, 그 타입에 맞춰 함수 블록을 개발할 수 있다. </p>
<p>암시적인 반환 타입은 함수 블록이 내가 원하는 타입으로 잘 반환하는지를 컴파일러의 입장을 들어볼 수 있다. 하지만 복잡성이 높아질수록 컴파일 속도가 엄청 느려지는 단점이 있다.</p>
<p>할당 식도 타입 추론이 똑같이 작동한다. 변수마다 타입을 명시해주는 것이 컴파일 속도가 빨라진다.</p>
<pre><code class="language-kotlin">fun a(): Number = if(true) {
    // 가정: 타입 추론을 적용했을 때
    val a: {Comparable&lt;*&gt; &amp; Number} = if (true) 3 else 7.0
    4
} else {
    6
}</code></pre>
<h3 id="2-디폴트-파라미터-vs-함수-오버로딩">2. 디폴트 파라미터 vs 함수 오버로딩</h3>
<p>디폴트 파라미터로 함수 오버로딩을 절대로 흉내낼 수 없는 부분은 인자가 다른 형을 갖는 것을 허용하지 않는다. 어떤 함수가 다양한 형의 인자를 받고 싶다면 함수 오버로딩 외에는 방법이 없다.</p>
<pre><code class="language-kotlin">fun plus(a: Int, b: Long) = a + b // Error

fun c(a: Int, b: Int = plus(a, 4), c: Int = plus(a + b)) = a + b + c</code></pre>
<h3 id="3-이름-붙은-인자-장단점">3. 이름 붙은 인자 장단점</h3>
<p><strong>장점</strong> : 인자의 순서를 바꿀 수 있다.</p>
<blockquote>
<p><strong>&quot;인자의 순서를 바꿀 수 있다&quot;의 조건</strong></p>
<p>(1) 인자가 너무 많을 때 (2) 기본값 인자가 많을 때 (3) vararg 뒤에 인자가 더 있을 때</p>
</blockquote>
<ul>
<li>선택적 인자이기 때문에 이름을 지정한 인자만 쓸 수 있어서 유리하다. 또는 디폴트가 있는 부분들은 순서에 상관없이 이름을 지정할 수 있어서 유리하다.<pre><code class="language-kotlin">fun plus(c: Int, a: Int = 3, b: Int = 4) = a + b
</code></pre>
</li>
</ul>
<p>fun main() {
    print(plus(5, b = 2, a = 1))
}</p>
<pre><code>- 모든 값을 다 넣을 수 없으니까 다 기본값으로 주어지고 선택적 인자로 쓸 수 밖에 없다.
```kotlin
// Compose-style : 인자가 수백가지다.
style( 
    border = 10.px,
    padding = 10.px,
    ...
)</code></pre><h3 id="8-지역-함수---언제-쓸까">8. 지역 함수 - 언제 쓸까?</h3>
<pre><code class="language-kotlin">fun main(args: List&lt;String&gt;) {
    fun localFun(a: Int) {
        a * 2
    }
    println(&quot;${localFun(11)}&quot;)
} </code></pre>
<ul>
<li>자바로 디컴파일된 코드 : 지역 함수를 람다로 바꿔서 처리한다. <pre><code class="language-java">public final class MainKt {
  public static void main(@NotNull String[] args) {
      ...
      println(String.valueOf($fun$localFun$1.invoke(11)))
  }
}</code></pre>
<h4 id="하지만-람다를-쓰나-함수를-쓰나-똑같다-그러면-람다는-언제-쓰고-함수는-언제-쓰나">하지만 람다를 쓰나 함수를 쓰나 똑같다(?) 그러면 람다는 언제 쓰고, 함수는 언제 쓰나?</h4>
함수는 람다와 달리 리턴문을 사용할 수 있기 때문에, 예외 처리를 다룰 때(쉴드 패턴) 함수 내부를 구현하기 쉽다.</li>
</ul>
<h3 id="13-when문">13. when문</h3>
<ul>
<li>반드시 순차적으로 수식을 평가한다. 따라서 절대로 컴파일 최적화가 이루어지지 않는다.<pre><code class="language-kotlin">// 임의의 정수를 그에 대응하는 16진 숫자로 바꾸는 함수
fun hexDigit(n: Int): Char {
  when {
      n in 0..9 -&gt; return &#39;0&#39; + n
      n in 10..15 -&gt; return &#39;A&#39; + n - 10
      else -&gt; return &#39;?&#39;
  }
}</code></pre>
</li>
<li>when 문에 값(n)이 있는 경우에는 병렬 최적화가 일어날 가능성이 존재한다.<pre><code class="language-kotlin">fun readHexDigit() = when(val n = readLine()!!.toInt()) {
  in 0..9 -&gt; &#39;0&#39; + n
  in 10...15 -&gt; &#39;A&#39; + n - 10
  else -&gt; &#39;?&#39;
}</code></pre>
</li>
<li>자바에서 if-else 문 대신 switch를 쓰는 이유 : 어셈블러가 if-else 문은 개별로 평가하는 단계형으로 컴파일하지만, switch문은 병렬 맵으로 한번에 점프하는 명령으로 컴파일된다.</li>
</ul>
<h3 id="14-for-문">14. for 문</h3>
<ul>
<li>코틀린은 2단계 컴파일을 한다 : 1단계에서 바이트코드를 만들고 2단계에서 플랫폼용으로 번역한다.</li>
<li>2단계에서 컴파일 최적화가 많이 이루어지기 때문에 1단계에서 코틀린에서는 객체였지만 2단계에서 그냥 값으로 번역될 수 있다.<pre><code class="language-kotlin">fun main() {
  val a = readLine()!!.toInt()
  val b = readLine()!!.toInt()
  println(5 in a..b)
}</code></pre>
</li>
<li>객체 생성 비용이나 함수 호출 비용이 발생하는가? : No - 런타임에 IntRange 인스턴스를 생성하지 않고, 5를 입력한 값과 비교한다.</li>
</ul>
<h3 id="15-for-vs-while">15. for vs while</h3>
<p>반복문 안에서 for 문은 while 문보다 얼마나 반복할지를 명확히 알 수 있다.</p>
<p>바디가 루프 계획에 영향을 미치는 경우(동적 계획)는 for 문보다 while 문을 사용하는 것이 좋다.</p>
<h4 id="참고한-사이트">참고한 사이트</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=tp-C6TtVjVA&amp;list=WL&amp;index=11&amp;t=25s">https://www.youtube.com/watch?v=tp-C6TtVjVA&amp;list=WL&amp;index=11&amp;t=25s</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin Coding conventions]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-Coding-conventions</link>
            <guid>https://velog.io/@dev-baik/Kotlin-Coding-conventions</guid>
            <pubDate>Tue, 12 Dec 2023 07:30:18 GMT</pubDate>
            <description><![CDATA[<h1 id="1-source-code-organization">1. Source code organization</h1>
<h2 id="11-directory-structure">1.1 Directory structure</h2>
<ul>
<li>순수 Kotlin 프로젝트에서 권장되는 디렉터리 구조는 공통 루트 패키지가 생략된 패키지 구조를 따른다.</li>
<li>예를 들어 프로젝트의 모든 코드가 <code>org.example.kotlin</code> 패키지와 해당 하위 패키지에 있는 경우<ul>
<li><code>org.example.kotlin</code> 패키지에 있는 파일은 소스 루트(Source Root) 바로 아래에 위치해야 한다.</li>
<li><code>org.example.kotlin.network.socket</code>에 있는 파일은 소스 루트의 <code>network/socket</code> 하위 디렉터리에 있어야 한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>JVM</strong>: Kotlin이 Java와 함께 사용되는 프로젝트에서 Kotlin 소스 파일은 Java 소스 파일과 동일한 소스 루트에 있어야 하며, 같은 디렉터리 구조를 따라야 한다. 각 파일은 각 패키지 문(statement)에 해당하는 디렉터리에 저장되어야 한다. 이렇게 하면 Java와 Kotlin 코드가 함께있어도 체계적으로 관리할 수 있다.</p>
</blockquote>
<h2 id="12-source-file-names">1.2 Source file names</h2>
<ul>
<li>Kotlin 파일이 단일 클래스나 인터페이스를 포함하고 있다면(관련 최상위 선언이 포함될 수 있음), 그 파일 이름은 클래스의 이름과 동일하게 하고, <code>.kt</code> 확장자를 붙여야 한다.</li>
<li><span style='background-color: #fff5b1'/>파일이 여러 개의 클래스를 포함하고 있거나, 오직 최상위 선언만을 포함하고 있다면, 파일이 무엇을 포함하고 있는지를 설명하는 이름을 선택하고, 그에 따라 파일의 이름을 지정하면 된다.<h4 id="여러-클래스가-포함된-경우">여러 클래스가 포함된 경우</h4>
<pre><code class="language-kotlin">// 파일 이름: ProcessDeclarations.kt
</code></pre>
</li>
</ul>
<p>// 선언 처리 관련 클래스
class DeclarationProcessor {
    fun processDeclaration(declaration: String): Boolean {
        // 선언 처리 로직
        println(&quot;Processing declaration: $declaration&quot;)
        return true
    }
}</p>
<p>// 선언 유효성 검사 관련 클래스
class DeclarationValidator {
    fun validateDeclaration(declaration: String): Boolean {
        // 선언 유효성 검사 로직
        println(&quot;Validating declaration: $declaration&quot;)
        return true
    }
}</p>
<p>// 선언 관련 유틸리티 함수들
fun formatDeclaration(declaration: String): String {
    // 선언 포맷 로직
    return declaration.trim()
}</p>
<p>// 기타 선언 처리와 관련된 클래스, 함수들...</p>
<pre><code>#### 최상위 수준 선언만 포함된 경우
```kotlin
// 파일 이름: FileProcessingOperations.kt

const val DEFAULT_FILE_PATH = &quot;/path/to/default/file&quot;

fun readFromFile(filePath: String): String {
    // 파일에서 읽기 로직
    return &quot;File content from $filePath&quot;
}

fun writeToFile(filePath: String, content: String) {
    // 파일에 쓰기 로직
    println(&quot;Writing to file at $filePath: $content&quot;)
}

fun validateFilePath(filePath: String): Boolean {
    // 파일 경로 유효성 검사 로직
    return filePath.isNotBlank()
}

// 다른 최상위 수준 선언들...</code></pre><ul>
<li>ProcessDeclarations.kt와 같이 첫 글자가 대문자인 <code>카멜 표기법(파스칼 표기법)</code>을 사용한다.</li>
<li><span style='background-color: #fff5b1'/>파일 이름은 파일의 코드가 수행하는 작업을 설명해야한다. 따라서 파일 이름에 Util과 같은 의미 없는 단어를 사용하는 것은 피해야 한다.</li>
</ul>
<h2 id="13-source-file-organization구성">1.3 Source file organization(구성)</h2>
<ul>
<li><span style='background-color: #fff5b1'/>같은 Kotlin 소스 파일에 여러 선언(클래스, 최상위 함수 또는 프로퍼티)을 배치하는 것은 이러한 선언들이 의미적으로 서로 밀접하게 관련되어 있고, 파일 크기가 합리적인 범위(몇 백 줄을 넘지 않는) 내에 있을 때 권장된다.<ul>
<li>특히, 모든 클라이언트에게 관련된 확장 함수를 클래스에 정의하는 경우, 그 함수들을 클래스 자체와 같은 파일에 배치하라.</li>
<li>특정 클라이언트에게만 의미가 있는 확장 함수를 정의하는 경우, 그 함수들을 해당 클라이언트의 코드 옆에 배치하라.</li>
</ul>
</li>
<li>어떤 클래스의 모든 확장을 담기 위해 파일을 생성하지 마라.</li>
</ul>
<h2 id="14-class-layout">1.4 Class layout</h2>
<ul>
<li>메소드 선언을 알파벳 순서로 정렬하거나 가시성에 따라 정렬하거나, 일반 메소드와 확장 메소드를 분리하지 마라.</li>
<li><span style='background-color: #fff5b1'/>대신, 관련 있는 것들을 함께 두어서, 클래스를 위에서 아래로 읽는 사람이 어떤 로직이 진행되고 있는지를 따라갈 수 있게 하라.<ul>
<li>순서를 선택하고(상위 수준의 것을 먼저 두거나 그 반대로 하거나) 그 순서를 지켜라.</li>
</ul>
</li>
</ul>
<ul>
<li>중첩 클래스는 그 클래스를 사용하는 코드 옆에 배치한다.</li>
<li><span style='background-color: #fff5b1'/>만약 그 클래스들이 외부에서 사용되고 클래스 내부에서 참조되지 않는다면, 해당 클래스의 맨 뒤에 companion object 이후에 놓아라.</li>
</ul>
<h2 id="15-interface-implementation-layout">1.5 Interface implementation layout</h2>
<ul>
<li>인터페이스를 구현할 때는, 인터페이스의 멤버들과 동일한 순서로 멤버들을 구현하라(필요하다면, 구현에 사용되는 추가적인 private 메소드와 교차하여 배치하라).</li>
</ul>
<h2 id="16-overload-layout">1.6 Overload layout</h2>
<ul>
<li>클래스 내에서 항상 오버로드된 메서드를 서로 옆에 배치하라.</li>
</ul>
<h1 id="2-naming-rules">2. Naming rules</h1>
<ul>
<li>패키지의 이름은 항상 소문자이며 밑줄을 사용하지 않는다. (org.example.project)</li>
<li>다중 단어 이름 사용은 일반적으로 권장되지 않지만, 만약 여러 단어를 사용해야 한다면, 단어들을 그냥 연결하거나 카멜 케이스를 사용할 수 있다(org.example.myProject).</li>
</ul>
<ul>
<li>클래스와 객체의 이름은 대문자로 시작하고 카멜 케이스를 사용한다.<pre><code class="language-kotlin">open class DeclarationProcessor { /*...*/ }
</code></pre>
</li>
</ul>
<p>object EmptyDeclarationProcessor : DeclarationProcessor() { /<em>...</em>/ }</p>
<pre><code>
## 2.1 Function names
- 함수, 프로퍼티, 그리고 지역 변수의 이름은 소문자로 시작하고 카멜 케이스를 사용하며 밑줄을 사용하지 않는다.
```kotlin
fun processDeclarations() { /*...*/ }
var declarationCount = 1</code></pre><ul>
<li>예외: 클래스의 인스턴스를 생성하는 데 사용되는 팩토리 함수는 추상 반환 타입과 동일한 이름을 가질 수 있다.<pre><code class="language-kotlin">interface Foo { /*...*/ }
</code></pre>
</li>
</ul>
<p>class FooImpl : Foo { /<em>...</em>/ }</p>
<p>fun Foo(): Foo { return FooImpl() }</p>
<pre><code>
## 2.2 Names for test methods
- 테스트에서 (그리고 오직 테스트에서만), 백틱으로 둘러싸인 공백이 있는 메소드 이름을 사용할 수 있다. 그러나 이런 메소드 이름은 현재 안드로이드 런타임에서 지원되지 않는다는 점을 주의하라.
- 메소드 이름에 밑줄을 사용하는 것도 테스트 코드에서는 허용된다.
```kotlin
class MyTestCase {
     @Test fun `ensure everything works`() { /*...*/ }

     @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}</code></pre><h2 id="23-property-names">2.3 Property names</h2>
<ul>
<li>상수의 이름(상수로 표시된 프로퍼티, 또는 사용자 정의 get 함수가 없는 깊이 불변 데이터를 가진 최상위 또는 객체 val 프로퍼티)은 대문자 밑줄 구분(스크리밍 스네이크 케이스) 이름을 사용해야 한다.<pre><code class="language-kotlin">const val MAX_COUNT = 8
val USER_NAME_FIELD = &quot;UserName&quot;</code></pre>
</li>
<li>행동이 있는 객체 또는 가변 데이터를 가진 최상위 또는 객체 프로퍼티의 이름은 카멜 케이스 이름을 사용해야 한다.<pre><code class="language-kotlin">val mutableCollection: MutableSet&lt;String&gt; = HashSet()</code></pre>
</li>
<li>싱글톤 객체에 대한 참조를 가진 프로퍼티의 이름은 객체 선언과 동일한 명명 스타일을 사용할 수 있다.<pre><code class="language-kotlin">val PersonComparator: Comparator&lt;Person&gt; = /*...*/</code></pre>
</li>
<li>열거형 상수의 경우, 사용법에 따라 대문자 밑줄 구분 이름(스크리밍 스네이크 케이스) (예: <code>enum class Color { RED, GREEN }</code>) 또는 대문자 카멜 케이스 이름을 사용하는 것이 좋다</li>
</ul>
<h2 id="24-names-for-backing-properties">2.4 Names for backing properties</h2>
<ul>
<li><p><span style='background-color: #fff5b1'/>클래스에 개념적으로 동일하지만 하나는 공개 API의 일부이고 다른 하나는 구현 세부 사항인 두 개의 프로퍼티가 있다면, 비공개 프로퍼티의 이름에는 밑줄을 접두사로 사용하라. </p>
<pre><code class="language-kotlin">class C {
  private val _elementList = mutableListOf&lt;Element&gt;()

  val elementList: List&lt;Element&gt;
       get() = _elementList
}</code></pre>
</li>
</ul>
<h2 id="25-choose-good-names">2.5 Choose good names</h2>
<ul>
<li>클래스의 이름은 보통 명사 또는 클래스가 무엇인지를 설명하는 명사구이다 : List, PersonReader</li>
</ul>
<ul>
<li>메소드의 이름은 보통 메소드가 무엇을 하는지를 말하는 동사 또는 동사구이다 : close, readPersons</li>
<li>이름은 메소드가 객체를 변형하는지 아니면 새로운 것을 반환하는지도 나타내야 한다. 예를 들어, sort는 컬렉션을 제자리에서 정렬하는 반면, sorted는 컬렉션의 정렬된 복사본을 반환한다.</li>
</ul>
<ul>
<li><span style='background-color: #fff5b1'/>이름은 엔티티의 목적이 무엇인지 명확하게 해야 하므로, 이름에서 의미없는 단어(Manager, Wrapper)를 사용하는 것을 피하는 것이 좋다.</li>
</ul>
<ul>
<li><span style='background-color: #fff5b1'/>선언 이름의 일부로 약어를 사용할 때는 두 글자로 이루어진 경우 대문자로 표시하고(IOStream), 길이가 더 긴 경우에는 첫 글자만 대문자로 표시한다(XmlFormatter, HttpInputStream).</li>
</ul>
<h1 id="3-formatting">3. Formatting</h1>
<h2 id="31-indentation-들여쓰기">3.1 Indentation (들여쓰기)</h2>
<ul>
<li>들여쓰기에는 네 개의 공백을 사용하라. 탭은 사용하지 마라.</li>
<li>중괄호에 대해서는, 구조가 시작되는 줄의 끝에 여는 중괄호를 놓고, 닫는 중괄호를 여는 구조와 수평으로 정렬된 별도의 줄에 놓아라.<pre><code class="language-kotlin">if (elements != null) {
  for (element in elements) {
      // ...
  }
} </code></pre>
</li>
<li>코틀린에서 세미콜론은 선택적이며, 따라서 줄바꿈이 중요하다. 언어 디자인은 자바 스타일의 중괄호를 가정하고 있으며, 다른 형식의 스타일을 사용하려고 하면 놀랄 만한 동작을 경험할 수 있다.</li>
</ul>
<h2 id="32-horizontal-whitespace-가로-공백">3.2 Horizontal whitespace (가로 공백)</h2>
<ul>
<li>이진 연산자 주위에는 공백을 넣는다(a + b).<ul>
<li>예외: `range to 연산자 주위에는 공백을 넣지 않는다(0..i).</li>
</ul>
</li>
</ul>
<ul>
<li>제어 흐름 키워드(if, when, for, while)와 해당하는 여는 괄호 사이에 공백을 넣는다.</li>
</ul>
<ul>
<li>기본 생성자 선언, 메소드 선언 또는 메소드 호출에서 여는 괄호 앞에 공백을 넣지 않는다.<pre><code class="language-kotlin">class A(val x: Int)
</code></pre>
</li>
</ul>
<p>fun foo(x: Int) { ... }</p>
<p>fun bar() {
    foo(1)
} </p>
<pre><code>
- `(`, `[` 뒤에 또는 `]`, `)` 앞에는 절대로 공백을 넣지 마라.


- `.`이나 `?:` 주위에는 절대로 공백을 넣지 마라 : `foo.bar().filter { it &gt; 2 }.joinToString()`, `foo?.bar()`


- `//` 뒤에는 공백을 넣어라 : `// this is a comment`


- 타입 파라미터를 지정하기 위해 사용되는 꺽쇠 괄호 &lt;&gt; 주위에는 공백을 넣지 마라 : `class Map&lt;K, V&gt; { ... }`


- `::` 주위에는 절대로 공백을 넣지 마라 : `Foo::class`, `String::length`


- 널 가능 타입을 표시하는 데 사용되는 `?` 앞에는 공백을 넣지 마라 : `String?`


- 일반적인 규칙으로, 모든 종류의 수평 정렬을 피해라. 식별자의 이름을 다른 길이의 이름으로 변경하더라도, 선언이나 사용의 형식에는 영향을 주지 않아야 한다.

## 3.3 Colon(:)
- 다음과 같은 경우에는 `:` 앞에 공백을 넣어라:
  - 타입과 상위 타입을 구분할 때
  - 슈퍼클래스 생성자 또는 같은 클래스의 다른 생성자에 위임할 때
  - object 키워드 다음에


- 선언과 그 타입을 구분할 때는 `:` 앞에 공백을 넣지 않는다.


- `:` 뒤에는 항상 공백을 넣는다.
```kotlin
// 타입과 상위 타입을 구분할 때
abstract class Foo&lt;out T : Any&gt; : IFoo {
    abstract fun foo(a: Int): T
}

// 슈퍼클래스 생성자 또는 같은 클래스의 다른 생성자에 위임할 때
class FooImpl : Foo() {
    constructor(x: String) : this(x) { /*...*/ }

    val x = object : IFoo { /*...*/ }
}

// 선언과 그 타입을 구분할 때
val foo: Int = 1

// &#39;:&#39; 뒤에는 항상 공백을 넣는다.
val foo: Int = 1
class Foo : Bar {
    ...
}</code></pre><h2 id="34-class-headers">3.4 Class headers</h2>
<ul>
<li><p>기본 생성자 매개변수가 몇 개인 클래스는 한 줄로 작성할 수 있다.</p>
<pre><code class="language-kotlin">class Person(id: Int, name: String)</code></pre>
</li>
<li><p>헤더가 긴 클래스는 각 기본 생성자 매개변수가 들여쓰기가 적용된 별도의 줄에 위치하도록 포맷되어야 한다. 또한 닫는 괄호는 새로운 줄에 위치해야 한다.</p>
</li>
<li><p>만약 상속을 사용한다면, 슈퍼클래스 생성자 호출 또는 구현된 인터페이스 목록은 괄호와 같은 줄에 위치해야 한다.</p>
<pre><code class="language-kotlin">class Person(
  id: Int,
  name: String,
  surname: String
) : Human(id, name) { /*...*/ }</code></pre>
</li>
<li><p>여러 인터페이스의 경우, 슈퍼클래스 생성자 호출이 먼저 위치해야 하며, 각 인터페이스는 다른 줄에 위치해야 한다.</p>
<pre><code class="language-kotlin">class Person(
  id: Int,
  name: String,
  surname: String
) : Human(id, name),
  KotlinMaker { /*...*/ }</code></pre>
</li>
<li><p>슈퍼타입 목록이 긴 클래스의 경우, 콜론 다음에 줄바꿈을 하고 모든 슈퍼타입 이름을 수평으로 정렬한다.</p>
<pre><code class="language-kotlin">class MyFavouriteVeryLongClassHolder :
  MyLongHolder&lt;MyFavouriteVeryLongClass&gt;(),
  SomeOtherInterface,
  AndAnotherOne {

  fun foo() { /*...*/ }
}</code></pre>
</li>
<li><p>클래스 헤더가 긴 경우 클래스 헤더와 본문을 명확하게 구분하기 위해, 클래스 헤더 다음에 빈 줄을 넣거나, 여는 중괄호를 별도의 줄에 놓는다.</p>
<pre><code class="language-kotlin">// 클래스 헤더 다음에 빈 줄을 넣기
class MyFavouriteVeryLongClassHolder :
  MyLongHolder&lt;MyFavouriteVeryLongClass&gt;(),
  SomeOtherInterface,
  AndAnotherOne {

  fun foo() { /*...*/ }
}
</code></pre>
</li>
</ul>
<p>// 여는 중괄호를 별도의 줄에 놓기
class MyFavouriteVeryLongClassHolder :
    MyLongHolder<MyFavouriteVeryLongClass>(),
    SomeOtherInterface,
    AndAnotherOne
{
    fun foo() { /<em>...</em>/ }
}</p>
<pre><code>- 생성자 매개변수에는 일반 들여쓰기(네 개의 공백)를 사용한다. 이렇게 하면 기본 생성자에서 선언된 프로퍼티가 클래스 본문에서 선언된 프로퍼티와 동일한 들여쓰기를 갖게 된다.

## 3.5 Modifiers order(순서)
- 선언에 여러 개의 수식어가 있는 경우, 항상 다음 순서로 배치하라.
```kotlin
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation / fun // as a modifier in `fun interface`
companion
inline / value
infix
operator
data </code></pre><ul>
<li>모든 어노테이션은 수정자 앞에 위치시켜라.<pre><code class="language-kotlin">@Named(&quot;Foo&quot;)
private val foo: Foo </code></pre>
</li>
<li>라이브러리를 작업하는 경우가 아니라면, 불필요한 수식어(예: public)는 생략하라.</li>
</ul>
<h2 id="36-annotations">3.6 Annotations</h2>
<ul>
<li>어노테이션은 그것이 첨부된 선언 전에 별도의 줄에 위치시키고, 동일한 들여쓰기를 적용하라.<pre><code class="language-kotlin">@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude </code></pre>
</li>
<li>인수가 없는 어노테이션은 같은 줄에 위치시킬 수 있다.<pre><code class="language-kotlin">@JsonExclude @JvmField
var x: String </code></pre>
</li>
<li>인수가 없는 단일 어노테이션은 해당 선언과 같은 줄에 배치될 수 있다.<pre><code class="language-kotlin">@Test fun foo() { /*...*/ }</code></pre>
</li>
</ul>
<h2 id="37-file-annotations">3.7 File annotations</h2>
<ul>
<li>파일 어노테이션은 파일 어노테이션(있는 경우) 다음, 패키지 선언 전에 위치하며, 패키지와는 빈 줄로 구분된다((이것은 그들이 패키지가 아닌 파일을 대상으로 한다는 사실을 강조하기 위함이다).<pre><code class="language-kotlin">/** License, copyright and whatever */
@file:JvmName(&quot;FooBar&quot;)
</code></pre>
</li>
</ul>
<p>package foo.bar</p>
<pre><code>
## 3.8 Functions
- 만약 함수 시그러니가 한 줄에 맞지 않는 경우, 다음과 같은 문법을 사용하라.
```kotlin
fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType,
): ReturnType {
    // body
}</code></pre><ul>
<li><p>함수 매배견수에는 일반 들여쓰기(4 개의 공백)를 사용하라. 이렇게 함으로써 생성자 매개변수와 일관성을 유지할 수 있다.</p>
</li>
<li><p><span style='background-color: #fff5b1'/>함수의 본문이 단일 표현식으로 구성된 경우 표현식 본문을 사용하는 것을 선호하라.</p>
<pre><code class="language-kotlin">fun foo(): Int {     // bad
  return 1
}
</code></pre>
</li>
</ul>
<p>fun foo() = 1        // good</p>
<pre><code>
## 3.9 Expression bodies
- 만약 함수가 표현식 본문을 가지고 있고 그 첫 번째 줄이 선언과 같은 줄에 맞지 않는 경우, 첫 번째 줄에 `=` 기호를 두고 표현식 본문을 네 칸 들여써라.
```kotlin
fun f(x: String, y: String, z: String) =
    veryLongFunctionCallWithManyWords(andLongParametersToo(), x, y, z)</code></pre><h2 id="310-properties">3.10 Properties</h2>
<ul>
<li>매우 간단한 읽기 전용 프로퍼티의 경우, 한 줄 형식을 고려해보라.<pre><code class="language-kotlin">val isEmpty: Boolean get() = size == 0</code></pre>
</li>
<li>더 복잡한 프로퍼티의 경우, 항상 get과 set 키워드를 별도의 줄에 놓는 것을 고려해보라.<pre><code class="language-kotlin">val foo: String
  get() { /*...*/ } </code></pre>
</li>
<li>초기화가 있는 프로퍼티의 경우, 초기화자가 길 경우 <code>=</code> 기호 뒤에 줄 바꿈을 추가하고 초기화를 네 개의 공백으로 들여써라.<pre><code class="language-kotlin">private val defaultCharset: Charset? =
  EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)</code></pre>
</li>
</ul>
<h2 id="311-control-flow-statements-제어-흐름문">3.11 Control flow statements (제어 흐름문)</h2>
<ul>
<li>if 또는 when 문의 조건이 여러 줄인 경우, 항상 문장의 본문 주위에 중괄호를 사용하라.</li>
<li>조건문의 각 후속 줄은 문장 시작에 대해 네 개의 공백으로 들여써라.</li>
<li>조건의 닫는 괄호를 별도의 줄에 있는 여는 중괄호와 함께 놓아라.<pre><code class="language-kotlin">if (!component.isSyncing &amp;&amp;
  !hasAnyKotlinRuntimeInScope(module)
) {
  return createKotlinNotConfiguredPanel(module)
}</code></pre>
</li>
<li>else, catch, finally 키워드와 do-while 루프의 while 키워드를 선행하는 중괄호와 같은 줄에 놓아라.<ul>
<li>이 방법은 조건과 문장 본문을 정렬하는 데 도움이 된다.<pre><code class="language-kotlin">if (condition) {
// body
} else {
// else part
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>try {
    // body
} finally {
    // cleanup
}</p>
<pre><code>- when 문에서, 한 브랜치가 한 줄 이상인 경우, 인접한 case 블록과 빈 줄로 분리하는 것을 고려하라.
```kotlin
private fun parsePropertyValue(propName: String, token: Token) {
    when (token) {
        is Token.ValueToken -&gt;
            callback.visitValue(propName, token.value)

        Token.LBRACE -&gt; { // ...
        }
    }
}</code></pre><ul>
<li>짧은 브랜치는 조건과 동일한 줄에 중괄호 없이 배치하라.<pre><code class="language-kotlin">when (foo) {
  true -&gt; bar() // good
  false -&gt; { baz() } // bad
}</code></pre>
</li>
</ul>
<h2 id="312-method-calls">3.12 Method calls</h2>
<ul>
<li>긴 인수 목록에서는 여는 괄호 다음에 줄 바꿈을 넣어라. 인수를 네 칸 들여써라.</li>
<li><span style='background-color: #fff5b1'/>밀접하게 관련된 여러 인수를 같은 줄에 그룹화한다.<pre><code class="language-kotlin">drawSquare(
  x = 10, y = 10,
  width = 100, height = 100,
  fill = true
)</code></pre>
</li>
<li>인수 이름과 값 사이를 구분하는 = 기호 주위에 공백을 넣어라.</li>
</ul>
<h2 id="313-wrap-chained-calls">3.13 Wrap chained calls</h2>
<ul>
<li>연결된 호출을 줄 바꿈할 때는 <code>.</code> 문자나 <code>?.</code> 연산자를 다음 줄에 넣고, 한 칸 들여써라.<pre><code class="language-kotlin">val anchor = owner
  ?.firstChild!!
  .siblings(forward = true)
  .dropWhile { it is PsiComment || it is PsiWhiteSpace } </code></pre>
</li>
<li>체인의 첫 번째 호출은 일반적으로 그 앞에 줄 바꿈이 있어야 하지만, 코드가 더 이해하기 쉬울 경우 생략해도 괜찮다.</li>
</ul>
<h2 id="314-lambdas">3.14 Lambdas</h2>
<ul>
<li>람다 표현식에서는 중괄호 주변에 공백을 사용해야하며, 또한 매개변수와 본문을 구분하는 화살표 주변에도 공백을 사용해야한다.</li>
<li>단일 람다를 전달하는 경우 가능한 경우 괄호 외부로 전달하라.<pre><code class="language-kotlin">list.filter { it &gt; 10 }</code></pre>
</li>
<li>람다에 레이블을 할당하는 경우 레이블과 여는 중괄호 사이에 공백을 넣지마라.<pre><code class="language-kotlin">fun foo() {
  ints.forEach lit@{
      // ...
  }
}</code></pre>
</li>
<li>다중 라인 람다에서 매개변수 이름을 선언할 때는 첫 번째 줄에 이름을 두고, 그 다음에 화살표와 줄 바꿈을 넣어라.<pre><code class="language-kotlin">appendCommaSeparated(properties) { prop -&gt;
  val propertyValue = prop.get(obj)  // ...
}</code></pre>
</li>
<li>매개변수 목록이 한 줄에 들어가지 않을 정도로 길다면, 화살표를 별도의 줄에 둬라.<pre><code class="language-kotlin">foo {
      context: Context,
      environment: Env
  -&gt;
  context.configureEnv(environment)
} </code></pre>
</li>
</ul>
<h2 id="315-trailing-commas">3.15 Trailing commas</h2>
<ul>
<li>마지막 콤마는 요소 시리즈의 마지막 항목 뒤에 오는 콤마 기호를 의미한다.<pre><code class="language-kotlin">class Person(
  val firstName: String,
  val lastName: String,
  val age: Int, // trailing comma
)</code></pre>
<h4 id="마지막-콤아-사용-이점">마지막 콤아 사용 이점</h4>
</li>
<li>버전 컨트롤 diff를 깔끔하게 만든다 - 모든 초점이 변경된 값에 집중된다.</li>
<li>요소를 추가하고 재정렬하는 것이 쉬워진다. - 요소를 조작하더라도 콤마를 추가하거나 삭제할 필요가 없다.</li>
<li>코드 생성을 단순화한다.  예를 들어, 객체 초기화자의 경우 마지막 요소에도 콤마를 사용할 수 있다.</li>
</ul>
<ul>
<li>마지막 콤마는 완전히 선택 사항이다. <code>-</code> 이것 없이도 코드는 작동한다. 코틀린 스타일 가이드는 선언 위치에서 마지막 콤마 사용을 권장하며, 호출 위치의 경우 사용자의 재량에 따른다.</li>
</ul>
<h1 id="4-documentation-comments">4. Documentation comments</h1>
<ul>
<li>긴 문서 주석의 경우, 개행을 사용하여 /<em>*를 별도의 줄에 위치시키고 이후 각 줄의 시작에 별표(</em>)를 붙인다.<pre><code class="language-kotlin">/**
* This is a documentation comment
* on multiple lines.
*/</code></pre>
</li>
<li>짧은 주석은 한 줄에 배치할 수 있다.<pre><code class="language-kotlin">/** This is a short documentation comment. */</code></pre>
</li>
<li>일반적으로 <code>@param</code> 및 <code>@return</code> 태그의 사용을 피하라. 대신, 매개변수와 반환 값의 설명을 문서 주석에 직접 포함시키고, 매개변수가 언급될 때마다 링크를 추가하라. 주 텍스트의 흐름에 맞지 않는 긴 설명이 필요할 때만 @param 및 @return을 사용하라.<pre><code class="language-kotlin">// Avoid doing this:
</code></pre>
</li>
</ul>
<p>/**</p>
<ul>
<li>주어진 숫자의 절댓값을 반환한다.</li>
<li>@param 숫자 절댓값을 반환할 숫자.</li>
<li>@return 절댓값.</li>
<li>/
fun abs(number: Int): Int { /<em>...</em>/ }</li>
</ul>
<p>// Do this instead:</p>
<p>/**</p>
<ul>
<li>주어진 [숫자]의 절댓값을 반환한다.</li>
<li>/
fun abs(number: Int): Int { /<em>...</em>/ } <pre><code></code></pre></li>
</ul>
<h1 id="5-avoid-redundant-constructs-중복-구성-방지">5. Avoid redundant constructs (중복 구성 방지)</h1>
<ul>
<li>일반적으로 코틀린에서 특정 구문 구조가 선택 사항이며 IDE에서 불필요하다고 강조하는 경우, 코드에서 생략해야 한다. &quot;명확성을 위해&quot; 불필요한 구문 요소를 코드에 남겨두지 마라.  </li>
</ul>
<h2 id="51-unit-return-type">5.1 Unit return type</h2>
<ul>
<li>함수가 Unit을 반환하는 경우, 반환 타입은 생략해야 한다.<pre><code class="language-kotlin">fun foo() { // &quot;: Unit&quot; is omitted here
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
## 5.2 Semicolons
- 가능한 한 세미콜론을 생략하라.

## 5.3 String templates
- 문자열 템플릿에 간단한 변수를 삽입할 때 중괄호를 사용하지 마라. 긴 표현식에만 중괄호를 사용하라. 
```kotlin
println(&quot;$name has ${children.size} children&quot;)</code></pre><h1 id="6-idiomatic-use-of-language-features-언어-기능의-관용적-사용">6. Idiomatic use of language features (언어 기능의 관용적 사용)</h1>
<h2 id="span-stylebackground-color-fff5b161-immutability"><span style='background-color: #fff5b1'/>6.1 Immutability</h2>
<ul>
<li>불변 데이터를 사용하는 것이 좋다. 초기화 후에 수정되지 않는 경우, 로컬 변수와 프로퍼티는 항상 var 대신 val로 선언해야 한다.</li>
<li>변경되지 않는 컬렉션을 선언할 때는 항상 불변 컬렉션 인터페이스(Collection, List, Set, Map)를 사용하라.</li>
<li>컬렉션 인스턴스를 생성하기 위해 팩토리 함수를 사용할 때는 가능한 한 불변 컬렉션 타입을 반환하는 함수를 사용하라.<pre><code class="language-kotlin">// Bad: 변경되지 않는 값에 변경 가능한 컬렉션 유형을 사용한다.
fun validateValue(actualValue: String, allowedValues: HashSet&lt;String&gt;) { ... }
</code></pre>
</li>
</ul>
<p>// Good: 대신에 불변 컬렉션 유형를 사용한다.
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }</p>
<p>// Bad: arrayListOf()는 변경 가능한 컬렉션 유형인 ArrayList<T>를 반환환다.
val allowedValues = arrayListOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)</p>
<p>// Good: listOf()는 List<T>를 반환한다.
val allowedValues = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;) </p>
<pre><code>
## 6.2 Default parameter values
- 오버로드된 함수를 선언하는 것보다 기본 매개변수 값을 가진 함수를 선언하는 것이 좋다.
```kotlin
// Bad
fun foo() = foo(&quot;a&quot;)
fun foo(a: String) { /*...*/ }

// Good
fun foo(a: String = &quot;a&quot;) { /*...*/ } </code></pre><h2 id="63-type-aliases-타입-별칭">6.3 Type aliases (타입 별칭)</h2>
<ul>
<li>코드베이스에서 여러 번 사용되는 함수형 타입이나 타입 매개변수가 있는 타입이 있다면, 이에 대한 타입 별칭을 정의하는 것이 좋다.<pre><code class="language-kotlin">typealias MouseClickHandler = (Any, MouseEvent) -&gt; Unit
typealias PersonIndex = Map&lt;String, Person&gt;</code></pre>
</li>
<li>이름 충돌을 피하기 위해 private 또는 internal 타입 별칭을 사용한다면, &#39;패키지와 임포트&#39;에서 언급한 <code>import ... as ...</code>를 선호하는 것이 좋다.</li>
</ul>
<h2 id="64-lambda-parameters">6.4 Lambda parameters</h2>
<ul>
<li>짧고 중첩되지 않은 람다에서는 매개변수를 명시적으로 선언하는 대신 it 컨벤션을 사용하는 것이 권장된다.</li>
<li><span style='background-color: #fff5b1'/>매개변수가 있는 중첩 람다에서는 항상 매개변수를 명시적으로 선언해야 한다.<pre><code class="language-kotlin">// Bad
val doubled = listOf(1, 2, 3).map { number -&gt; number * 2 }
</code></pre>
</li>
</ul>
<p>// Good
val doubled = listOf(1, 2, 3).map { it * 2 }</p>
<p>listOf(1, 2, 3).map { number -&gt;
    listOf(&#39;a&#39;, &#39;b&#39;, &#39;c&#39;).map { char -&gt;
        // 이곳에서 &#39;it&#39;을 사용하면 어떤 &#39;it&#39;인지 명확하지 않다.
        // 따라서 매개변수를 명시적으로 선언하는 것이 좋다.
    }
}</p>
<pre><code>
## 6.5 Returns in a lambda
- 람다에서 여러 레이블이 붙은 반환을 사용하는 것은 피해라. 람다가 단일 종료 지점을 가질 수 있도록 구조를 재구성하라. 만약 그것이 불가능하거나 충분히 명확하지 않다면, 람다를 익명 함수로 변환하는 것을 고려하라.
- 람다의 마지막 문장에 레이블이 붙은 반환을 사용하지 마라.
```kotlin
// Bad
val result = run loop@{
    listOf(1, 2, 3).forEach {
        if (it == 2) return@loop it
    }
    return@loop null
}

// Good
val result = listOf(1, 2, 3).firstOrNull { it == 2 }</code></pre><h2 id="66-named-arguments">6.6 Named arguments</h2>
<ul>
<li>메서드가 동일한 기본 타입의 여러 매개변수를 취하거나 Boolean 타입의 매개변수에 대해서는, 모든 매개변수의 의미가 문맥에서 절대적으로 명확하지 않는 한, 명명된 인수 구문을 사용하는 것이 좋다.<pre><code class="language-kotlin">drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)</code></pre>
</li>
</ul>
<h2 id="67-conditional-statements-조건문">6.7 Conditional statements (조건문)</h2>
<ul>
<li>try, if, 그리고 when의 표현식 형태를 사용하는 것이 좋다.<pre><code class="language-kotlin">return if (x) foo() else bar()</code></pre>
<pre><code class="language-koltin">return when(x) {
  0 -&gt; &quot;zero&quot;
  else -&gt; &quot;nonzero&quot;
}</code></pre>
</li>
<li>위의 내용이 다음보다 바람직하다.<pre><code class="language-kotlin">if (x)
  return foo()
else
  return bar()</code></pre>
<pre><code class="language-kotlin">when(x) {
  0 -&gt; return &quot;zero&quot;
  else -&gt; return &quot;nonzero&quot;
}</code></pre>
</li>
</ul>
<h2 id="68-if-versus-when">6.8 if versus when</h2>
<ul>
<li>이진 조건에 대해서는 when 대신 if를 사용하는 것이 좋다.<pre><code class="language-kotlin">if (x == null) ... else ...</code></pre>
</li>
<li><code>When</code>을 사용하는 대신<pre><code class="language-kotlin">when (x) {
  null -&gt; // ...
  else -&gt; // ...
}</code></pre>
</li>
<li><span style='background-color: #fff5b1'/>옵션이 3개 이상일 때 사용하는 것이 좋다.</li>
</ul>
<h2 id="69-nullable-boolean-values-in-conditions">6.9 Nullable Boolean values in conditions</h2>
<ul>
<li>조건문에서 nullable Boolean을 사용해야 하는 경우, <code>if (value == true)</code> 또는 <code>if (value == false)</code> 검사를 사용하는 것이 좋다.</li>
</ul>
<h2 id="span-stylebackground-color-fff5b1610-loops"><span style='background-color: #fff5b1'/>6.10 Loops</h2>
<ul>
<li>루프 대신 고차 함수(filter, map 등)를 사용하는 것이 좋다.<ul>
<li>예외: forEach (forEach의 수신자가 nullable이거나 forEach가 더 긴 호출 체인의 일부로 사용되는 경우를 제외하고는, 일반 for 루프를 사용하는 것이 좋다.)<pre><code class="language-kotlin">data class Item(val name: String)
</code></pre>
</li>
</ul>
</li>
</ul>
<p>fun processItems(items: List<Item>?) {
    // Nullable 체크 후 forEach 사용
    if (items != null) {
        items.forEach {
            println(it.name)
        }
    }</p>
<pre><code>// 안전한 호출 연산자와 forEach 사용
items?.forEach {
    println(it.name)
}

// forEach가 더 긴 호출 체인의 일부로 사용될 때
val result = someFunctionReturningNullableList()?.forEach {
    // 이 부분에서 각 아이템을 처리
    println(it.name)
}</code></pre><p>}</p>
<p>fun someFunctionReturningNullableList(): List<Item>? {
    return emptyList()
}</p>
<p>fun main() {
    val items: List<Item>? = listOf(Item(&quot;A&quot;), Item(&quot;B&quot;), Item(&quot;C&quot;))
    processItems(items)
} </p>
<pre><code>
## 6.11 Loops on ranges
- 범위를 열린 범위로 반복할 때 `..&lt;` 연산자를 사용하라.
```kotlin
for (i in 0..n - 1) { /*...*/ }  // bad
for (i in 0..&lt;n) { /*...*/ }  // good </code></pre><h2 id="612-strings">6.12 Strings</h2>
<ul>
<li>문자열 연결보다는 문자열 템플릿을 선호한다.</li>
<li>일반 문자열 리터럴에 이스케이프 시퀀스를 포함시키는 것보다는 여러 줄 문자열을 선호한다.</li>
<li>여러 줄 문자열에서 들여쓰기를 유지하려면, 결과 문자열이 내부 들여쓰기를 필요로하지 않을 때는 <code>trimIndent</code>를 사용하거나, 내부 들여쓰기가 필요한 경우에는 <code>trimMargin</code>를 사용하라.<pre><code class="language-kotlin">println(&quot;&quot;&quot;
  Not
  trimmed
  text
  &quot;&quot;&quot;
     )
</code></pre>
</li>
</ul>
<p>// 
// Not
// trimmed
// text
// </p>
<p>println(&quot;&quot;&quot;
    Trimmed
    text
    &quot;&quot;&quot;.trimIndent()
       )</p>
<p>// Trimmed
// text</p>
<p>println()</p>
<p>val a = &quot;&quot;&quot;Trimmed to margin text:
          |if(a &gt; 1) {
          |    return a
          |}&quot;&quot;&quot;.trimMargin()</p>
<p>println(a)</p>
<p>// Trimmed to margin text:
// if(a &gt; 1) {
//     return a
// }</p>
<pre><code>
## 6.13 Functions vs properties
- 일부 경우에는 인수가 없는 함수를 읽기 전용 프로퍼티와 교환하여 사용할 수 있다. 의미론적으로는 비슷하지만, 어떤 것을 선호해야 할지에 대한 스타일 가이드라인이 있다.

#### 다음 조건을 만족하는 경우, 함수보다 프로퍼티를 선호한다. 
- 예외를 발생시키지 않는다.
- 계산 비용이 적거나(또는 첫 번째 실행에서 캐시된다).
- 객체 상태가 변경되지 않았다면 호출에 대해 항상 같은 결과를 반환한다.
```kotlin
// Before
fun calculateArea(): Double {
    return width * height
}

// After
val area: Double
    get() = width * height</code></pre><h2 id="614-extension-functions-확장-함수">6.14 Extension functions (확장 함수)</h2>
<ul>
<li>확장 함수를 자유롭게 사용하라. 객체를 주로 대상으로 작동하는 함수가 있을 때마다 해당 객체를 수신자로 받는 확장 함수로 만드는 것을 고려하라. </li>
<li>API 오염을 최소화하기 위해, 가능한 한 확장 함수의 가시성을 제한하라. 필요에 따라 로컬 확장 함수, 멤버 확장 함수, 또는 private 가시성을 가진 최상위 확장 함수를 사용하라.<pre><code class="language-kotlin">fun String.isNumeric(): Boolean {
  return this.all { it.isDigit() }
}
</code></pre>
</li>
</ul>
<p>val str = &quot;12345&quot;
println(str.isNumeric())  // true</p>
<pre><code>
## 6.15 Infix functions
- 함수를 중위(infix)로 선언하는 것은 두 객체가 유사한 역할을 수행할 때만 한다. 좋은 예로는 and, to, zip 등이 있다. 나쁜 예로는 add가 있다.
- 수신자 객체를 변경하는 경우에는 메서드를 중위로 선언하지마라.
#### Bad: 중위 함수로 선언하여 사용
```kotlin
class MutablePoint(var x: Int, var y: Int) {
    infix fun translate(pair: Pair&lt;Int, Int&gt;) {
        x += pair.first
        y += pair.second
    }
}

fun main() {
    val point = MutablePoint(10, 20)
    point translate (5 at 5)
    println(&quot;Translated Point: (${point.x}, ${point.y})&quot;)
}

infix fun Int.at(dy: Int) = Pair(this, dy)</code></pre><h4 id="good-중위-함수로-선언하지-않은-경우">Good: 중위 함수로 선언하지 않은 경우</h4>
<pre><code class="language-kotlin">class MutablePoint(var x: Int, var y: Int) {
    fun translate(dx: Int, dy: Int) {
        x += dx
        y += dy
    }
}

fun main() {
    val point = MutablePoint(10, 20)
    point.translate(5, 5)
    println(&quot;Translated Point: (${point.x}, ${point.y})&quot;) // Translated Point: (15, 25)
}</code></pre>
<h2 id="616-factory-functions">6.16 Factory functions</h2>
<ul>
<li>클래스에 대한 팩토리 함수를 선언할 때, 클래스 자체와 같은 이름을 주는 것은 피하라. 팩토리 함수의 동작이 특별한 이유를 명확하게 나타내는 독특한 이름을 사용하는 것을 선호하라. 정말로 특별한 의미가 없을 때만 클래스와 같은 이름을 사용할 수 있다. <pre><code class="language-kotlin">class Point(val x: Double, val y: Double) {
  companion object {
      fun fromPolar(angle: Double, radius: Double) = Point(...)
  }
}</code></pre>
</li>
<li>다른 상위 클래스 생성자를 호출하지 않고, 기본 인자 값으로 단일 생성자로 줄일 수 없는 여러 개의 오버로드된 생성자를 가진 객체가 있다면, 오버로드된 생성자를 팩토리 함수로 대체하는 것이 좋다.<pre><code class="language-kotlin">// Before
class User {
  constructor(name: String) { ... }
  constructor(name: String, age: Int) { ... }
  constructor(name: String, age: Int, address: String) { ... }
} 
</code></pre>
</li>
</ul>
<p>// After
class User private constructor(name: String, age: Int, address: String) {
    companion object {
        fun createWithName(name: String) = User(name, 0, &quot;&quot;)
        fun createWithNameAndAge(name: String, age: Int) = User(name, age, &quot;&quot;)
        fun createWithNameAgeAndAddress(name: String, age: Int, address: String) = User(name, age, address)
    }
}</p>
<pre><code>
## 6.17 Platform types
- 플랫폼 타입의 표현식을 반환하는 공개 함수/메서드는 그것의 코틀린 타입을 명시적으로 선언해야 한다.
```kotlin
fun apiCall(): String = MyJavaApi.getProperty(&quot;name&quot;)</code></pre><ul>
<li>플랫폼 타입의 표현식으로 초기화된 모든 속성(패키지 레벨 또는 클래스 레벨)은 코틀린 타입을 명시적으로 선언해야 한다.<pre><code class="language-kotlin">class Person {
  val name: String = MyJavaApi.getProperty(&quot;name&quot;)
}</code></pre>
</li>
<li>플랫폼 타입의 표현식으로 초기화된 로컬 값은 타입 선언을 가질 수도 있고 가지지 않을 수도 있다.<pre><code class="language-kotlin">fun main() {
  val name = MyJavaApi.getProperty(&quot;name&quot;)
  println(name)
}</code></pre>
</li>
</ul>
<h2 id="618-scope-functions">6.18 <a href="https://velog.io/@dev-baik/%EB%B2%94%EC%9C%84-%EC%A7%80%EC%A0%95-%ED%95%A8%EC%88%98">Scope functions</a></h2>
<ul>
<li>코틀린은 주어진 객체의 컨텍스트에서 코드 블록을 실행하기 위한 일련의 함수를 제공한다 : let, run, with, apply, also</li>
<li>공식 문서 : <a href="https://kotlinlang.org/docs/scope-functions.html">https://kotlinlang.org/docs/scope-functions.html</a></li>
</ul>
<h1 id="7-coding-conventions-for-libraries">7. Coding conventions for libraries</h1>
<ul>
<li>라이브러리를 작성할 때에는 API 안정성을 보장하기 위해 추가적인 규칙 집합을 따르는 것이 권장된다.<ul>
<li>항상 멤버 가시성을 명시적으로 지정하라(무심코 선언을 공개 API로 노출하는 것을 피하기 위해). </li>
<li>항상 함수의 반환 타입과 속성 타입을 명시적으로 지정하라(구현이 변경될 때 반환 타입이 무심코 변경되는 것을 피하기 위해).</li>
<li>모든 공개 멤버에 대해 <a href="https://kotlinlang.org/docs/kotlin-doc.html">KDoc</a> 주석을 제공하라. 단, 새로운 문서화가 필요하지 않은 오버라이드는 제외한다(라이브러리에 대한 문서화를 생성하는데 도움이 되기 때문이다).</li>
</ul>
</li>
<li>라이브러리의 API를 작성할 때 고려해야 할 최고의 사례와 아이디어에 대해 더 자세히 알아보려면 <a href="https://kotlinlang.org/docs/jvm-api-guidelines-introduction.html">라이브러리 제작자 가이드라인</a>을 참조하라.</li>
</ul>
<h4 id="참고한-사이트">참고한 사이트</h4>
<ul>
<li><a href="https://kotlinlang.org/docs/coding-conventions.html">https://kotlinlang.org/docs/coding-conventions.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 11장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-11%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-11%EC%9E%A5</guid>
            <pubDate>Wed, 06 Dec 2023 05:45:15 GMT</pubDate>
            <description><![CDATA[<h1 id="dsl-만들기">DSL 만들기</h1>
<blockquote>
<ul>
<li>영역 특화 언어 만들기</li>
<li>수신 객체 지정 람다 사용</li>
<li>invoke 관례 사용</li>
<li>기존 코틀린 DSL 예제</li>
</ul>
</blockquote>
<h2 id="api에서-dsl로">API에서 DSL로</h2>
<ul>
<li>코드의 가독성과 유지 보수성을 가장 좋게 유지하기 위해서 상호작용이 일어나는 연결 지점(인터페이스)를 살펴봐야한다.</li>
<li>클래스 간의 상호작용을 이해하기 쉽고 명확하게 표현할 수 있게 만들어야 프로젝트를 계속 유지 보수할 수 있다.</li>
</ul>
<h4 id="api가-깔끔하다">API가 깔끔하다.</h4>
<ul>
<li>코드를 읽는 독자들이 어떤 일이 벌어질지 명확하게 이해할 수 있어야 한다. 이름과 개념을 잘 선택하면 이런 목적을 달성할 수 있다. 어떤 언어를 사요하건 이름을 잘 붙이고 적절한 개념을 사용하는 것은 매우 중요하다.</li>
<li>코드가 간결해야 한다. 불필요한 구문이나 번잡한 준비 코드가 가능한 한 적어야 한다. (간결함) 깔끔한 API는 언어에 내장된 기능과 거의 구분할 수 없다.</li>
</ul>
<h4 id="코틀린이-간결한-구문을-어떻게-지원하는가">코틀린이 간결한 구문을 어떻게 지원하는가?</h4>
<table>
    <tr>
        <th>일반 구문</th>
        <th>간결한 구문</th>
        <th>사용한 언어 특성</th>
    </tr>
    <tr>
        <td>StringUtil.capitalize(s)</td>
        <td>s.capitalize()</td>
        <td>확장 함수</td>
    </tr>
    <tr>
        <td>1.to("one")</td>
        <td>1 to "one"</td>
        <td>중위 호출</td>
    </tr>
    <tr>
        <td>set.add(2)</td>
        <td>set += 2</td>
        <td>연산자 오버로딩</td>
    </tr>
    <tr>
        <td>map.get("key")</td>
        <td>map["key"]</td>
        <td>get 메소드에 대한 관례</td>
    </tr>
    <tr>
        <td>file.use({ f -> f.read() })</td>
        <td>file.use { it.read() }</td>
        <td>람다를 괄호 밖으로 빼내는 관례</td>
    </tr>
    <tr>
        <td>sb.append("yes") sb.append("no")</td>
        <td>with(sb) { append("yes") append("no") }</td>
        <td>수신 객체 지정 람다</td>
    </tr>
</table>

<ul>
<li>코틀린 DSL은 간결한 구문을 제공하는 기능과 그런 구문을 확장해서 여러 메소드 호출을 조합함으로써 구조를 만들어내는 기능에 의존한다.</li>
<li>온전히 컴파일 시점에 타입이 정해진다. 따라서 컴파일 시점 오류 감지, IDE 지원 등 모든 정적 타입 지정 언어의 장점을 코틀린 DSL을 사용할 떄도 누릴 수 있다.</li>
</ul>
<h3 id="영역-특화-언어라는-개념">영역 특화 언어라는 개념</h3>
<h4 id="컴퓨터가-발명된-초기부터-컴퓨터로-풀-수-있는-모든-문제를-충분히-풀-수-있는-기능을-제공하는-범용-프로그래밍-언어와-특정-과업-또는-영역에-초점을-맞추고-그-영역에-필요하지-않은-기능을-없앤-영역-특화-언어dsl를-구분한다">컴퓨터가 발명된 초기부터 컴퓨터로 풀 수 있는 모든 문제를 충분히 풀 수 있는 기능을 제공하는 범용 프로그래밍 언어와 특정 과업 또는 영역에 초점을 맞추고 그 영역에 필요하지 않은 기능을 없앤 영역 특화 언어(DSL)를 구분한다.</h4>
<ol>
<li>SQL 문장을 실행할 필요가 있는 경우 클래스나 함수를 선언하는 것부터 시작할 필요가 없다. 대신에 모든 SQL 문장은 첫 키워드가 수행하려는 연산의 종류를 지정하고, 각 연산은 처리해야 할 작업에 맞춰 각각 서로 다른 문법과 키워드를 사용한다.</li>
</ol>
<ol start="2">
<li>정규식 프로그램은 압축적인 기호 문법을 사용해 텍스트가 어떻게 달라질 수 있는지 지정함으로써 대상 텍스트를 직접 기술한다. 이런 압축적인 문법을 사용함으로서 DSL은 범용 언어를 사용하는 경우보다 특정 영역에 대한 연산을 더 간결하게 기술할 수 있다.</li>
</ol>
<h4 id="dsl이-범용-프로그래밍-언어와-달리-더-선언적declarative이라는-점이-중요하다">DSL이 범용 프로그래밍 언어와 달리 더 선언적(declarative)이라는 점이 중요하다.</h4>
<ul>
<li><p>범용 프로그래밍 언어는 보통 명령적(imperative)이다. 명력적 언어는 어떤 연산을 완수하기 위해 필요한 각 단계를 순서대로 정확히 기술한다.</p>
</li>
<li><p>선언적 언어는 원하는 결과를 기술하기만 하고 그 결과를 달성하기 위해 필요한 세부 실행은 언어를 해석하는 엔진에 맡긴다. 실행 엔진이 결과를 얻는 과정을 전체적으로 한꺼번에 최적화하기 때문에 선언적 언어가 더 효율적인 경우가 자주 있다.</p>
</li>
<li><p>반면 명령적 접근법에서는 각 연산에 대한 구현을 독립적으로 최적화해야 한다.</p>
</li>
<li><p><strong>DSL 단점</strong> : DSL을 범용 언어로 만든 호스트 애플리케이션과 함께 조합하기가 어렵다.</p>
</li>
</ul>
<h3 id="내부-sdl">내부 SDL</h3>
<ul>
<li>독립적인 문법 구조를 가진 외부 DSL과는 반대로 내부 DSL은 범용언어로 작성된 프로그램의 일부이며, 범용 언어와 동일한 문법을 사용한다.</li>
<li>따라서 내부 DSL은 완전히 다른 언어가 아니라 DSL의 핵심 장점을 유지하면서 주 언어를 톡별한 방법으로 사용하는 것이다.</li>
</ul>
<h3 id="dsl의-구조">DSL의 구조</h3>
<ul>
<li>전형적인 라이브러리 : 여러 메소드로 이뤄지며, 클라이언트는 그런 메소드를 한 번씩 호출함으로써 라이브러리를 사용<ul>
<li>명령-질의 API : 함수 호출 시퀀스에는 아무런 구조가 없으며, 한 호출과 다른 호출 사이에는 아무 맥락도 존재하지 않는다.</li>
</ul>
</li>
<li>반대로 DSL의 메소드 호출은 DSL 문법에 의해 정해지는 더 커다란 구조에 속한다.</li>
</ul>
<h2 id="구조화된-api-구축-dsl에서-수신-객체-지정-dsl-사용">구조화된 API 구축: DSL에서 수신 객체 지정 DSL 사용</h2>
<h3 id="수신-객체-지정-람다와-확장-함수-타입">수신 객체 지정 람다와 확장 함수 타입</h3>
<ul>
<li>람다를 인자로 받는 buildString() 정의하기<pre><code class="language-kotlin">fun buildString(
  builderAction: (StringBuilder) -&gt; Unit
) : String {
  val sb = StringBuilder()
  // 람다 인자로 StringBuilder 인스턴스를 넘긴다.
  builderAction(sb)
  return sb.toString()
}
</code></pre>
</li>
</ul>
<p>val s = buildString {
    // it은 StringBuilder 인스턴스를 가리킨다.
    it.append(&quot;Hello, &quot;)
    it.append(&quot;World!&quot;)
}</p>
<p>println(s) // Hello, World!</p>
<pre><code>- 수신 객체 지정 람다를 사용해 다시 정의한 buildString()
```kotlin
fun buildString(
    // 확장 함수 타입 : 수신 객체가 있는 함수 타입의 파라미터를 선언한다.
    // 수신 객체 타입.파라미터 타입 -&gt; 반환 타입
    builderAction: StringBuilder.() -&gt; Unit
) : String {
    val sb = StringBuilder()
    // StringBuilder 인스턴스를 람다의 수신 객체로 넘긴다.
    sb.builderAction()
    return sb.toString()
}

val s = buildString {
    // &quot;this&quot; 키워드는 StringBuilder 인스턴스를 가리킨다.
    this.append(&quot;Hello, &quot;)
    // &quot;this&quot;를 생략해도 묵시적으로 StringBuilder 인스턴스가 수신 객체로 취급된다.
    append(&quot;World!&quot;)
}

println(s) // Hello, World!</code></pre><ul>
<li>확장 함수나 수신 객체 지정 람다에서는 모든 함수(람다)를 호출할 때 수신 객체를 지정해야만 하고, 함수(람다) 본문 안에서는 그 수신 객체를 특별한 수식자 없이 사용할 수 있다.</li>
<li>builderActiondms StringBuilder 클래스 안에 정의가 있는 함수가 아니며, StringBuilder 인스턴스인 sb는 확장 함수를 호출할 때와 동일한 구문으로 호출할 수 있는 함수 타입(따라서 확장 함수 타입)의 인자일 뿐이다.</li>
</ul>
<ul>
<li>수신 객체 지정 람다를 변수에 저장하기<pre><code class="language-kotlin">// appendExcl은 확장 함수 타입의 값이다.
val appendExcl : StringBuilder.() -&gt; Unit = { this.append(&quot;!&quot;) }
</code></pre>
</li>
</ul>
<p>val stringBuilder = StringBuilder(&quot;Hi&quot;)
// appendExcl을 확장 함수처럼 호출할 수 있다.
stringBuilder.appendExcl()</p>
<p>// appendExcel을 인자로 넘길 수 있다.
println(StringBuilder) // Hi!
println(buildString(appendExcl)) // !</p>
<pre><code>- builderAction을 apply 함수에게 인자로 넘기기
```kotlin
fun buildString(builderAction: StringBuilder.() -&gt; Unit): String =
    StringBuilder().apply(builderAction).toString()</code></pre><ul>
<li>apply 함수는 인자로 받은 람다나 함수(builderAction)를 호출하면서 자신의 수신 객체(StringBuilder의 인스턴스)를 람다나 함수의 묵시적 수신 객체로 사용한다.</li>
<li>apply는 수신 객체 타입에 대한 확장 함수로 선언됐기 때문에 수신 객체의 메소드처럼 불리며, 수신 객체를 묵시적 인자(this)로 받는다.<pre><code class="language-kotlin">inline fun &lt;T&gt; T.apply(block: T.() -&gt; Unit) : T {
  block()
  return this // 수신 객체를 다시 반환한다.
}</code></pre>
</li>
<li>with는 수신 객체를 첫 번째 파라미터로 받는다.<pre><code class="language-kotlin">inline fun &lt;T, R&gt; with(receiver: T, block: T.() -&gt; R): R =
  receiver.block() // 람다를 호출해 얻은 결과를 반환한다.</code></pre>
</li>
<li>결과를 받아서 쓸 필요가 없다면 두 함수를 서로 바꿔 쓸 수 있다.<pre><code class="language-kotlin">val map = mutableMapOf(1 to &quot;one&quot;)
map.apply { this[2] = &quot;two&quot; }
with(map) { this[3] = &quot;three&quot; }
println(map) // {1=one, 2=two, 3=three}</code></pre>
</li>
</ul>
<h2 id="invoke-관례를-사용한-더-유연한-블록-중첩">invoke 관례를 사용한 더 유연한 블록 중첩</h2>
<ul>
<li>invoke 관례를 사용하면 객체를 함수처럼 호출할 수 있고, 함수처럼 호출할 수 있는 객체를 만드는 클래스를 정의할 수 있다.</li>
</ul>
<h3 id="invoke-관례-함수처럼-호출할-수-있는-객체">invoke 관례: 함수처럼 호출할 수 있는 객체</h3>
<ul>
<li>관례 : 특별한 이름이 붙은 함수를 일반 메소드 호출 구문으로 호출하지 않고 더 간단한 다른 구문으로 호출할 수 있게 지원하는 기능</li>
<li>operator 변경자가 붙은 invoke 메소드 정의가 들어있는 클래스의 객체를 함수처럼 호출할 수 있다.<pre><code class="language-kotlin">// 클래스 안에서 invoke 메소드 정의하기
class Greeter(val greeting: String) {
  // Greeter 안에 &quot;invoke&quot; 메소드를 정의한다.
  operator fun invoke(name: String) {
      println(&quot;$greeting, $name&quot;)
  }
}
</code></pre>
</li>
</ul>
<p>val bavarianGreeter = Greeter(&quot;Servus&quot;)
// Greeter 인스턴스를 함수처럼 호출한다.
// 내부적으로 bavarianGreeter.invoke(&quot;Dmitry&quot;)로 컴파일된다.
bavarianGreeter(&quot;Dmitry&quot;) // Servus, Dmitry!</p>
<pre><code>- 여러 파라미터 타입을 지원하기 위해 invoke를 오버로딩할 수 있다. 오버로딩한 invoke가 있는 클래스의 인스턴스를 함수처럼 사용할 때는 오버로딩한 여러 시그니처를 모두 다 활용할 수 있다.

### invoke 관례와 함수형 타입
- 인라인하는 람다를 제외한 모든 람다는 함수형 인터페이스(Function1 등)를 구현하는 클래스로 컴파일된다. 각 함수형 인터페이스 안에는 그 인터페이스 이름이 가리키는 개수만큼 파라미터를 받는 invoke 메소드가 들어있다.
```kotlin
// 이 인터페이스는 인자를 2개 받는 함수를 표현한다.
interface Function2&lt;in P1, in P2, out R&gt; {
    operator fun invoke(p1: P1, p2: P2): R
} </code></pre><ul>
<li>람다를 함수처럼 호출하면 이 관례에 따라 invoke 메소드 호출로 변환된다.</li>
</ul>
<h4 id="이런-사실을-알면-어떤-점이-좋을까">이런 사실을 알면 어떤 점이 좋을까?</h4>
<ul>
<li><p>복잡한 람다를 여러 메소드로 분리하면서도 여전히 분리 전의 람다처럼 외부에서 호출할 수 있는 객체를 만들 수 있다.</p>
<pre><code class="language-kotlin">class ComplexLambda {
  private var value: Int = 0

  fun initialize(initialValue: Int): ComplexLambda {
      value = initialValue
      return this
  }

  fun add(number: Int): ComplexLambda {
      value += number
      return this
  }

  fun multiply(factor: Int): ComplexLambda {
      value *= factor
      return this
  }

  operator fun invoke(): Int {
      return value
  }
}
</code></pre>
</li>
</ul>
<p>fun main() {
    // 복잡한 람다를 여러 메소드로 분리하면서도 호출 가능한 객체 생성
    val complexLambda = ComplexLambda()
        .initialize(10)
        .add(5)
        .multiply(2)</p>
<pre><code>val result = complexLambda()
println(result) // 30</code></pre><p>}</p>
<pre><code>- 함수 타입 파라미터를 받는 함수에게 그 객체를 전달할 수 있다.
```kotlin
class DataProcessor {
    // 함수 타입 파라미터를 받는 함수
    fun &lt;T&gt; processData(data: T, processorFunction: (T) -&gt; Unit) {
        // 전달된 함수에 객체 전달
        // processorFunction.invoke(data)
        processorFunction(data)
    }
}

fun main() {
    val dataProcessor = DataProcessor()

    val stringProcessor: (String) -&gt; Unit = { value -&gt;
        println(&quot;Processing string: $value&quot;)
    }

    val intProcessor: (Int) -&gt; Unit = { value -&gt;
        println(&quot;Processing integer: $value&quot;)
    }

    dataProcessor.processData(&quot;Hello, Kotlin!&quot;, stringProcessor)
    dataProcessor.processData(42, intProcessor)
} </code></pre><ul>
<li><p>이런 식으로 기존 람다를 여러 함수로 나눌 수 있으려면 함수 타입 인터페이스를 구현하는 클래스를 정의해야 한다.</p>
</li>
<li><p>이때 기반 인터페이스를 FunctionN&lt;P1, ..., PN, R&gt; 타입이나 (P1, ..., PN) -&gt; R 타입으로 명시해야 한다.</p>
<pre><code class="language-kotlin">interface MyLambda : Function3&lt;Int, Int, Int, Int&gt; {
  fun initialize(initialValue: Int): MyLambda
  fun add(number: Int): MyLambda
  fun multiply(factor: Int): MyLambda

  override operator fun invoke(p1: Int, p2: Int, p3: Int): Int {
      return p1 + p2 + p3
  }
}
</code></pre>
</li>
</ul>
<p>class LambdaProcessor : MyLambda {
    private var value: Int = 0</p>
<pre><code>override fun initialize(initialValue: Int): MyLambda {
    value = initialValue
    return this
}

override fun add(number: Int): MyLambda {
    value += number
    return this
}

override fun multiply(factor: Int): MyLambda {
    value *= factor
    return this
}</code></pre><p>}</p>
<p>fun main() {
    val lambdaProcessor = LambdaProcessor()
        .initialize(10)
        .add(5)
        .multiply(2)</p>
<pre><code>val result = lambdaProcessor(1, 2, 3)
println(result) // 18</code></pre><p>}</p>
<pre><code>- 함수 타입을 확장하면서 invoke()를 오버라이딩하기
```kotlin
data class Issue(
    val id: String, val project: String, val type: String,
    val property: String, val description: String
)

// 함수 타입을 부모 클래스로 사용한다.
class ImportantIssuesPredicate(val project: String) : (Issue) -&gt; Boolean {
    override fun invoke(issue: Issue): Boolean {
        return issue.project == project &amp;&amp; issue.isImportant()
    }

    private fun Issue.isImportant(): Boolean {
        return type == &quot;Bug&quot; &amp;&amp; (priority == &quot;Major&quot; || priority == &quot;Critical&quot;)
    }
}

val i1 = Issue(&quot;IDEA-154446&quot;, &quot;IDEA&quot;, &quot;Bug&quot;, &quot;Major&quot;, &quot;Save settings failed&quot;)
val i2 = Issue(&quot;KT-12183&quot;, &quot;Kotlin&quot;, &quot;Feature&quot;, &quot;Normal&quot;,
    &quot;Intention: convert several calls on the same receiver to with/apply&quot;)
val predicate = ImportantIssuesPredicate(&quot;IDEA&quot;)

for (issue in listOf(i1, i2).filter(predicate)) {
    println(issue.id)
} // IDEA-154446</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 8장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-8%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-8%EC%9E%A5</guid>
            <pubDate>Fri, 01 Dec 2023 06:16:02 GMT</pubDate>
            <description><![CDATA[<h1 id="고차-함수-파라미터와-반환-값으로-람다-사용">고차 함수: 파라미터와 반환 값으로 람다 사용</h1>
<blockquote>
<ul>
<li>함수 타입</li>
<li>고차 함수와 코드를 구조화할 때 고차 함수를 사용하는 방법</li>
<li>인라인 함수</li>
<li>비로컬 return과 레이블</li>
<li>무명 함수</li>
</ul>
</blockquote>
<h2 id="고차-함수-정의">고차 함수 정의</h2>
<blockquote>
<p>💡 다른 함수를 인자로 받거나 함수를 반환하는 함수</p>
</blockquote>
<ul>
<li>코틀린에서는 람다나 함수 참조를 사용해 함수를 값으로 표현할 수 있다.<ul>
<li>고차 함수는 람다나 함수 참조를 인자로 넘길 수 있거나 람다나 함수 참조를 반환하는 함수다.</li>
</ul>
</li>
<li>물론 함수를 인자로 받는 동시에 함수를 반환하는 함수도 고차 함수다.</li>
</ul>
<ul>
<li>filter는 술어 함수를 인자로 받으므로 고차 함수다 : <code>list.filter { x &gt; 0 }</code></li>
</ul>
<h3 id="함수-타입">함수 타입</h3>
<p>람다를 인자로 받는 함수를 정의하려면 먼저 람다 인자의 타입을 어떻게 선언할 수 있는 지 알아야 한다.</p>
<ul>
<li>람다를 로컬 변수에 대입하는 경우<pre><code class="language-kotlin">// 타입 추론으로 인해 변수 타입을 지정하지 않아도 된다.
val sum = { x: Int, y: Int -&gt; x, y }
val action = { println(42) }
</code></pre>
</li>
</ul>
<p>// 구체적인 타입 선언을 추가한다면 : 파라미터 타입 -&gt; 반환 타입
val sum: (Int, Int) -&gt; Int = { x, y -&gt; x + y }
val action: () -&gt; Unit = { println(42) }</p>
<pre><code>- Unit 타입 : 의미 있는 값을 반환하지 않는 함수 반환 타입에 쓰는 특별한 타입


- 반환 타입을 널이 될 수 있는 타입으로 지정
```kotlin
var canReturnNull: (Int, Int) -&gt; Int? = { x, y -&gt; null } </code></pre><ul>
<li>널이 될 수 있는 함수 타입 변수 정의<pre><code class="language-kotlin">var funOrNull: ((Int, Int) -&gt; Int)? = null </code></pre>
</li>
<li>funOrNull의 타입을 지정하면서 괄호를 빼먹으면 널이 될 수 있는 함수 타입이 아니라 널이 될 수 있는 반환 타입을 갖는 함수 타입을 선언하게 된다.</li>
</ul>
<blockquote>
<p>canReturnNull의 타입과 funOrNull의 타입 사이에는 큰 차이가 있다는 사실에 유의하라.</p>
</blockquote>
<h4 id="파라미터-이름과-함수-타입">파라미터 이름과 함수 타입</h4>
<ul>
<li>함수 타입에서 파라미터 이름을 지정할 수도 있다.<pre><code class="language-kotlin">fun performRequest(
  url: String,
  callback: (code: Int, content: String) -&gt; Unit
) { /*...*/ }
</code></pre>
</li>
</ul>
<p>val url = &quot;<a href="http://kotl.in&quot;">http://kotl.in&quot;</a>
performRequest(url) { code, content -&gt; /<em>...</em>/ }
performRequest(url) { code, page -&gt; /<em>...</em>/ }</p>
<pre><code>- 함수 타입의 람다를 정의할 때 파라미터 이름이 꼭 함수 타입 선언의 파라미터 이름과 일치하지 않아도 된다.
- 하지만 함수 타입에 인자 이름을 추가하면 코드 가독성이 좋아지고, IDE는 그 이름을 코드 완성에 사용할 수 있다.

### 인자로 받은 함수 호출
- 간단한 고차 함수 정의하기
```kotlin
fun twoAndThree(operation: (Int, Int) -&gt; Int) {
    val result = operation(2, 3)
    println(&quot;The result is $result&quot;)
}

twoAndThree { a, b -&gt; a + b } // The result is 5
twoAndThree { a, b -&gt; a * b } // The result is 6</code></pre><ul>
<li>술어 함수를 파라미터로 받는 filter 함수 정의하기<pre><code class="language-kotlin">fun String.filter(
  predicate: (Char) -&gt; Boolean
): String
</code></pre>
</li>
</ul>
<p>fun 수신객체타입.filter(
    // 파라미터 이름: 파라미터 함수 타입
    파라미터 이름: (파라미터로 받는 함수의 파라미터 타입) -&gt; 파라미터로 받는 함수의 반환 타입
): String</p>
<pre><code>- predicate 파라미터는 문자(Char 타입)를 파라미터로 받고 불리언 결과 값을 반환한다.
- 술어는 인자로 받은 문자가 filter 함수가 돌려주는 결과 문자열에 남아 있기를 바라면 true를 반환하고 문자열에서 사리지기를 바라면 false를 반환하면 된다.

&gt; ✅ **[술어(명제)](https://ko.wikipedia.org/wiki/%EB%AA%85%EC%A0%9C)** : &#39;참&#39; 혹은 &#39;거짓&#39;임을 검증할 수 있는 &#39;객관적 사태&#39;가 포함된 문장

- filter 함수를 단순하게 만든 버전 구현하기
```kotlin
fun String.filter(predicate: (Char) -&gt; Boolean): String {
    val sb = StringBuilder()
    for (index in 0 until length) {
        val element = get(index)
        if (predicate(element)) sb.append(element)
    }
    return sb.toString()
}

println(&quot;ab1c&quot;.filter { it in &#39;a&#39;..&#39;z&#39; }) // abc</code></pre><h3 id="자바에서-코틀린-함수-타입-사용">자바에서 코틀린 함수 타입 사용</h3>
<p><span style='background-color: #fff5b1'/>함수 타입인 변수는 인자 개수에 따라 적당한 FunctionN 인터페이스를 구현하는 클래스의 인스턴스를 저장하며, 그 클래스의 invoke 메소드 본문에는 람다의 본문이 들어간다.</p>
<ul>
<li>컴파일된 코드 안에서 함수 타입은 일반 인터페이스로 바뀐다. 즉 함수 타입의 변수는 FunctionN 인터페이스를 구현하는 객체를 저장한다.</li>
<li>코틀린 표준 라이브러리는 함수 인자의 개수에 따라 Function0<R>(인자가 없는 함수), Function1&lt;P1, R&gt;(인자가 하나인 함수) 등의 인터페이스를 제공한다.</li>
<li>각 인터페이스에는 invoke 메소드 정의가 하나 들어있다. invoke를 호출하면 함수를 실행할 수 있다.<pre><code class="language-kotlin">// 코틀린
fun processTheAnswer(f: (Int) -&gt; Int) {
  println(42)
}
</code></pre>
</li>
</ul>
<p>// 자바 8 이후
processTheAnswer(number -&gt; number + 1); // 43</p>
<p>// 자바 8 이전
processTheAnswer { 
    new Function1&lt;Integer, Integer&gt;() {
        @Override
        public Integer invoke(Integer number) {
            System.out.println(number);
            return number + 1;
        }
    }
}</p>
<pre><code>- 확장 함수
```java
List&lt;String&gt; strings = new ArrayList();
strings.add(&quot;42&quot;);
CollectionsKt.forEach(strings, s -&gt; {
    System.out.println(s);
    return Unit.INSTANCE;
});</code></pre><h3 id="디폴트-값을-지정한-함수-타입-파라미터나-널이-될-수-있는-함수-타입-파라미터">디폴트 값을 지정한 함수 타입 파라미터나 널이 될 수 있는 함수 타입 파라미터</h3>
<ul>
<li>하드 코딩을 통해 toString 사용 관례를 따르는 joinToString</li>
</ul>
<blockquote>
<p>✅ <strong><a href="https://namu.wiki/w/%ED%95%98%EB%93%9C%EC%BD%94%EB%94%A9">하드 코딩</a></strong> : 프로그래밍에서 특정 값이나 정보를 소스 코드 내에 직접 입력하여 사용하는 것</p>
</blockquote>
<pre><code class="language-kotlin">fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}</code></pre>
<ul>
<li><p>이 구현은 유연하지만 핵심 요소 하나를 제어할 수 없다는 단점이 있다.</p>
<ul>
<li>핵심 요소 : 컬렉션의 각 원소를 문자열로 변환하는 방법</li>
</ul>
</li>
<li><p><strong>WHY)</strong> StringBuilder.append(o: Any?)를 사용하는 데, 이 함수는 항상 객체를 toString 메소드를 통해 문자열로 바꾼다.</p>
</li>
<li><p><strong>해결책)</strong> 원소를 문자열로 바꾸는 방법을 람다로 전달하면 된다.</p>
<pre><code class="language-kotlin">fun main() {
  val stringBuilder = StringBuilder()

  val list = listOf(1, 2, 3, 4, 5)

  stringBuilder.append(&quot;[&quot;)
  list.joinTo(stringBuilder, transform = { element -&gt;
      (element * element).toString()
  })
  stringBuilder.append(&quot;]&quot;)

  println(stringBuilder.toString()) // [1, 4, 9, 16, 25]
}</code></pre>
</li>
<li><p><strong>문제점)</strong> joinToString를 호출할 때마다 매번 람다를 넘기게 만들면 기본 동작으로도 충분한 대부분의 경우 함수 호출을 오히려 더 불편하게 만든다.</p>
</li>
<li><p><strong>해결책)</strong> 함수 타입의 파라미터에 대한 디폴트 값 지정하기</p>
<pre><code class="language-kotlin">fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
  separator: String = &quot;, &quot;,
  prefix: String = &quot;&quot;,
  postfix: String = &quot;&quot;,
  // 함수 타입 파라미터를 선언하면서 람다를 디폴트 값으로 지정
  transform: (T) -&gt; String = { it.toString() }
): String {
  val result = StringBuilder(prefix)

  for ((index, element) in this.withIndex()) {
      if (index &gt; 0) result.append(separator)
      result.append(transform(element))
  }
  result.append(postfix)
  return result.toString()
}
</code></pre>
</li>
</ul>
<p>val letters = listOf(&quot;Alpha&quot;, &quot;Beta&quot;)
println(letters.joinToString()) // Alpha, Beta
println(letters.joinToString { it.toLowerCase() }) // alpha, beta
println(letters.joinToString(separator = &quot;!&quot;, postfix = &quot;! &quot;,
    transform = { it.toUpperCase() })) // ALPHA! BETA</p>
<pre><code>- 해당 함수(joinToString)는 컬렉션의 원소 타입을 표현하는 T를 타입 파라미터로 받는 제네릭 함수다. transfrom 람다는 그 T 타입의 값을 인자로 받는다.
- 다른 디폴트 파라미터 값과 마찬가지로 함수 타입에 대한 디폴트 값 선언도 `=`뒤에 람다를 넣으면 된다.
```kotlin
// buildString 함수 사용하기: stringBuilder를 직접 다룰 필요 없이 문자열 생성
fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;,
    transform: (T) -&gt; String = { it.toString() }
): String = buildString {
    append(prefix)
    for ((index, element) in this@joinToString.withIndex()) {
        if (index &gt; 0) append(separator)
        append(transform(element))
    }
    append(postfix)
}

fun main() {
    val letters = listOf(&quot;Alpha&quot;, &quot;Beta&quot;)
    println(letters.joinToString()) // Alpha, Beta
    println(letters.joinToString { it.toLowerCase() }) // alpha, beta
    println(letters.joinToString(separator = &quot;!&quot;, postfix = &quot;! &quot;) { it.toUpperCase() }) // ALPHA! BETA
} </code></pre><ol>
<li>람다를 아예 생략하거나(람다를 생략하면 디폴트 람다에 있는 대로 toString()을 써서 원소를 변환한다)</li>
<li>인자 목록 뒤에 람다를 넣거나(여기서는 람다 밖에 전달할 인자가 없어서 () 없이 람다만 썻다)</li>
<li>이름 붙인 인자로 람다를 전달할 수 있다.</li>
<li>널이 될 수 있는 함수 타입 사용할 수도 있다.</li>
</ol>
<ul>
<li><p>널이 될 수 있는 함수 타입으로 함수를 받으면 그 함수를 직접 호출할 수 없다는 점에 유의하라.</p>
</li>
<li><p><strong>WHY)</strong> 코틀린은 NPE가 발생할 가능성이 있으므로 그런 코드의 컴파일을 거부할 것이다.</p>
</li>
<li><p>null 여부를 명시적으로 검사하는 것도 한 가지 해결 방법이다.</p>
<pre><code class="language-kotlin">fun foo(callback: (() -&gt; Unit)?) {
  // ...
  if (callback != null) {
      callback()
  }
} </code></pre>
</li>
<li><p>함수 타입이 invoke 메소드를 구현하는 인터페이스라는 사실을 활용하면 더 짧게 만들 수 있다.</p>
<pre><code class="language-kotlin">// 널이 될 수 있는 함수 타입 파라미터를 사용하기
fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
  separator: String = &quot;, &quot;,
  prefix: String = &quot;&quot;,
  postfix: String = &quot;&quot;,
  transform: ((T) -&gt; String)? = null
): String {
  val result = StringBuilder(prefix)

  for ((index, element) in this.withIndex()) {
      if (index &gt; 0) result.append(separator)
      val str = transform?.invoke(element)
          ?: element.toString()
      result.append(str)
  }
  result.append(postfix)
  return result.toString()
}</code></pre>
</li>
</ul>
<h3 id="함수를-함수에서-반환">함수를 함수에서 반환</h3>
<pre><code class="language-kotlin">// 함수를 반환하는 함수 정의하기
enum class Delivery { STANDARD, EXPEDITED }

class Order(val itemCount: Int)

fun getShippingCostCalculator(delivery: Delivery): (Order) -&gt; Double {
    if (delivery == Delivery.EXPEDITED) {
        return { order -&gt; 6 + 2.1 * order.itemCount }
    }
    return { order -&gt; 1.2 * order.itemCount }
}

val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println(&quot;Shipping costs ${calculator(Order(3))}&quot;) // Shipping costs 12.3</code></pre>
<ul>
<li>다른 함수를 반환하는 함수를 정의하려면 함수의 반환 타입으로 함수 타입을 지정해야 한다.</li>
<li>함수를 반환하려면 return 식에 람다나 멤버 참조나 함수 타입의 값을 계산하는 식 등을 넣으면 된다.</li>
</ul>
<ul>
<li>ContactListFilters 클래스를 사용해 선택 사항의 상태를 저장한다.<pre><code class="language-kotlin">class ContactListFilters {
  var prefix: String = &quot;&quot;
  var onlyWithPhoneNumber: Boolean = false
}</code></pre>
</li>
<li>함수를 반환하는 함수를 UI 코드에서 사용하기<pre><code class="language-kotlin">data class Person(
  val firstName: String,
  val lastName: String,
  val phoneNumbeer: String?
)
</code></pre>
</li>
</ul>
<p>class ContactListFilters {
    var prefix: String = &quot;&quot;
    var onlyWithPhoneNumber: Boolean = false</p>
<pre><code>fun getPredicate(): (Person) -&gt; Boolean {
    val startsWithPrefix = { p: Person -&gt;
        p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
    }
    if (!onlyWithPhoneNumber) {
        return startsWithPrefix
    }
    return { startsWithPrefix(it) &amp;&amp; it.phoneNumbeer != null }
}</code></pre><p>}</p>
<p>val contacts = listOf(Person(&quot;Dmitry&quot;, &quot;Jemerov&quot;, &quot;123-4567&quot;),
            Person(&quot;Svetlana&quot;, &quot;Isakova&quot;, null))
val contactListFilters = ContactListFilters()
with(contactListFilters) {
    prefix = &quot;Dm&quot;
    onlyWithPhoneNumber = true
}</p>
<p>// 함수 타입을 사용하면 함수에서 함수를 쉽게 반환할 수 있다.
println(contacts.filter(
    contactListFilters.getPredicate()
)) // [Person(firstName=Dmitry, lastName=Jemerov, phoneNumber=123-4567)]</p>
<pre><code>- getPredicate 메소드는 filter 함수에게 인자로 넘길 수 있는 함수를 반환한다.

### 람다를 활용한 중복 제거
```kotlin
// 사이트 방문 데이터 정의
data class SiteVisit(
    val path: String,
    val duration: Double,
    val os: OS
)

enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

val log = listOf(
    SiteVisit(&quot;/&quot;, 34.0, OS.WINDOWS),
    SiteVisit(&quot;/&quot;, 22.0, OS.MAC),
    SiteVisit(&quot;/login&quot;, 12.0, OS.WINDOWS),
    SiteVisit(&quot;/signup&quot;, 8.0, OS.IOS),
    SiteVisit(&quot;/&quot;, 16.3.0, OS.ANDROID),
)

// 사이트 방문 데이터를 하드 코딩한 필터를 사용해 분석하기
val averageWindowsDuration = log
    .filter { it.os == OS.WINDOWS }
    .map(SiteVisit::duration)
    .average()

// 윈도우 사용자의 평균 방문 시간 출력
println(averageWindowsDuration) // 23.0</code></pre><ul>
<li>일반 함수를 통해 중복 제거하기<pre><code class="language-kotlin">fun List&lt;SiteVisit&gt;.averageDurationFor(os: OS) =
  filter { it.os == os }.map(SiteVisit::duration).average()
</code></pre>
</li>
</ul>
<p>println(log.averageDurationFor(OS.WINDOWS)) // 23.0
println(log.averageDurationFor(OS.MAC)) // 22.0 </p>
<pre><code>- 복잡하게 하드코딩한 필터를 사용해 방문 데이터 분석하기
```kotlin
val averageMobileDuration = log
    .filter { it.os in setOf(OS.IOS, OS.ANDROID) }
    .map(SiteVisit::duration)
    .average()

println(averageMobileDuration) // 12.15</code></pre><ul>
<li>고차 함수를 사용해 중복 제거하기<pre><code class="language-kotlin">fun List&lt;SiteVisit&gt;.averageDurationFor(predicate: (SiteVisit) -&gt; Boolean) =
  filter(predicate).map(SiteVisit::duration).average()
</code></pre>
</li>
</ul>
<p>println(log.averageDurationFor {
    it.os in setOf(OS.ANDROID, OS.IOS)
}) // 12.15</p>
<p>// ios 사용자의 /signup 페이지 평균 방문 시간
println(log.averageDurationFor {
    it.os == OS.IOS &amp;&amp; it.path == &quot;/signup&quot;
}) // 8.0</p>
<pre><code>
## [인라인 함수: 람다의 부가 비용 없애기](https://velog.io/@dev-baik/inline-%ED%95%A8%EC%88%98)

- 어떤 함수를 inline으로 선언하면 그 함수의 본문이 인라인된다.
  - 함수를 호출하는 코드를 함수를 호출하는 바이트코드 대신에 함수 본문을 번역한 바이트 코드로 컴파일한다.
```kotlin
inline fun &lt;T&gt; synchronzied(lock: Lock, action: () -&gt; T): T {
    lock.lock()
    try {
        return action()
    }
    finally {
        lock.unlock()
    }
}

val l = Lock()
synchronzied(l) {
    ...
}</code></pre><pre><code class="language-kotlin">fun foo(l: Lock) {
    println(&quot;Before sync&quot;)
    synchronized(l) {
        println(&quot;Action&quot;)
    }
    println(&quot;After sync&quot;)
} </code></pre>
<ul>
<li>foo 함수를 컴파일한 버전<pre><code class="language-kotlin">fun __foo__(l: Lock) {
  // synchronized 함수를 호출하는 foo 함수의 코드 1
  println(&quot;Before sync&quot;)
  // synchronized 함수가 인라이닝된 코드 1
  l.lock()
  try {
      // 람다 코드의 본문이 인라이닝된 코드
      println(&quot;Action&quot;)
  // synchronized 함수가 인라이닝된 코드 2
  } finally {
      l.unlock()
  }
  // synchronized 함수를 호출하는 foo 함수의 코드 2
  println(&quot;After sync&quot;)
} </code></pre>
</li>
</ul>
<blockquote>
<p>synchronized 함수의 본문뿐 아니라 synchronized에 전달된 람다의 본문도 함께 인라이닝된다는 점에 유의하라.</p>
</blockquote>
<ul>
<li>람다의 본문에 의해 만들어지는 바이트코드는 그 람다를 호출하는 코드(synchronized) 정의의 일부분으로 간주되기 때문에 코틀린 컴파일러는 그 람다를 함수 인터페이스를 구현하는 무명 클래스로 감싸지 않는다.</li>
</ul>
<ul>
<li><span style='background-color: #fff5b1'/>인라인 함수를 호출하면서 람다를 넘기는 대신에 함수 타입의 변수를 넘기는 경우 인라인 함수를 호출하는 코드 위치에서는 변수에 저장된 람다의 코드를 알 수 없다.<pre><code class="language-kotlin">class LockOwner(val lock: Lock) {
  fun runUnderLock(body: () -&gt; Unit) {
      // 람다 대신에 함수 타입인 변수를 인자로 넘긴다.
      synchronized(lock, body)
  }
} </code></pre>
</li>
<li><span style='background-color: #fff5b1'/>람다 본문은 인라이닝되지 않고 synchronized 함수의 본문만 인라이닝된다.<ul>
<li>람다는 다른 일반적인 경우와 마찬가지로 호출된다.</li>
</ul>
</li>
<li>runUnderLock을 컴파일한 바이트코드<pre><code class="language-kotlin">class LockOwner(val lock: Lock) {
  fun __runUnderLock__(body: () -&gt; Unit) {
      lock.lock()
      try {
          // synchronized를 호출하는 부분에서 람다를 알 수 없으므로
          // 본문(body())은 인라이닝되지 않는다.
          body()
      } finally {
          lock.unlock()
      }
  }
}</code></pre>
</li>
<li>한 인라인 함수를 두 곳에서 다른 람다를 사용해 호출한다면 그 두 호출은 각각 따로 인라이닝된다.</li>
<li>인라인 함수의 본문 코드가 호출 지점에 복사되고 각 람다의 본문이 인라인 함수의 본문 코드에서 람다를 사용하는 위치에 복사된다.</li>
</ul>
<h3 id="인라인-함수의-한계">인라인 함수의 한계</h3>
<ul>
<li><span style='background-color: #fff5b1'/>함수가 인라이닝될 때 그 함수에 인자로 전달된 람다 식의 본문은 결과 코드에 직접 들어갈 수 있다.</li>
<li>하지만 이렇게 람다가 본문에 직접 펼쳐지기 때문에 함수가 파라미터로 전달받은 람다를 본문에 사용하는 방식이 한정될 수밖에 없어 람다를 사용하는 모든 함수를 인라이닝할 수는 없다.</li>
</ul>
<ul>
<li>파라미터로 받은 람다를 다른 변수에 저장하고 나중에 그 변수를 사용한다면 람다를 표현하는 객체가 어딘가는 존재해야 하기 때문에 람다를 인라이닝할 수 없다.</li>
<li>일반적으로 인라인 함수의 본문에서 람다 식을 바로 호출하거나 람다 식을 인자로 전달받아 바로 호출하는 경우에는 그 람다를 인라이닝할 수 있다. </li>
<li>그 외에는 &quot;Illegal usage of inline-parameter&quot; 오류와 함께 인라이닝을 금지시킨다.</li>
</ul>
<pre><code class="language-kotlin">fun &lt;T, R&gt; Sequence&lt;T&gt;.map(transform: (T) -&gt; R): Sequence&lt;R&gt; {
    return TransformingSequence(this, transform)
}</code></pre>
<ul>
<li>Transform 파라미터로 전달받은 함수 값을 호출하지 않는 대신, TransformingSequence라는 클래스의 생성자에게 그 함수 값을 넘긴다.<ul>
<li>TransformingSequence 생성자는 전달받은 람다를 프로퍼티로 저장한다.</li>
</ul>
</li>
<li>이런 기능을 지원하려면 map에 전달되는 transform 인자를 일반적인(인라이닝하지 않은) 함수 표현으로 만들 수밖에 없다.<ul>
<li>즉, 여기서는 transform을 함수 인터페이스를 구현하는 무명 클래스 인스턴스로 만들어야만 한다. 인라이닝하면 안되는 람다를 파라미터로 받아, nonline 변경자를 이름 앞에 붙여서 인라이닝을 금지할 수 있다.</li>
</ul>
</li>
</ul>
<ul>
<li>둘 이상의 람다를 인자로 받는 함수에서 일부 람다만 인라이닝하고 싶은 경우<pre><code class="language-kotlin">inline fun foo(inlined: () -&gt; Unit, noinline notInlined: () -&gt; Unit) {
  // ...
}                                                                                        </code></pre>
</li>
<li>코틀린에서는 어떤 모듈이나 서드파티 라이브러리 안에서 인라인 함수를 정의하고 그 모듈이나 라이브러리 밖에서 해당 인라인 함수를 사용할 수 있다.</li>
</ul>
<h3 id="컬렉션-연산-인라이닝">컬렉션 연산 인라이닝</h3>
<ul>
<li>람다를 사용해 컬렉션 거르기<pre><code class="language-kotlin">data class Person(val name: String, val age: Int)
</code></pre>
</li>
</ul>
<p>val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))</p>
<p>println(people.filter { it.age &lt; 30 }) // [Person(name=Alice, age=29)]</p>
<pre><code>- 컬렉션 직접 거르기
```kotlin
val result = mutableListOf&lt;Person&gt;()
for (person in people) {
    if (person.age &lt; 30) result.add(person)
}
println(result) // [Person(name=Alice, age=29)]</code></pre><ul>
<li>코틀린의 filter 함수는 인라인 함수다. 따라서 filter 함수의 바이트코드는 그 함수에 전달된 람다 본문의 바이트코드와 함께 filter를 호출한 위치에 들어간다.</li>
</ul>
<ul>
<li>filter와 map을 연쇄해서 사용하기<pre><code class="language-kotlin">println(people.filter { it.age &gt; 30 }
            .map(Person::name)) // [Bob] </code></pre>
</li>
<li>이 예제는 람다 식과 멤버 참조를 사용한다. filter와 map은 인라인 함수다. 따라서 그 두 함수의 본문은 인라이닝되며, 추가 객체나 클래스 생성은 없다.</li>
<li><strong>문제점</strong> : filter 함수에서 만들어진 코드는 원소를 그 중간 리스트에 추가하고, map 함수에서 만들어진 코드는 그 중간 리스트를 읽어서 사용한다.</li>
<li><strong>해결책</strong> : asSequence를 통해 리스트 대신 시퀀스를 사용하면 중간 리스트로 인한 부가 비용은 줄어든다.</li>
</ul>
<ul>
<li>각 중간 시퀀스는 람다를 필드에 저장하는 객체로 표현되며, 최종 연산은 중간 시퀀스에 있는 여러 람다를 연쇄 호출한다.</li>
<li>따라서 시퀀스는 람다를 저장해야 하므로 람다를 인라인하지 않는다.</li>
</ul>
<blockquote>
<p>지연 계산을 통해 성능을 향상시키려는 이유로 모든 컬렉션 연산에 asSequence를 붙여서는 안 된다.</p>
</blockquote>
<ul>
<li>시퀀스 연산에서는 람다가 인라이닝되지 않기 때문에 크기가 작은 컬렉션은 오히려 일반 컬렉션 연산이 더 성능이 나을 수도 있다.</li>
<li><span style='background-color: #fff5b1'/>시퀀스를 통해 성능을 향상시킬 수 있는 경우는 컬렉션 크기가 큰 경우뿐이다.</li>
</ul>
<h3 id="함수를-인라인으로-선언해야-하는-경우">함수를 인라인으로 선언해야 하는 경우</h3>
<blockquote>
<p>inline 키워드는 람다를 인자로 받는 함수만 성능이 좋아질 가능성이 높다. 다른 경우에는 주의 깊게 성능을 측정하고 조사해봐야 한다.</p>
</blockquote>
<ul>
<li><strong>WHY)</strong> 일반 함수 호출의 경우 JVM은 이미 강력하게 인라이닝을 지원한다. JVM은 코드 실행을 분석해서 가장 이익이 되는 방향으로 호출을 인라이닝한다.</li>
<li>이런 과정은 바이트코드를 실제 기계어 코드로 번역하는 과정(JIT)에서 일어난다.</li>
<li>이런 JVM의 최적화를 활용한다면 바이트코드에서는 각 함수 구현이 정확히 한 번만 있으면 되고, 그 함수를 호출하는 부분에서 따로 함수 코드를 중복할 필요가 없다.</li>
<li><span style='background-color: #fff5b1'/>반면 코틀린 인라인 함수는 바이트코드에서 각 함수 호출 지점을 함수 본문으로 대치하기 때문에 코드 중복이 생긴다.</li>
<li>게다가 함수를 직접 호출하면 <a href="https://ccomccomhan.tistory.com/190">스택 트레이스</a>가 더 깔끔해진다.</li>
</ul>
<blockquote>
<p>✅ <strong>스택 트레이스</strong> : 프로그램의 실행 과정에서 호출된 메서드들의 순서와 위치 정보를 나타내는 것</p>
</blockquote>
<h4 id="반면-람다를-인자로-받는-함수를-인라이닝하면-이익이-더-많다">반면 람다를 인자로 받는 함수를 인라이닝하면 이익이 더 많다.</h4>
<ol>
<li>인라이닝을 통해 없앨 수 있는 부가 비용이 상당하다.<ul>
<li>함수 호출 비용을 줄일 수 있을 뿐 아니라 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체를 만들 필요도 없어진다.</li>
</ul>
</li>
<li>현재의 JVM은 함수 호출과 람다를 인라이닝해 줄 정도로 똑똑하지는 못하다.</li>
<li>인라이닝을 사용하면 일반 람다에서는 사용할 수 없는 몇 가지 기능을 사용할 수 있다. (non-local 반환)</li>
</ol>
<h4 id="하지만-inline-변경자를-함수에-붙일-때는-코드-크기에-주의를-기울여야-한다">하지만 inline 변경자를 함수에 붙일 때는 코드 크기에 주의를 기울여야 한다.</h4>
<ul>
<li>인라이닝하는 함수가 큰 경우 함수의 본문에 해당하는 바이트코드를 모든 호출 지점에 복사해 넣고 나면 바이트코드가 전체적으로 아주 커질 수 있다.<ul>
<li><strong>HOW)</strong> 람다 인자와 무관한 코드를 별도의 비인라인 함수로 빼낼 수도 있다.</li>
</ul>
</li>
<li>코틀린 표준 라이브러리가 제공하는 inline 함수를 보면 모두 크기가 아주 작다는 사실을 알 수 있다.</li>
</ul>
<h3 id="자원-관리를-위해-인라인된-람다-사용">자원 관리를 위해 인라인된 람다 사용</h3>
<blockquote>
<p>💡 <strong>자원 관리</strong> : 람다로 중복을 없앨 수 있는 패턴으로, 어떤 작업을 하기 전에 자원을 획득하고 작업을 마친 후 자원을 해제하는 패턴</p>
</blockquote>
<blockquote>
<p>✅ <strong>자원</strong> : 컴퓨팅 환경에서 사용되는 파일, 락, 데이터베이스 트랙잭션 등 모든 유한한 시스템 요소들</p>
</blockquote>
<ul>
<li>자원 관리 패턴을 만들 때 보통 try/finally문을 사용하되 try 블록을 시작하기 직전에 자원을 획득하고 finally 블록에서 자원을 해제하는 방법을 사용한다.</li>
<li>락 객체를 인자로 취하는 자바의 synchronized 함수와 같은 기능을 제공하는 코틀린 라이브러리 withLock 함수가 있다.<pre><code class="language-kotlin">val l: Lock = ...
// 락을 잠근 다음에 주어진 동작을 수행한다.
l.withLock {
  // 락에 의해 보호되는 자원을 사용한다.
}</code></pre>
</li>
<li>withLock 함수 정의<pre><code class="language-kotlin">// 락을 획득한 후 작업하는 과정을 별도의 함수로 분리한다.
fun&lt;T&gt; Lock.withLock(action: () -&gt; T): T {
  lock()
  try {
      return action()
  } finally {
      unlock()
  }
} </code></pre>
</li>
<li>try-with-resource : 파일의 각 줄을 읽는 자바 메소드<pre><code class="language-java">static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br =
          new BufferedReader(new FileReader(path))) {
      return br.readLine();
  }
}</code></pre>
</li>
<li>코틀린에서는 함수 타입의 값을 파라미터로 받는 함수(람다를 인자로 받는 함수)를 통해 매끄럽게 이를 처리할 수 있으므로, try-with-resource와 같은 기능을 언어 구성 요소로 제공하지 않는다.</li>
<li>대신 자바 try-with-resource와 같은 기능을 제공하는 <code>use 함수</code>를 자원 관리에 활용할 수 있다.<pre><code class="language-kotlin">fun readFirstLineFromFile(path: String): String {
  // BufferedReader 객체를 만들고 &quot;use&quot; 함수를 호출하면서 파일에 대한 연산을 실행할 람다를 넘긴다.
  BufferedReader(FileReader(path)).use { br -&gt; 
      // 자원(파일)에서 맨 처음 가져온 한 줄을 람다가 아닌 readFirstLineFromFile에서 반환한다.
      return br.readLine()
  }
}</code></pre>
</li>
<li>use 함수는 닫을 수 있는(closeable) 자원에 대한 확장 함수며, 람다를 인자로 받는다.</li>
</ul>
<blockquote>
<p>✅ <strong>닫을 수 있는 자원?</strong> : 시스템 리소스를 사용한 후에 반드시 해제해야 하는 객체로, 주로 I/O 관련 작업에 사용되는 파일, 네트워크 연결, 데이터베이스 연결 등이 있다.</p>
</blockquote>
<ul>
<li>use는 람다를 호출한 다음에 자원을 닫아준다. 이때 람다가 정상 종료한 경우는 물론 람다 안에서 예외가 발생한 경우에도 자원을 확실히 닫는다. 물론 use 함수도 인라인 함수다.</li>
</ul>
<h2 id="고차-함수-안에서-흐름-제어">고차 함수 안에서 흐름 제어</h2>
<h3 id="람다-안의-return문-람다를-둘러싼-함수로부터-반환">람다 안의 return문: 람다를 둘러싼 함수로부터 반환</h3>
<ul>
<li>일반 루프 안에서 return 사용하기<pre><code class="language-kotlin">data class Person(val name: String, val age: Int)
</code></pre>
</li>
</ul>
<p>val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))</p>
<p>fun lookForAlice(people: List<Person>) {
    for (person in people) {
        if (person.name == &quot;Alice&quot;) {
            println(&quot;Found!&quot;)
            return
        }
    }
    // &quot;people&quot; 안에 엘리스가 없다면 이 줄이 출력된다.
    println(&quot;Alice is not found&quot;)
}</p>
<p>lookForAlice(people) // Found!</p>
<pre><code>- forEach에 전달된 람다에서 return 사용하기
```kotlin
fun lookForAlice(people: List&lt;Person&gt;) {
    people.forEach {
        if (it.name == &quot;Alice&quot;) {
            println(&quot;Found!&quot;)
            return
        }
    }
    println(&quot;Alice is not found&quot;)
} </code></pre><ul>
<li>람다 안에서 return을 사용하면 람다로부터만 반환되는게 아니라 그 람다를 호출하는 함수가 실행을 끝내고 반환한다.</li>
<li>이렇게 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return문을 <code>넌로컬(non-local) return</code>이라 부른다.</li>
<li><span style='background-color: #fff5b1'/>return이 바깥쪽 함수를 반환시킬 수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우뿐이다.</li>
<li>forEach는 인라인 함수이므로 람다 본문과 함께 인라이닝된다. 따라서 return 식이 바깥쪽 함수(lookForAlice)를 반환시키도록 쉽게 컴파일할 수 있다.</li>
<li>인라이닝되지 않는 함수는 람다를 변수에 저장할 수 있고, 바깥쪽 함수로부터 반환된 뒤에 저장해 둔 람다가 호출될 수도 있다.</li>
</ul>
<h3 id="람다로부터-반환-레이블을-사용한-return">람다로부터 반환: 레이블을 사용한 return</h3>
<ul>
<li>로컬 return은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어간다.</li>
<li>로컬 return과 넌로컬 return을 구분하기 위해 레이블을 사용해야 한다. return으로 실행을 끝내고 싶은 람다 식 앞에 레이블을 붙이고, return 키워드 뒤에 그 레이블을 추가하면 된다.<pre><code class="language-kotlin">// 레이블을 통해 로컬 리턴 사용하기
fun lookForAlice(people: List&lt;Person&gt;) {
  // 람다에 레이블을 붙이거나 return 뒤에 레이블을 붙이기 위해 @ 사용하기
  people.forEach label@{
      // return@label은 앞에서 정의한 레이블을 참조한다.
      if (it.name == &quot;Alice&quot;) return@label
  }
  println(&quot;Alice might be somewhere&quot;)
}
</code></pre>
</li>
</ul>
<p>lookForAlice(people) // Alice might be somewhere</p>
<pre><code>- 람다에 레이블을 붙여서 사용하는 대신 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 된다.
```kotlin
// 함수 이름을 return 레이블로 사용하기
fun lookForAlice(people: List&lt;Person&gt;) {
    people.forEach {
        if (it.name == &quot;Alice&quot;) return@forEach
    }
    println(&quot;Alice might be somewhere&quot;)
} </code></pre><blockquote>
<p>람다 식의 레이블을 명시하면 함수 이름을 레이블로 사용할 수 없다는점에 유의하라. 람다식에는 레이블이 2개 이상 붙을 수 없다.</p>
</blockquote>
<h4 id="레이블이-붙은-this-식">레이블이 붙은 this 식</h4>
<ul>
<li><p>수신 객체 지정 람다의 본문에서는 this 참조를 사용해 묵시적인 컨텍스트 객체를 가리킬 수 있다.</p>
</li>
<li><p>수신 객체 지정 람다 앞에 레이블을 붙인 경우 this 뒤에 그 레이블을 붙여서 묵시적인 컨텍스트 객체를 지정할 수 있다.</p>
<pre><code class="language-kotlin">println(StringBuilder().apply sb@ {
  listOf(1, 2, 3).apply {
      this@sb.append(this.toString())
  }
}) // [1, 2, 3]</code></pre>
</li>
<li><p>하지만 넌로컬 반환문은 장황하고, 람다 안의 여러 위치에 return 식이 들어가야 하는 경우 사용하기 불편하다.</p>
</li>
</ul>
<h3 id="무명-함수-기본적으로-로컬-return">무명 함수: 기본적으로 로컬 return</h3>
<ul>
<li>무명 함수 안에서 return 사용하기<pre><code class="language-kotlin">fun lookForAlice(people: List&lt;Person&gt;) {
  // 람다 식 대신 무명 함수를 사용한다.
  people.forEach(fun(person) {
      // &quot;return&quot;은 가장 가까운 함수를 가리키는데 이 위치에서 가장 가까운 함수는 무명 함수다.
      if (person.name == &quot;Alice&quot;) return
      println(&quot;${person.name} is not Alice&quot;)
  })
}
</code></pre>
</li>
</ul>
<p>lookForAlice(people) // Bob is not Alice</p>
<pre><code>- 무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다.
```kotlin
// filter에 무명 함수 넘기기
people.filter(fun (person) : Boolean) {
    return person.age &lt; 30
}</code></pre><ul>
<li>블록이 본문인 무명 함수는 반환 타입을 명시해야 하지만, 식을 본문으로 하는 무명 함수의 반환 타입은 생략할 수 있다.<pre><code class="language-kotlin">people.filter(fun (person) = person.age &lt; 30) </code></pre>
</li>
<li>무명 함수 안에서 레이블이 붙지 않은 return 식은 무명 함수 자체를 반환시킬 뿐 무명 함수를 둘러싼 다른 함수를 반환시키지 않는다.</li>
</ul>
<h4 id="정리">정리</h4>
<ul>
<li>return은 fun 키워드를 사용해 정의된 가장 안쪽 함수를 반환시킨다.</li>
<li>람다식은 fun을 사용해 정의되지 않으므로 람다 본문의 return은 람다 밖에 함수를 반환시킨다.</li>
<li>무명 함수는 fun을 사용해 정의되므로 그 함수 자신이 바로 가장 안쪽에 있는 fun으로 정의된 함수다. 따라서 무명 함수 본문의 return은 그 무명 함수를 반환시키고, 무명 함수 밖의 다른 함수를 반환시키지 못한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 7장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-7%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-7%EC%9E%A5</guid>
            <pubDate>Thu, 16 Nov 2023 01:41:50 GMT</pubDate>
            <description><![CDATA[<h1 id="연산자-오버로딩과-기타-관례">연산자 오버로딩과 기타 관례</h1>
<blockquote>
<ul>
<li>연산자 오버로딩</li>
<li>관례: 여러 연산을 지원하기 위해 특별한 이름이 붙은 메소드</li>
<li>위임 프로퍼티</li>
</ul>
</blockquote>
<hr>
<h2 id="산술-연산자-오버로딩">산술 연산자 오버로딩</h2>
<h3 id="이항-산술-연산-오버로딩">이항 산술 연산 오버로딩</h3>
<pre><code class="language-kotlin">// plus 연산자 구현하기
data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

val p1 = Point(10, 20)
val p2 = Point(30, 40)
println(p1 + p2) // Point(x=40, y=60)</code></pre>
<ul>
<li>연산자를 오버로딩하는 함수 앞에는 꼭 operator가 있어야 한다.</li>
<li><span style='background-color: #fff5b1'/>operator 키워드를 붙임으로써 어떤 함수가 관례를 따르는 함수임을 명확히 할 수 있다.<pre><code class="language-kotlin">// 연산자를 확장 함수로 정의하기
operator fun Point.plus(other: Point): Point {
  return Point(x + other.x, y + other.y)
}</code></pre>
</li>
</ul>
<blockquote>
<p>외부 함수의 클래스에 대한 연산자를 정의할 때는 관례를 따르는 이름의 확장 함수로 구현하는 게 일반적인 패턴이다.</p>
</blockquote>
<ul>
<li>코틀린에서는 프로그래머가 직접 연산자를 만들어 사용할 수 없고 언어에서 미리 정해둔 연산자만 오버로딩할 수 있으며, 관례에 따르기 위해 클래스에서 정의해야 하는 이름이 연산자별로 정해져 있다.</li>
</ul>
<h4 id="오버로딩-가능한-이항-산술-연산자">오버로딩 가능한 이항 산술 연산자</h4>
<table>
    <tr>
        <th>식</th>
        <th>함수 이름</th>
    </tr>
    <tr>
        <td>a * b</td>
        <td>times</td>
    </tr>
    <tr>
        <td>a / b</td>
        <td>div</td>
    </tr>
    <tr>
        <td>a % b</td>
        <td>mod(1.1부터 rem)</td>
    </tr>
    <tr>
        <td>a + b</td>
        <td>plus</td>
    </tr>
    <tr>
        <td>a - b</td>
        <td>minus</td>
    </tr>
</table>

<ul>
<li>연산자를 정의할 때 두 피 연산자(연산자 함수의 두 파라미터)가 같은 타입일 필요는 없다.<pre><code class="language-kotlin">// 두 피연산자의 타입이 다른 연산자 정의하기
operator fun Point.times(scale: Double): Point {
  return Point((x * scale).toInt(), (y * scale).toInt())
}
</code></pre>
</li>
</ul>
<p>val p = Point(10, 20)
println(p * 1.5) // Point(x=15, y=30)</p>
<pre><code>
&gt; 코틀린 연산자가 자동으로 교환 법칙(a op b == b op a인 설정)을 지원하지는 않음에 유의하라.

```kotlin
operator fun Double.times(point: Point): Point {
    return Point((this * point.x).toInt(), (this * point.y).toInt())
}

fun main() {
    val p = Point(10, 20)
    println(1.5 * p) // Point(x=15, y=30)
}</code></pre><ul>
<li>연산자 함수의 반환 타입이 꼭 두 피연산자 중 하나와 일치해야만 하는 것도 아니다.<pre><code class="language-kotlin">// 결과 타입이 피연산자 타입과 다른 연산자 정의하기
operator fun Char.times(count: Int): String {
  return toString().repeat(count)
}
</code></pre>
</li>
</ul>
<p>println(&#39;a&#39; * 3) // aaa</p>
<pre><code>- 일반 함수와 마찬가지로 operator 함수도 오버로딩할 수 있다.

&gt; **비트 연산자에 대해 특별한 연산자 함수를 사용하지 않는다.**
&gt; 
&gt; 코틀린은 표준 숫자 타입에 대해 비트 연산자를 정의하지 않는다. 따라서 커스텀 타입에서 비트 연산자를 정의할 수 없다. 대신에 중의 연산자 표기법을 지원하는 일반 함수(and, shl, xor 등)를 사용해 비트 연산을 수행한다. 커스텀 타입에서도 그와 비슷한 함수를 정의해 사용할 수 있다.

### 복합 대입 연산자 오버로딩
- 연산자를 오버로딩하면 + 연산자뿐 아니라 복합 대입 연산자(+=, -= 등)도 자동으로 함께 지원한다.
```kotlin
var point = Point(1, 2)
point += Point(3, 4)
println(point) // Point(x=4, y=6)

val numbers = ArrayList&lt;Int&gt;()
numbers += 42
println(numbers[0]) // 42</code></pre><ul>
<li>반환 타입이 Unit인 plusAssign 함수를 정의하면 코틀린은 += 연산자에 그 함수를 사용한다.<pre><code class="language-kotlin">operator fun &lt;T&gt; MutableCollection&lt;T&gt;.plusAssign(element: T) {
  this.add(element)
}</code></pre>
</li>
</ul>
<blockquote>
<p>plus와 plusAssign 연산을 동시에 정의하지 말라.</p>
</blockquote>
<ul>
<li>코틀린 표준 라이브러리는 컬렉션에 대해 두 가지 접근 방법을 함께 제공한다. </li>
<li>+와 -는 항상 새로운 컬렉션을 반환하며, +=와 -= 연산자는 항상 변경 가능한 컬렉션에 적용해 메모리에 있는 객체 상태를 변화시킨다.<ul>
<li>또한 읽기 전용 컬렉션에서 +=와 -=는 변경을 적용한 복사본을 반환한다(따라서 var로 선언한 변수가 가리키는 읽기 전용 컬렉션에만 +=와 -=를 적용할 수 있다).</li>
</ul>
</li>
<li>이런 연산자의 피연산자로는 개별 원소를 사용하거나 원소 타입이 일치하는 다른 컬렉션을 사용할 수 있다.<pre><code class="language-kotlin">val list = arrayListOf(1, 2)
// +=는 &quot;list&quot;를 변경한다.
list += 3
// +는 두 리스트의 모든 원소를 포함하는 새로운 리스트를 반환한다.
val newList = list + listOf(4, 5)
println(list) // [1, 2, 3]
println(newList) // [1, 2, 3, 4, 5]</code></pre>
</li>
</ul>
<h3 id="단항-연산자-오버로딩">단항 연산자 오버로딩</h3>
<pre><code class="language-kotlin">// 단항 minus(음수) 함수는 파라미터가 없다.
operator fun Point.unaryMinus(): Point {
    // 좌표에서 각 성분의 음수를 취한 새 점을 반환한다.
    return Point(-x, -y)
}

val p = Point(10, 20)
println(-p) // Point(x=-10, y=-20)</code></pre>
<ul>
<li>단항 연산자를 오버로딩하기 위해 사용하는 함수는 인자를 취하지 않는다.<h4 id="오버로딩할-수-있는-단항-산술-연산자">오버로딩할 수 있는 단항 산술 연산자</h4>
<table>
  <tr>
      <th>식</th>
      <th>함수 이름</th>
  </tr>
  <tr>
      <td>+a</td>
      <td>unaryPlus</td>
  </tr>
  <tr>
      <td>-a</td>
      <td>unaryMinus</td>
  </tr>
  <tr>
      <td>!a</td>
      <td>not</td>
  </tr>
  <tr>
      <td>++a, a++</td>
      <td>inc</td>
  </tr>
  <tr>
      <td>--a, a--</td>
      <td>dec</td>
  </tr>
</table>

</li>
</ul>
<pre><code class="language-kotlin">operator fun BigDecimal.inc() = this + BigDecimal.ONE

var bd = BigDecimal.ZERO
// 후위 증가 연산자는 println이 실행된 다음에 값을 증가시킨다.
println(bd++) // 0
// 전위 증가 연산자는 println이 실행되기 전에 값을 증가시킨다.
println(++bd) // 2</code></pre>
<h2 id="비교-연산자-오버로딩">비교 연산자 오버로딩</h2>
<h3 id="동등성-연산자-equals">동등성 연산자: equals</h3>
<ul>
<li>==와 !=는 내부에서 인자가 널인지 검사하므로 다른 연산과 달리 널이 될 수 있는 값에도 적용할 수 있다.<ul>
<li>a == b라는 비교를 처리할 때 코틀린은 a가 널인지 판단해서 널이 아닌 경우에만 a.equals(b)를 호출한다.</li>
<li><code>a == b</code> ➡️ <code>a?.equals(b) ?: (b == null)</code><pre><code class="language-kotlin">class Point(val x: Int, val y: Int) {
// Any에 정의된 메소드를 오버라이딩한다.
override fun equals(obj: Any?): Boolean {
    // 최적화: 파라미터가 &quot;this&quot;와 같은 객체인지 살펴본다.
    if (obj === this) return true
    // 파라미터 타입을 검사한다.
    if (obj !is Point) return false
    // Point로 스마트 캐스트해서 x와 y 프로퍼티에 접근한다.
    return obj.x == x &amp;&amp; obj.y == y
}
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>println(Point(10, 20) == Point(10, 20)) // true
println(Point(10, 20) != Point(5, 5)) // true
println(null == Point(1, 2)) // false</p>
<pre><code>- 식별자 비교(identity equals) 연산자(===)를 사용해 equals의 파라미터가 수신 객체와 같은지 살펴본다.
  - 즉, 자신의 두 피연산자가 서로 같은 객체를 가리키는지(원시 타입인 경우 두 값이 같은지) 비교한다.
- 다른 연산자 오버로딩 관례와 달리 equals는 Any에 정의된 메소드이므로 override가 필요하다(동등성 비교를 모든 코틀린 객체에 대해 적용할 수 있다).
  - Any의 equals에는 operator가 붙어있지만 그 메소드를 오버라이드하는 (하위 클래스) 메소드 앞에는 operator 변경자를 붙이지 않아도 자동으로 상위 클래스의 operator 지정이 적용된다.
- Any에서 상속 받은 equals가 확장 함수보다 우선순위가 높기 때문에 equals를 확장 함수로 정의할 수는 없다.

### 순서 연산자: compareTo
- 자바에서 사용하는 Comparable 인터페이스에 들어있는 compareTo 메소드는 한 객체와 다른 객체의 크기를 비교해 정수로 나타내준다.
- 코틀린도 똑같은 Comparable 인터페이스를 지원한다. 게다가 코틀린은 Comparable 인터페이스 안에 있는 compareTo 메소드를 호출하는 관례를 제공한다.
  - 따라서 비교 연산자(&lt;, &gt;, &lt;=, &gt;=)는 compareTo 호출로 컴파일된다. compareTo가 반환하는 값은 Int다.
  - `a &gt;= b` ➡️ `a.compareTo(b) &gt;= 0`
```kotlin
class Person(
    val firstName: String, val lastName: String
): Comparable&lt;Person&gt; {
    override fun compareTo(other: Person): Int {
        // 인자로 받은 함수를 차례로 호출하면서 값을 비교한다.
        return compareValuesBy(this, other,
            Person::lastName, Person::firstName)    
    }
}

val p1 = Person(&quot;Alice&quot;, &quot;Smith&quot;)
val p2 = Person(&quot;Bob&quot;, &quot;Johnson&quot;)
println(p1 &lt; p2) // false</code></pre><ul>
<li>equals와 마찬가지로 Comparable의 compareTo에도 operator 변경자가 붙어있으므로 하위 클래스의 오버라이딩 함수에 operator를 붙일 필요가 없다.</li>
<li>compareValuesBy 함수 : 두 객체와 여러 비교 함수를 인자로 받는다. 첫 번째 비교 함수에 두 객체를 넘겨서 두 객체가 같지 않다는 결과(0)가 나오면 두 번째 비교 함수를 통해 두 객체를 비교한다.<ul>
<li>두 객체의 대소를 알려주는 0이 아닌 값이 처음 나올 때까지 인자로 받은 함수를 차례로 호출해 두 값을 비교하며, 모든 함수가 0을 반환하면 0을 반환한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>그렇지만 필드를 직접 비교하면 코드는 조금 더 복잡해지지만 비교 속도는 훨씬 더 빨라진다. 언제나 그렇듯이 처음에는 성능에 신경 쓰지 말고 이해하기 쉽고 간결하게 코드를 작성하고, 나중에 그 코드가 자주 호출됨에 따라 성능이 문제가 되면 성능을 개선하라</p>
</blockquote>
<pre><code class="language-kotlin">class Person(
    val firstName: String, val lastName: String
): Comparable&lt;Person&gt; {
    override fun compareTo(other: Person): Int {
        // lastName 비교
        val lastNameComparison = this.lastName.compareTo(other.lastName)
        if (lastNameComparison != 0) {
            return lastNameComparison
        }

        // lastName이 같으면 firstName 비교
        return this.firstName.compareTo(other.firstName)
    }
}

fun main() {
    val p1 = Person(&quot;Alice&quot;, &quot;Smith&quot;)
    val p2 = Person(&quot;Bob&quot;, &quot;Johnson&quot;)
    println(p1 &lt; p2) // false
}</code></pre>
<h2 id="컬렉션과-범위에-대해-쓸-수-있는-관례">컬렉션과 범위에 대해 쓸 수 있는 관례</h2>
<h3 id="인덱스로-원소에-접근-get과-set">인덱스로 원소에 접근: get과 set</h3>
<ul>
<li>인덱스 연산자를 사용해 원소를 읽는 연산은 get 연산자 메소드로 변환되고, 원소를 쓰는 연산은 set 연산자 메소드로 변환된다.<pre><code class="language-kotlin">// get 관례 구현하기
operator fun Point.get(index: Int): Int {
  return when(index) {
      0 -&gt; x
      1 -&gt; y
      else -&gt;
          throw IndexOutOfBoundsException(&quot;Invalid coordinate $index&quot;)
  }
}
</code></pre>
</li>
</ul>
<p>val p = Point(10, 20)
println(p[1]) // 20</p>
<pre><code>- 인덱스에 해당하는 컬렉션 원소를 쓰고 싶을 때는 set이라는 이름의 함수를 정의하면 된다.
  - Point 클래스는 불변 클래스이므로 set이 의미가 없다. 대신 변경 가능한 점을 표현하는 다른 클래스를 만들어서 예제로 사용하자.
```kotlin
data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
    when(index) {
        0 -&gt; x = value
        1 -&gt; y = value
        else -&gt;
            throw IndexOutOfBoundsException(&quot;Invalid coordinate $index&quot;)
    }
}

val p = MutablePoint(10, 20)
p[1] = 42
println(p) // MutablePoint(x=10, y=42)</code></pre><h3 id="in-관례">in 관례</h3>
<ul>
<li>in은 객체가 컬렉션에 들어있는지 검사(멤버십 검사)한다. 그런 경우 in 연산자와 대응하는 함수는 contains다.<pre><code class="language-kotlin">data class Rectangle(val upperLeft: Point, val lowerRight: Point)
</code></pre>
</li>
</ul>
<p>operator fun Rectangle.contains(p: Point): Boolean {
    // 범위를 만들고 좌표가 그 범위 안에 있는지 검사한다.
    return p.x in upperLeft.x unitl lowerRight.x &amp;&amp;
            p.y in upperLeft.y until lowerRight.y
}</p>
<p>val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect) // true
println(Point(5, 5) in rect) // false</p>
<pre><code>- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;in의 우항에 있는 객체는 contains 메소드의 수신 객체가 되고, in의 좌항에 있는 객체는 contains 메소드에 인자로 전달된다.

### rangeTo 관례
- rangeTo 함수는 범위를 반환한다. 이 연산자를 아무 클래스에나 정의할 수 있다.
- 하지만 어떤 클래스가 Comparable 인터페이스를 구현하면 rangeTo를 정의할 필요가 없다.
  - rangeTo 함수는 Comparable에 대한 확장 함수다.
```kotlin
operator fun &lt;T: Comparable&lt;T&gt;&gt; T.rangeTo(that: T): ClosedRange&lt;T&gt;</code></pre><ul>
<li>이 함수는 범위를 반환하며, 어떤 원소가 그 범위 안에 들어있는지 in을 통해 검사할 수 있다.<pre><code class="language-kotlin">// 날짜의 범위 다루기
val now = LocalDate.now()
</code></pre>
</li>
</ul>
<p>// val vacation = now.rangeTo(now.plusDays(10))
val vacation = now..now.plusDays(10)
println(now.plusWeeks(1) in vacation) // true</p>
<pre><code>- rangeTo 연산자는 다른 산술 연산자보다 우선순위가 낮다. 하지만 혼동을 피하기 위해 괄호로 인자를 감싸주면 더 좋다.
```kotlin
val n = 9
println(0..(n + 1)) // 0..10</code></pre><ul>
<li>또한 <code>0..n.forEach {}</code>와 같은 식은 컴파일할 수 없음에 유의하라. 범위 연산자는 우선 순위가 낮아서 범위의 메소드를 호출하려면 범위를 괄호로 둘러싸야 한다.<pre><code class="language-kotlin">(0..n).forEach { println(it) } // 0123456789 </code></pre>
</li>
</ul>
<h3 id="for-루프를-위한-iterator-관례">for 루프를 위한 iterator 관례</h3>
<ul>
<li><p><code>for (x in list) { ... }</code>와 같은 문장은 list.iterator()를 호출해서 이터레이터를 얻은 다음, 자바와 마찬가지로 그 이터레이터에 대해 hasNext와 next 호출을 반복하는 식으로 변환된다.</p>
<pre><code class="language-kotlin">fun main() {
val list = listOf(1, 2, 3, 4, 5)

  val iterator = list.iterator()
  while (iterator.hasNext()) {
      val x = iterator.next()
      println(x)
  }
} </code></pre>
</li>
<li><p>하지만 코틀린에서는 이 또한 관례이므로 iterator 메소드를 확장 함수로 정의할 수 있다.</p>
<ul>
<li>이런 성질로 인해 일반 자바 문자열에 대한 for 루프가 가능하다.</li>
</ul>
</li>
<li><p>코틀린 표준 라이브러리는 String의 상위 클래스인 CharSequence에 대한 iterator 확장 함수를 제공한다.</p>
<pre><code class="language-kotlin">// CharaIterator 라이브러리 : 문자열을 이터레이션할 수 있게 해준다.
operator fun CharSequence.iterator(): CharaIterator
</code></pre>
</li>
</ul>
<p>for (c in &quot;abc&quot;) {}</p>
<pre><code>```kotlin
// 날짜 범위에 대한 이터레이터 구현하기
operator fun ClosedRange&lt;LocalDate&gt;.iterator(): Iterator&lt;LocalDate&gt; =
    // 이 객체는 LocalDate 원소에 대한 Iterator를 구현한다.
    object : Iterator&lt;LocalDate&gt; {
        var current = start

        // compareTo 관례를 사용해 날짜를 비교한다.
        override fun hasNext() = current &lt;= endInclusive

        // 현재 날짜를 저장한 다음에 날짜를 변경한다. 그 후 저장해둔 날짜를 반환한다.
        override fun next() = current.apply { 
            current = plusDays(1) // 현재 날짜를 1일 뒤로 변경한다.
        }
    }

val newYear = LocalDate.ofYearDay(2017, 1)
val daysOff = newYear.minusDays(1)..newYear
// daysOff에 대응하는 iterator 함수가 있으면 daysOff에 대해 이터레이션한다.
for (dayOff in daysOff) { println(dayOff) }
// 2016-12-31
// 2017-01-01</code></pre><ul>
<li>ClosedRange<LocalData>에 대한 확장 함수 iterator를 정의했기 때문에 LocalDate의 범위 객체를 for 루프에 사용할 수 있다.</li>
</ul>
<h2 id="구조-분해-선언과-component-함수">구조 분해 선언과 component 함수</h2>
<ul>
<li>구조 분해를 사용하면 복합적인 값을 분해해서 여러 다른 변수를 한꺼번에 초기화할 수 있다.<pre><code class="language-kotlin">val p = Point(10, 20)
// x와 y 변수를 선언한 다음에 p의 여러 컴포넌트로 초기화한다.
val (x, y) = p
println(x) // 10
println(y) // 20</code></pre>
</li>
<li>구조 분해 선언은 일반 변수 선언과 비슷해 보인다. 다만 <code>=</code>의 좌변에 여러 변수를 괄호로 묶었다는 점이 다르다.</li>
<li>내부에서 구조 분해 선언은 다시 관례를 사용한다. 구조 분해 선언의 각 변수를 초기화하기 위해 componentN이라는 함수를 호출한다.<ul>
<li>N : 구조 분해 선언에 있는 변수 위치에 따라 붙는 번호<pre><code class="language-kotlin">class Point(val x: Int, val y: Int) {
operator fun component1() = x
operator fun component2() = y
}</code></pre>
</li>
</ul>
</li>
<li>data 클래스의 주 생성자에 들어있는 프로퍼티에 대해서는 컴파일러가 자동으로 <a href="https://velog.io/@dev-baik/Data-Class#componentn">componentN</a> 함수를 만들어준다.<pre><code class="language-kotlin">// 값을 저장하기 위한 데이터 클래스 선언한다.
data class NameComponents(val name: String, val extension: String)
</code></pre>
</li>
</ul>
<p>fun splitFilename(fullName: String): NameComponents {
    // val result = fullName.split(&#39;.&#39;, limit = 2)
    // return NameComponents(result[0], result[1])</p>
<pre><code>// 컬렉션에 대해 구조 분해 선언 사용하기
val (name, extension) = fullName.split(&#39;.&#39;, limit=2)
return NameComponents(name, extension)</code></pre><p>}</p>
<p>// 구조 분해 선언을 사용해 여러 값 반환하기
val (name, next) = splitFilename(&quot;example.kt&quot;)
println(name) // example
println(ext) // kt</p>
<pre><code>- 표준 라이브러리의 Pair나 Triple 클래스를 사용하면 함수에서 여러 값을 더 간단하게 반환할 수 있다.
- Pair와 Triple은 그 안에 담겨있는 원소의 의미를 말해주지 않으므로 경우에 따라 가독성이 떨어질 수 있는 반면, 직접 클래스를 작성할 필요가 없으므로 코드는 더 단순해진다.

### 구조 분해 선언과 루프
```kotlin
// 구조 분해 선언을 사용해 맵 이터레이션하기
fun printEntries(map: Map&lt;String, String&gt;) {
    // 루프 변수에 구조 분해 선언을 사용한다.
    for ((key, value) in map) {
        println(&quot;$key -&gt; $value&quot;)
    }
}

val map = mapOf(&quot;Oracle&quot; to &quot;Java&quot;, &quot;JetBrains&quot; to &quot;Kotlin&quot;)
printEntries(map)
// Oracle -&gt; Java
// JetBrains -&gt; Kotlin</code></pre><ul>
<li>코틀린 표준 라이브러리에는 맵에 대한 확장 함수로 iterator가 들어있다. 그 iterator는 맵 원소에 대한 이터레이터를 반환한다. 따라서 자바와 달리 코틀린에서는 맵을 직접 이터레이션할 수 있다.</li>
<li>또한 Map.Entry에 대한 확장 함수로 component1과 component2를 제공한다.<pre><code class="language-kotlin">for (entry in map.entries) {
  val key = entry.component1()
  val value = entry.component2()
  // ...
}</code></pre>
</li>
</ul>
<h2 id="프로퍼티-접근자-로직-재활용-위임-프로퍼티">프로퍼티 접근자 로직 재활용: 위임 프로퍼티</h2>
<ul>
<li>위임 프로퍼티를 사용하면 값을 뒷받침하는 필드에 단순히 저장하는 것보다 더 복잡한 방식으로 자동하는 프로퍼티를 쉽게 구현할 수 있다. 또한 그 과정에서 접근자 로직을 매번 재구현할 필요도 없다.</li>
<li>위임은 객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 그 작업을 처리하게 맡기는 디자인 패턴을 말한다.<ul>
<li>이때 작업을 처리하는 도우미 객체를 위임 객체(delegate)라고 한다.</li>
</ul>
</li>
</ul>
<h3 id="위임-프로퍼티-소개">위임 프로퍼티 소개</h3>
<pre><code class="language-kotlin">class Foo {
    var p: Type by Delegate()
}</code></pre>
<ul>
<li><p>p 프로퍼티는 접근자 로직을 다른 객체에게 위임한다. 여기서는 Delegate 클래스의 인스턴스를 위임 객체로 사용한다.</p>
</li>
<li><p>by 뒤에 있는 식을 계산해서 위임에 쓰일 객체를 얻는다.</p>
<ul>
<li>프로퍼티 위임 객체가 따라야 하는 관례를 따르는 모든 객체를 위임에 사용할 수 있다.</li>
</ul>
</li>
<li><p>컴파일러는 숨겨진 도우미 프로퍼티를 만들고 그 프로퍼티를 위임 객체의 인스턴스로 초기화한다.</p>
<ul>
<li><p>p 프로퍼티는 바로 그 위임 객체에게 자신의 작업을 위임한다.</p>
<pre><code class="language-kotlin">class Foo {
private val delegate: Delegate() // 컴파일러가 생성한 도우미 프로퍼티다.

// &quot;p&quot; 프로퍼티를 위해 컴파일러가 생성한 접근자는
// &quot;delegate&quot;의 getValue와 setValue 메소드를 호출한다.
var p: Type
  set(value: Type) = delegate.setValue(..., value)
  get() = delegate.getValue(...)
}</code></pre>
</li>
</ul>
</li>
<li><p>프로퍼티 위임 관례를 따르는 Delegate 클래스는 getValue와 setValue 메소드를 제공해야 한다(물론 변경 가능한 프로퍼티만 setValue를 사용한다).</p>
</li>
<li><p>관례를 사용하는 다른 경우와 마찬가지로 getValue와 setValue는 멤버 메소드이거나 확장 함수일 수 있다.</p>
<pre><code class="language-kotlin">class Delegate {
  // getValue는 게터를 구현하는 로직을 담는다.
  operator fun getValue(...) { ... }
  // setValue 메소드는 세터를 구현하는 로직을 담는다.
  operator fun setValue(..., value: Type) { ... }
}
</code></pre>
</li>
</ul>
<p>class Foo {
    // &quot;by&quot; 키워드는 프로퍼티와 위임 객체를 연결한다.
    var p: Type by Delegate()
}</p>
<p>val foo = Foo()
val oldValue = foo.p
foo.p = newValue</p>
<pre><code>- p의 게터나 세터는 Delegate 타입의 위임 프로퍼티 객체에 있는 메소드를 호출한다.

### 위임 프로퍼티 사용: by lazy()를 사용한 프로퍼티 초기화 선언
- 지연 초기화는 객체의 일부분을 초기화하지 않고 남겨뒀다가 실제로 그 부분의 값이 필요한 경우 초기화할 때 흔히 쓰이는 패턴이다.
  - 초기화 과정에 많이 사용하거나 객체를 사용할 때마다 꼭 초기화하지 않아도 되는 프로퍼티에 대해 지연 초기화 패턴을 사용할 수 있다.
```kotlin
class Email { /*...*/ }

fun loadEmails(person: Person): List&lt;Email&gt; {
    println(&quot;${person.name}의 이메일을 가져옴&quot;)
    return listOf(/*...*/)
}</code></pre><ul>
<li><p>지연 초기화를 뒷받침하는 프로퍼티를 통해 구현하기</p>
<pre><code class="language-kotlin">class Person(val name: String) {
  // 데이터를 저장하고 emails의 위임 객체 역할을 하는 emails 프로퍼티
  private var _emails: List&lt;Email&gt;? = null

  val emails: List&lt;Email&gt;
    get() {
        if (_emails == null) {
            // 최초 접근 시 이메일을 가져온다.
            _emails = loadEmails(this)
        }
        // 저장해둔 데이터가 있으면 그 데이터를 반환한다.
        return _emails!!
    }
}
</code></pre>
</li>
</ul>
<p>val p = Person(&quot;Alice&quot;)
p.emails
// 최초로 emails를 읽을 때 단 한번만 이메일을 가져온다.
// 결과: Load emails for Alice
p.emails</p>
<pre><code>- 뒷받침하는 프로퍼티(_emails)는 값을 저장하고, 다른 프로퍼티인 emails는 _emails라는 프로퍼티에 대한 읽기 연산을 제공한다.
- _emails는 널이 될 수 있는 타입인 반면 emails는 널이 될 수 없는 타입이므로 프로퍼티를 두 개 사용해야 한다.
- 이 구현은 스레드 안전하지 않아서 언제나 제대로 작동한다고 말할 수 없다.
- HOW) 위임 프로퍼티 사용
  - 위임 프로퍼티 : 데이터를 저장할 때 쓰이는 뒷받침하는 프로퍼티와 값이 오직 한 번만 초기화됨을 보장하는 게터 로직을 함께 캡슐화해준다.
```kotlin
// 지연 초기화를 위임 프로퍼티를 통해 구현하기
class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}</code></pre><ul>
<li>lazy 함수는 코틀린 관례에 맞는 시그니처의 getValue 메소드가 들어있는 객체를 반환한다. 따라서 lazy를 by 키워드와 함께 사용해 위임 프로퍼티를 만들 수 있다.</li>
<li>lazy 함수의 인자는 값을 초기화할 때 호출할 람다. 기본적으로 스레드 안전하다.</li>
<li>하지만 필요에 따라 동기화에 사용할 락을 lazy 함수에 전달할 수 있고, 다중 스레드 환경에서 사용하지 않을 프로퍼티를 위해 lazy 함수가 동기화를 하지 못하게 막을 수 있다.<pre><code class="language-kotlin">import kotlin.concurrent.thread
</code></pre>
</li>
</ul>
<p>class ExpensiveObject {
    init {
        println(&quot;ExpensiveObject initialized&quot;)
    }
}</p>
<p>class Example {
    // 동기화에 사용할 락을 lazy 함수에 전달
    val lazyProperty: ExpensiveObject by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        println(&quot;Initializing Lazy Property&quot;)
        ExpensiveObject()
    }</p>
<pre><code>// 다중 스레드 환경에서 사용하지 않을 프로퍼티를 위해 lazy 함수가 동기화를 하지 못하게 막기
// val lazyProperty: ExpensiveObject by lazy(LazyThreadSafetyMode.NONE) {
//    ...
// }</code></pre><p>}</p>
<p>fun main() {
    val example = Example()</p>
<pre><code>// 여러 스레드에서 동시에 접근하는 상황 시뮬레이션
repeat(5) {
    thread {
        println(&quot;Accessing Lazy Property: ${example.lazyProperty}&quot;)
    }
}

// 잠시 대기하여 모든 스레드가 실행을 마치도록 함
Thread.sleep(1000)</code></pre><p>}</p>
<pre><code>
### 위임 프로퍼티 구현
- PropertyChangeSupport 클래스 : 리스너의 목록을 관리하고 PropertyChangeEvent 이벤트가 들어오면 목록의 모든 리스너에게 이벤트를 통지한다.
- 자바 빈 클래스의 필드에 PropertyChangeSupport 인스턴스를 저장하고 프로퍼티 변경 시 인스턴스에게 처리를 위임하는 방식으로 이런 통지 기능을 주로 구현한다.

&gt; ✅ **자바 빈 클래스** : 일정한 규약을 따르는 클래스로, 기본 생성자, getter 및 setter 메서드를 포함하며, 멤버 변수는 private으로 선언되고 이에 접근하기 위한 표준 메서드를 제공한다. 이러한 클래스는 재사용 가능한 컴포넌트를 만드는데 사용된다.

```kotlin
// PropertyChangeSupport를 사용하기 위한 도우미 클래스
open class PropertyChangeAware {
    protected val changeSupport = PropertyChangeSupport(this)

    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)      
    }
}</code></pre><pre><code class="language-kotlin">// 프로퍼티 변경 통지를 직접 구현하기
class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    var age: Int = age
      set(newValue) {
          val oldValue = field
          field = newValue

          // 프로퍼티 변경을 리스너에게 통지한다.
          changeSupport.firePropertyChange(
              &quot;age&quot;, oldValue, newValue)
      }

    var salary: Int = salary
      set(newValue) {
          val oldValue = field
          field = newValue

          changeSupport.firePropertyChange(
              &quot;salary&quot;, oldValue, newValue)
      }
}

val p = Person(&quot;Dmitry&quot;, 34, 2000)
p.addPropertyChangeListener(
    PropertyChangeListener { event -&gt;
        println(&quot;Property ${event.propertyName} changed &quot; +
            &quot;from ${event.oldValue} to ${event.newValue}&quot;)
    }
)
p.age = 35 // Property age changed from 34 to 35
p.salary = 2100 // Property salary changed from 2000 to 2100</code></pre>
<ul>
<li>세터 코드를 보면 중복이 많이 보인다. 이제 프로퍼티의 값을 저장하고 필요에 따라 통지를 보내주는 클래스를 추출해보자.</li>
</ul>
<pre><code class="language-kotlin">// 도우미 클래스를 통해 프로퍼티 변경 통지 구현하기
class ObservableProperty(
  val propName: String, var propValue: Int,
  val changeSupport: PropertyChangeSupport
) {
    fun getValue(): Int = propValue

    fun setValue(newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(propName, oldValue, newValue)
    }
}

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    val _age = ObservableProperty(&quot;age&quot;, age, changeSupport)
    var age: Int
      get() = _age.getValue()
      set(value) { _age.setValue(value) }

    val _salary = ObservableProperty(&quot;salary&quot;, salary, changeSupport)
    val salary: Int
      get() = _salary.getValue()
      set(value) { _salary.setValue(value) }
}</code></pre>
<ul>
<li><p>코틀린의 위임이 실제로 작동하는 방식과 비슷하다. 프로퍼티 값을 저장하고 그 값이 바뀌면 자동으로 변경 통지를 전달해주는 클래스를 만들었고, 로직의 중복을 상당 부분 제거했다.</p>
</li>
<li><p>하지만 아직도 각각의 프로퍼티마다 ObservableProperty를 만들고 게터와 세터에서 ObservableProperty에 작업을 위임하는 준비 코드가 상당 부분 필요하다.</p>
</li>
<li><p><strong>HOW)</strong> 코틀린의 위임 프로퍼티 기능을 활용하면 이런 준비 코드를 없앨 수 있다.</p>
<pre><code class="language-kotlin">// ObservableProperty를 프로퍼티 위임에 사용할 수 있게 바꾼 모습
class ObservableProperty(
  var propValue: Int, val changeSupport: PropertyChangeSupport
) {
  operator fun getValue(p: Person, prop: KProperty&lt;*&gt;): Int = propValue

  operator fun setValue(p: Person, prop: KProperty&lt;*&gt;, newValue: Int) {
      val oldValue = propValue
      propValue = newValue
      changeSupport.firePropertyChange(prop.name, oldValue, newValue)
  }
}</code></pre>
</li>
<li><p>코틀린 관례에 사용하는 다른 함수와 마찬가지로 getValue와 setValue에도 operator 변경자가 붙는다.</p>
</li>
<li><p>getValue와 setValue는 프로퍼티가 포함된 객체(여기서는 Person 타입인 p)와 프로퍼티를 표현하는 객체를 파라미터로 받는다. 코틀린은 <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-property/">KProperty</a> 타입의 객체를 사용해 프로퍼티를 표현한다.</p>
</li>
</ul>
<table>
  <tr>
    <th>getValue</th>
    <th>setValue</th>
  </tr>
  <tr>
    <td><strong>thisRef</strong> : 위임을 사용하는 클래스와 같은 타입이거나 Any 타입이어야 한다.</td>
    <td><strong>thisRef</strong> : 위임을 사용하는 클래스와 같은 타입이거나 Any 타입이어야 한다.</td>
  </tr>
  <tr>
    <td><strong>property</strong> : Property<*>거나 Any 타입이어야 한다.</td>
    <td><strong>property</strong> : Property<*>거나 Any 타입이어야 한다.</td>
  </tr>
  <tr>
    <td></td>
    <td><strong>newValue</strong> : 위임을 사용하는 프로퍼티와 같은 타입이거나 Any 타입이어야 한다.</td>
  </tr>
</table>

<ul>
<li>KProperty 인자를 통해 프로퍼티 이름을 전달받으므로 주 생성자에서는 name 프로퍼티를 없앤다.</li>
</ul>
<blockquote>
<p>✅ <strong>KProperty</strong> : 코틀린의 리플렉션 API 중 하나로, 프로퍼티를 나타내는 역할을 한다. KProperty의 인스턴스는 <code>::</code> 연산자로 얻을 수 있습니다.</p>
<p>✅ <strong>리플렉션(Reflection)</strong> : 실행 중인 프로그램의 클래스, 메소드, 필드 등과 같은 구조를 동적으로 살펴보거나 수정할 수 있는 능력</p>
</blockquote>
<pre><code class="language-kotlin">// 위임 프로퍼티를 통해 프로퍼티 변경 통지 받기
class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}</code></pre>
<ul>
<li>by 키워드를 사용해 위임 객체를 지정하면 여러 작업을 코틀린 컴파일러가 자동으로 처리해준다.</li>
<li>by 오른쪽에 오는 객체를 위임 객체라고 부른다.</li>
<li>코틀린은 위임 객체를 감춰진 프로퍼티에 저장하고, 주 객체의 프로퍼티를 읽거나 쓸 때마다 위임 객체의 getValue와 setValue를 호출해준다.</li>
</ul>
<ul>
<li><p>관찰 가능한 프로퍼티 로직을 직접 작성하는 대신 코틀린 표준 라이브러리를 사용해도 된다.</p>
</li>
<li><p>표준 라이브러리에는 이미 ObservableProperty와 비슷한 클래스가 있다. 다만 이 표준 라이브러리의 클래스는 PropertyChangeSupport와는 연결돼 있지 않다.</p>
<ul>
<li><p>따라서 프로퍼티 값의 변경을 통지할 때 PropertyChangeSupport를 사용하는 방법을 알려주는 람다를 그 표준 라이브러리 클래스에게 넘겨야 한다.</p>
<pre><code class="language-kotlin">// Delegates.observable을 사용해 프로퍼티 변경 통지 구현하기
class Person(
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
private val observer = {
    prop: KProperty&lt;*&gt;, oldValue: Int, newValue: Int -&gt;
    changeSupport.firePropertyChange(prop.namme, oldValue, newValue)
}

var age: Int by Delegates.observable(age, observer)
var salary: Int by Delegates.observable(salary, observer)
}</code></pre>
</li>
</ul>
</li>
<li><p>by의 오른쪽에 있는 식이 꼭 새 인스턴스를 만들 필요는 없다. 함수 호출, 다른 프로퍼티, 다른 식 등이 by의 우항에 올 수 있다.</p>
<ul>
<li><span style='background-color: #fff5b1'/>다만 우항에 있는 식을 계산한 결과인 객체는 컴파일러가 호출할 수 있는 올바른 타입의 getValue와 setValue를 반드시 제공해야 한다.</li>
<li>다른 관례와 마찬가지로 getValue와 setValue 모두 객체 안에 정의된 메소드이거나 확장 함수일 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="위임-프로퍼티-컴파일-규칙">위임 프로퍼티 컴파일 규칙</h3>
<pre><code class="language-kotlin">class C {
    var prop: Type by MyDelegate()
}

val c = C()</code></pre>
<ul>
<li>컴파일러는 MyDelegate 클래스의 인스턴스를 감춰진 프로퍼티에 저장하며 그 감춰진 프로퍼티를 <code>&lt;delegate&gt;</code>라는 이름으로 부른다.</li>
<li>또한 컴파일러는 프로퍼티를 표현하기 위해 KProperty 타입의 객체를 사용한다. 이 객체를 <code>&lt;property&gt;</code>라고 부른다.<pre><code class="language-kotlin">class C {
  private val &lt;delegate&gt; = MyDelegate()
  var prop: Type
    get() = &lt;delegate&gt;.getValue(this, &lt;property&gt;)
    set(value: Type) = &lt;delegate&gt;.setValue(this, &lt;property&gt;, value)
}</code></pre>
</li>
</ul>
<h3 id="프로퍼티-값을-맵에-저장">프로퍼티 값을 맵에 저장</h3>
<ul>
<li><p>자신의 프로퍼티를 동적으로 정의할 수 있는 객체를 만들 때 위임 프로퍼티를 활용하는 경우가 자주 있다. 그런 객체를 <code>확장 가능한 객체</code>라고 부르기도 한다.</p>
<pre><code class="language-kotlin">// 값을 맵에 저장하는 프로퍼티 정의하기
class Person {
  // 추가 정보
  private val _attributes = hashMapOf&lt;String, String&gt;()

  fun setAttribute(attrName: String, value: String) {
      _attributes[attrName] = value
  }

  // 필수 정보
  val name: String
    // 수동으로 맵에서 정보를 꺼낸다.
    get() = _attributes[&quot;name&quot;]!!
}
</code></pre>
</li>
</ul>
<p>val p = Person()
val data = mapOf(&quot;name&quot; to &quot;Dmitry&quot;, &quot;company&quot; to &quot;JetBrains&quot;)
for ((attrName, value) in data)
    p.setAttribute(attrName, value)
println(p.name) // Dmitry</p>
<pre><code>- 이 코드는 추가 데이터를 저장하기 위해 일반적인 API를 사용하고(실제 프로젝트에서는 JSON 역직렬화 등의 기술을 활용할 수 있다), 특정 프로퍼티(name)를 처리하기 위해 구체적인 개별 API를 제공한다.
- 이를 위임 프로퍼티를 활용하게 변경할 수 있다. by 키워드 뒤에 맵을 직접 넣으면 된다.
```kotlin
// 값을 맵에 저장하는 위임 프로퍼티 사용하기
class Person {
    private val _attributes = hashMapOf&lt;String, String&gt;()

    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    // 위임 프로퍼티로 맵을 사용한다.
    val name: String by _attributes
}</code></pre><ul>
<li>이런 코드가 작동하는 이유는 표준 라이브러리가 Map과 MutableMap 인터페이스에 대해 getValue와 setValue 확장 함수를 제공하기 때문이다.<ul>
<li>getValue에서 맵에 프로퍼티 값을 저장할 때는 자동으로 프로퍼티 이름을 키로 활용한다.</li>
</ul>
</li>
<li><code>p.name</code>은 <code>_attributes.getValue(p, prop)</code>라는 호출을 대신하고, <code>_attributes.getValue(p, prop)</code>는 다시 <code>_attributes[prop.name]</code>을 통해 구현된다.</li>
</ul>
<h3 id="프레임워크에서-위임-프로퍼티-활용">프레임워크에서 위임 프로퍼티 활용</h3>
<ul>
<li>객체 프로퍼티를 저장하거나 변경하는 방법을 바꿀 수 있으면 프레임워크를 개발할 때 유용하다.<pre><code class="language-kotlin">// 위임 프로퍼티를 사용해 데이터베이스 칼럼 접근하기
object Users: IdTable() { // 객체는 데이터베이스 테이블에 해당한다.
  val name = varchar(&quot;name&quot;, length = 50).index()
  val age = integer(&quot;age&quot;)
}
</code></pre>
</li>
</ul>
<p>// 각 User 인스턴스는 테이블에 들어있는 구체적인 엔티티에 해당한다.
class User(id: EntityID) : Entity(id) {
    // 사용자 이름은 데이터베이스 &quot;name&quot; 칼럼에 들어 있다.
    var name: String by Users.name
    var age: Int by Users.age
}</p>
<pre><code>- User의 상위 클래스인 Entity 클래스는 데이터베이스 칼럼을 엔티티의 속성 값으로 연결해주는 매핑이 있다.
  - 이 프레임워크를 사용하면 User의 프로퍼티에 접근할 때 자동으로 Entity 클래스에 정의된 데이터베이스 매핑으로부터 필요한 값을 가져오므로 편리하다.
- 어떤 User 객체를 변경하면 그 객체는 변경됨 dirty 상태로 변하고, 프레임워크는 나중에 적절히 데이터베이스에 변경 내용을 반영한다.
- 각 엔티티 속성(name, age)은 위임 프로퍼티며, 칼럼 객체(Users.name, Users.age)를 위임 객체로 사용한다.
- 프레임워크는 Column 클래스 안에 getValue와 setValue 메소드를 정의한다. 이 두 메소드는 코틀린의 위임 객체 관례에 따른 시그니처 요구 사항을 만족한다.
```kotlin
operator fun &lt;T&gt; Column&lt;T&gt;.getValue(o: Entity, desc: KProperty&lt;*&gt;): T {
    // 데이터베이스에서 칼럼 값 가져오기
}

operator fun &lt;T&gt; Column&lt;T&gt;.setValue(o: Entity, desc: KProperty&lt;*&gt;, value: T) {
    // 데이터베이스의 값 변경하기
}</code></pre><ul>
<li><code>user.age += 1</code>이라는 식을 코드에서 사용하면 그 식은 <code>user.ageDelegate.setValue(user.ageDelegate.getValue() + 1)</code>과 비슷한 코드로 변환한다(객체 인스턴스와 프로퍼티 파라미터는 생략함)</li>
<li>getValue와 setValue 메소드는 데이터베이스에서 데이터를 가져오고 기록하는 작업을 처리한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 6장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-6%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-6%EC%9E%A5</guid>
            <pubDate>Wed, 15 Nov 2023 00:36:45 GMT</pubDate>
            <description><![CDATA[<h1 id="코틀린-타입-시스템">코틀린 타입 시스템</h1>
<blockquote>
<ul>
<li>널이 될 수 있는 타입과 널을 처리하는 구문의 문법</li>
<li>코틀린 원시 타입 소개와 자바 타입과 코틀린 원시 타입의 관계</li>
<li>코틀린 컬렉션 소개와 자바 컬렉션과 코틀린 컬렉션의 관계</li>
</ul>
</blockquote>
<hr>
<ul>
<li>코틀린의 타입 시스템은 코드의 가독성을 향상시키는 데 도움이 되는 몇 가지 특성을 제공한다.<ul>
<li>널이 될 수 있는 타입과 읽기 전용 컬렉션이 있다.</li>
</ul>
</li>
<li>코틀린은 자바 타입 시스템에서 불필요하거나 문제가 되던 부분을 제거했다.</li>
</ul>
<h2 id="널-가능성">널 가능성</h2>
<ul>
<li><span style='background-color: #fff5b1'/>NullPointerException 오류(NPE)를 피할 수 있게 돕기 위한 코틀린 타입 시스템의 특성이다.</li>
<li>null에 대한 접근 방법은 가능한 한 이 문제를 실행 시점에서 컴파일 시점으로 옮기는 것이다.<ul>
<li>널이 될 수 있는지 여부를 타입 시스템에 추가함으로써 컴파일러가 여러 가지 오류를 컴파일 시 미리 감지해서 실행 시점에 발생할 수 있는 예외의 가능성을 줄일 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="널이-될-수-있는-타입">널이 될 수 있는 타입</h3>
<ul>
<li>코틀린과 자바의 가장 중요한 차이는 코틀린 타입 시스템이 널이 될 수 있는 타입을 명시적으로 지원한다는 점이다.<ul>
<li>널이 될 수 있는 타입은 프로그램 안의 프로퍼티나 변수에 null을 허용하게 만드는 방법이다.</li>
</ul>
</li>
<li>어떤 변수가 널이 될 수 있다면 그 변수에 대해(그 변수를 수신 객체로) 메소드를 호출하면 NullPointerException이 발생할 수 있으므로 안전하지 않다. 코틀린은 그런 메소드 호출을 금지함으로써 많은 오류를 방지한다.<pre><code class="language-java">int strLen(String s) {
  return s.lenth();
}</code></pre>
<pre><code class="language-kotlin">fun strLen(s: String) = s.length
</code></pre>
</li>
</ul>
<p>strLen(null) // Error: Null can not be a value of a non-null type String</p>
<pre><code>- 컴파일러는 널이 될 수 있는 값을 strLen에게 인자로 넘기지 못하게 막는다. 따라서 strLen 함수가 결코 실행 시점에 NullPointerException을 발생시키지 않으리라 장담할 수 있다.


- 이 함수가 널과 문자열을 인자로 받을 수 있게 하려면 타입 이름 뒤에 물음표(?)를 명시해야 한다.
```kotlin
fun strLenSafe(s: String?) = ... </code></pre><ul>
<li>String?, Int?, MyCustomType? 등 어떤 타입이든 타입 이름 뒤에 물음표를 붙이면 그 타입의 변수나 프로퍼티에 null 참조를 저장할 수 있다는 뜻이다.</li>
<li>따라서 모든 타입은 기본적으로 널이 될 수 없는 타입이다. </li>
</ul>
<ul>
<li>널이 될 수 있는 타입의 변수가 있다면 그에 대해 수행할 수 있는 연산이 제한된다.<pre><code class="language-kotlin">fun strLenSafe(s: String?) = s.length()
// 결과: Error: only safe(?.) or non-null asserted (!!.) calls are allowed on ..</code></pre>
</li>
<li>널이 될 수 있는 값을 널이 될 수 없는 타입의 변수에 대입할 수 없다.<pre><code class="language-kotlin">val x: String? = null
var y: String = x
// 결과: Error: Type mismatch: inferred type is String? but String was expected</code></pre>
</li>
<li>널이 될 수 있는 타입의 값을 널이 될 수 없는 타입의 파라미터를 받는 함수에 전달할 수 없다.<pre><code class="language-kotlin">strLen(x)
// 결과: Error: Type mismatch: inferred type is String? but String was expected</code></pre>
</li>
</ul>
<blockquote>
<p><strong>이렇게 제약이 많으면 널이 될 수 있는 타입의 값으로 대체 뭘 할 수 있을까?</strong></p>
<p>가장 중요한 일은 바로 null과 비교하는 것이다. 일단 null과 비교하고 나면 컴파일러는 그 사실을 기억하고 null이 아님이 확실한 영역에서는 해당 값을 널이 될 수 없는 타입의 값처럼 사용할 수 있다.</p>
</blockquote>
<pre><code class="language-kotlin">// if 검사를 통해 null 값 다루기
fun strLenSafe(s: String?): Int =
    if (s != null) s.length else 0 // null 검사를 추가하면 코드가 컴파일된다.

val x: String? = null
println(strLenSafe(x)) // 0

println(strLenSafe(&quot;abc&quot;)) // 3</code></pre>
<h3 id="타입의-의미">타입의 의미</h3>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%9E%90%EB%A3%8C%ED%98%95">타입</a> : 분류로 ... 어떤 값이 가능한지와 그 타입에 대해 수행할 수 있는 연산의 종류를 결정한다.</li>
<li>코틀린은 널이 될 수 있는 타입과 널이 될 수 없는 타입을 구분하면 각 타입의 값에 대해 어떤 연산이 가능할 지 명확히 이해할 수 있고, 실행 시점에 예외를 발생시킬 수 있는 연산을 판단할 수 있다. 따라서 그런 연산을 아예 금지시킬 수 있다.</li>
</ul>
<blockquote>
<p>실행 시점에 널이 될 수 있는 타입이나 널이 될 수 없는 타입의 객체는 같다. 널이 될 수 있는 타입은 널이 될 수 없는 타입을 감싼 래퍼 타입이 아니다. 모든 검사는 컴파일 시점에 수행된다. 따라서 코틀린에서는 널이 될 수 있는 타입을 처리하는 데 별도의 실행 시점 부가 비용이 들지 않는다.</p>
</blockquote>
<h3 id="안전한-호출-연산자-">안전한 호출 연산자: ?.</h3>
<ul>
<li>?. : null 검사와 메소드 호출을 한 번의 연산으로 수행한다.<ul>
<li><code>s?.toUpperCase()</code>와 <code>if (s != null) s.toUpperCase() else null</code>는 같다.</li>
</ul>
</li>
<li>안전한 호출(?.)의 결과 타입도 널이 될 수 있는 타입이라는 사실에 유의하라.<pre><code class="language-kotlin">fun printAllCaps(s: String?) {
  val allCaps: String? = s?.toUpperCase() // allCaps는 널일 수도 있다.
  println(allCaps)
}
</code></pre>
</li>
</ul>
<p>printAllCaps(&quot;abc&quot;) // ABC
printAllCaps(null) // null</p>
<pre><code>- 메서드 호출뿐 아니라 프로퍼티를 읽거나 쓸 때도 안전한 호출을 사용할 수 있다.
```kotlin
class Employee(val name: String, val manager: Employee?)

fun managerName(employee: Employee): String? = employee.manager?.name

val ceo = Employee(&quot;Da Boss&quot;, null)
val developer = Employee(&quot;Bob smith&quot;, ceo)

println(managerName(developer)) // Da Boss
println(ManagerName(ceo)) // null</code></pre><ul>
<li>객체 그래프에서 널이 될 수 있는 중간 객체가 여럿 있다면 한 식 안에서 안전한 호출을 연쇄해서 함께 사용하면 편할 때가 자주 있다.<pre><code class="language-kotlin">// 안전한 호출 연쇄시키기
class Address(val streetAddress: String, val zipCode: Int,
          val city: String, val country: String)
</code></pre>
</li>
</ul>
<p>class Company(val name: String, val address: Address?)</p>
<p>class Person(val name: String, val company: Company?)</p>
<p>fun Person.countryName(): String {
    // 여러 안전한 호출 연산자를 연쇄해 사용한다.
    val country = this.company?.address?.country
    return if (country != null) country else &quot;Unknown&quot;<br>}</p>
<p>val person = Person(&quot;Dmitry&quot;, null)
println(person.countryName()) // Unknown</p>
<pre><code>
### 엘비스 연산자: ?:
- 엘비스 연산자 : null 대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있는 연산자를 제공한다.
```kotlin
fun foo(s: String?) {
    val t: String = s ?: &quot;&quot;
}</code></pre><pre><code class="language-kotlin">// 엘비스 연산자를 활용해 널 값 다루기
fun strLenSafe(s: String?): Int = s?.length ?: 0

print(strLenSafe(&quot;abc&quot;)) // 3
println(strLenSafe(null)) // 0</code></pre>
<pre><code class="language-kotlin">fun Person.countryName() = company?.address?.country ?: &quot;Unknown&quot;</code></pre>
<ul>
<li>코틀린에서는 return이나 throw 등의 연산도 식이다. 따라서 엘비스 연산자의 우항에 return, throw 등의 연산을 넣을 수 있고, 엘비스 연산자를 더욱 편하게 사용할 수 있다.<ul>
<li>함수의 전체 조건을 검사하는 경우 특히 유용하다.</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">// throw와 엘비스 연산자 함께 사용하기
class Address(val streetAddress: String, val zipCode: Int, 
              val city: String, val country: String)

class Company(val name: String, val address: Address?)

class Person(val name: String, val company: Company?)

fun printShippingLabel(person: Person) {
    val address = person.company?.address
      ?: throw IllegalArgumentException(&quot;No address&quot;) // 주소가 없으면 예외를 발생시킨다.

    with(address) {
        println(streetAddress)
        println(&quot;$zipCode $city, $country&quot;)
    }
}

val address = Address(&quot;Elsestr. 47&quot;, 80687, &quot;Munich&quot;, &quot;Germany&quot;)
val jetbrains = Company(&quot;JetBrains&quot;, address)
val person = Person(&quot;Dmitry&quot;, jetbrains)

printShippingLabel(person)
// Elsestr. 47
// 80687 Munich, Germany

printShippingLabel(Person(&quot;Alexey&quot;, null))
// java.lang.IllegalArgumentException: No address</code></pre>
<h3 id="안전한-캐스트-as">안전한 캐스트: as?</h3>
<ul>
<li><p>자바 타입 캐스트와 마찬가지로 대상 값을 as로 지정한 타입으로 바꿀 수 없으면 ClassCastException이 발생한다.</p>
</li>
<li><p>as? 연산자는 어떤 값을 지정한 타입으로 캐스트한다. as?는 값을 대상 타입으로 변환할 수 없으면 null을 반환한다.</p>
<pre><code class="language-kotlin">// 안전한 연산자를 사용해 equals 구현하기
class Person(val firstName: String, val lastName: String) {
  override fun equals(o: Any?): Boolean {
      // 타입이 서로 일치하지 않으면 false를 반환한다.
      val otherPerson = o as? Person ?: return false

      // 안전한 캐스트를 하고나면 otherPerson이 Person 타입으로 스마트 캐스트된다.
      return otherPerson.firstName == firstName &amp;&amp;
              otherPerson.lastName == lastName
  }

  override fun hashCode(): Int =
      firstName.hashCode() * 37 + lastName.hashCode()
}
</code></pre>
</li>
</ul>
<p>val p1 = Person(&quot;Dmitry&quot;, &quot;Jemerov&quot;)
val p2 = Person(&quot;Dmitry&quot;, &quot;Jemerov&quot;)
println(p1 == p2) // true
println(p1.equals(42)) // false</p>
<pre><code>
### 널 아님 단언: !!
- 널 아님 단언은 코틀린에서 널이 될 수 있는 타입의 값을 다룰 때 사용할 수 있는 도구 중에서 가장 단순하면서도 무딘 도구다.
- 느낌표를 이중(!!)으로 사용하면 어떤 값이든 널이 될 수 없는 타입으로 (강제로) 바꿀 수 있다. 실제 널에 대해 !!를 적용하면 NPE가 발생한다.
```kotlin
fun ignoreNulls(s: String?) {
    val sNotNull: String = s!! // 예외는 이 지점을 가리킨다.
    println(sNotNull.length)
}

ignoreNulls(null)
// Expceion in thread &quot;main&quot; kotlin.KotlinNullPointerException ...</code></pre><ul>
<li><p>예외(NullPointerException의 한 종류)를 던지는 일 외에 코틀린이 택할 수 있는 대안이 별로 없다. 하지만 발생한 예외는 null 값을 사용하는 코드(sNotNull.length가 있는 줄)가 아니라 단언문이 위치한 곳을 가리킨다는 점에 유의하라.</p>
</li>
<li><p>근본적으로 !!는 컴파일러에게 &quot;나는 이 값이 null이 아님을 잘 알고 있다. 내가 잘못 생각했다면 예외가 발생해도 감수하겠다&quot;라고 말하는 것이다.</p>
</li>
<li><p>호출된 함수가 언제나 다른 함수에서 널이 아닌 값을 전달받는 사실이 분명하다면 굳이 검사를 다시 수행하지 않는다.</p>
<pre><code class="language-kotlin">// 스윙 액션에서 널 아님 단언 사용하기
class CopyRowAction(val list: JList&lt;String&gt;) : AbstractAction() {
  override fun isEnabled(): Boolean =
      list.selectedValue != null

  // actionPerformed는 isEnabled가 &quot;true&quot;인 경우에만 호출된다.
  override fun actionPerformed(e: ActionEvent) {
      val value = list.selectedValue!!
  }
  // value를 클립보드로 복사
}</code></pre>
</li>
<li><p>이 경우 !!를 사용하지 않으려면 <code>val value = list.selectedValue ?: return</code>처럼 널이 될 수 없는 타입의 값을 얻어야 한다.</p>
<ul>
<li>list.selectedValue가 null이면 함수가 조기 종료되므로 함수의 나머지 본문에서는 value가 항상 널이 아니게 된다.</li>
</ul>
</li>
<li><p>이 식에서 엘비스 연산자는 중복이라 할 수 있지만 나중에 isEnabled가 더 복잡해질 가능성에 대비해 미리 보호 장치를 마련해 둔다고 생각할 수도 있다.</p>
</li>
</ul>
<blockquote>
<p>!!를 널에 대해 사용해서 발생하는 예외의 스택 트레이스(stack trace)에는 어떤 파일의 몇 번째 줄인지에 대한 정보는 들어있지만 어떤 식에서 예외가 발생햇는지에 대한 정보는 들어있지 않다. 어떤 값이 널이었는지 확실히 하기 위해 여러 !! 단언문을 한 줄에 함께 쓰는 일을 피하라.</p>
</blockquote>
<blockquote>
<p>✅ <strong>스택 트레이스</strong> : 프로그램이 시작된 시점부터 현재 위치까지의 메서드 호출 목록으로, 예외가 발생할 경우 JVM이 어디서 예외가 발생했는지 알려주는 역할을 한다.</p>
</blockquote>
<h3 id="let-함수">let 함수</h3>
<ul>
<li>let 함수를 안전한 호출 연산자와 함께 사용하면 원하는 식을 평가해서 결과가 널인지 검사한 다음에 그 결과를 변수에 넣는 작업을 간단한 식을 사용해 한꺼번에 처리할 수 있다.</li>
<li>let 함수는 널이 될 수 있는 값을 널이 아닌 값만 인자로 받는 함수에 넘기는 경우에 자주 사용된다.<pre><code class="language-kotlin">fun sendEmailTo(email: String) { /*...*/ }
</code></pre>
</li>
</ul>
<p>val email: string? = ...
sendEmailTo(email)
// 결과: Error: Type mismatch: inferred type is String? but String was expected</p>
<pre><code>```kotlin
// 인자를 넘기기 전에 주어진 값이 널인지 검사해야 한다.
if (email != null) sendEmailTo(email)</code></pre><ul>
<li>let 함수는 자신의 수신 객체를 인자로 전달받은 람다에게 넘긴다.</li>
<li>널이 될 수 있는 값에 대해 안전한 호출 구문을 사용해 let을 호출하되 널이 될 수 없는 타입을 인자로 받는 람다를 let에 전달한다.<pre><code class="language-kotlin">// let을 사용해 null이 아닌 인자로 함수 호출하기
fun sendEmailTo(email: String) {
  println(&quot;Sending email to $email&quot;)
}
</code></pre>
</li>
</ul>
<p>var email: String? = &quot;<a href="mailto:yole@example.com">yole@example.com</a>&quot;
email?.let { sendEmailTo(it) }
// Sending email to <a href="mailto:yole@example.com">yole@example.com</a></p>
<p>email = null
email?.let { sendEmailTo(it) } // 아무 일도 일어나지 않는다.</p>
<pre><code>- let을 쓰면 긴 식의 결과를 저장하는 변수를 따로 만들 필요가 없다.
```kotlin
fun getTheBestPersonInTheWorld(): Person? = null

val person: Person? = getTheBestPersonInTheWorld()
if (person != null) sendEmailTo(person.email)

getTheBestPersonInTheWorld()?.let { sendEmailTo(it.email) }</code></pre><ul>
<li>여러 값이 널인지 검사해야 한다면 let 호출을 중첩시켜서 처리할 수 있다. 그렇게 let을 중첩시켜 처리하면 코드가 복잡해져서 알아보기 어려워진다. 그런 경우 일반적인 if를 사용해 모든 값을 한꺼번에 검사하는 편이 낫다.</li>
</ul>
<h3 id="나중에-초기화할-프로퍼티">나중에 초기화할 프로퍼티</h3>
<ul>
<li>코틀린에서 클래스 안의 널이 될 수 없는 프로퍼티를 생성자 안에서 초기화하지 않고 특별한 메소드 안에서 초기화할 수 없다.<pre><code class="language-kotlin">class MyClass(val nonNullableProperty: String) {
  // 특별한 메소드에서 초기화할 수 없음
  fun initializeProperty(value: String) {
      // 컴파일 오류: Val cannot be reassigned
      nonNullableProperty = value
  }
}
</code></pre>
</li>
</ul>
<p>fun main() {
    val instance = MyClass(&quot;initialValue&quot;)
    instance.initializeProperty(&quot;newValue&quot;)
}</p>
<pre><code>- 코틀린은 일반적으로 생성자에서 모든 프로퍼티를 초기화해야 한다. 게다가 프로퍼티 타입이 널이 될 수 없는 타입이라면 반드시 널이 아닌 값으로 그 프로퍼티를 초기화해야 한다. 
- 그런 초기화 값을 제공할 수 없으면 널이 될 수 있는 타입을 사용할 수 밖에 없다. 하지만 널이 될 수 있는 타입을 사용하려면 모든 프로퍼티 접근에 널 검사를 넣거나 !! 연산자를 써야 한다.
```kotlin
class MyClass(var nullableProperty: String?) {
    fun initializeProperty(value: String) {
        nullableProperty = value
    }
}

fun main() {
    val instance = MyClass(null)
    println(instance.nullableProperty) // null

    instance.initializeProperty(&quot;newValue&quot;)
    println(instance.nullableProperty) // newValue
}</code></pre><pre><code class="language-kotlin">// 널 아님 단언을 사용해 널이 될 수 있는 프로퍼티 접근하기
class MyService {
    fun performAction(): String = &quot;foo&quot;
}

class MyTest {
    // null로 초기화하기 위해 널이 될 수 있는 타입인 프로퍼티를 선언한다.
    private var myService: MyService? = null

    // setUp 메소드 안에서 진짜 초기값을 지정한다.
    @Before fun setUp() {
        myService = myService()
    }

    @Test fun testAction() {
        // 반드시 널 가능성에 신경 써야 한다. !!나 ?을 꼭 써야 한다.
        Assert.assertEquals(&quot;foo&quot;, myService!!.performAction())
    }
}</code></pre>
<ul>
<li>이 코드는 보기 나쁘다. 특히 프로퍼티를 여러 번 사용해야 하면 코드가 더 못생겨진다.</li>
<li>이를 해결하기 위해 myService 프로퍼티를 나중에 초기화(late-initialized) 할 수 있다.<pre><code class="language-kotlin">// 나중에 초기화하는 프로퍼티 사용하기
class myService {
  fun performAction(): String = &quot;foo&quot;
}
</code></pre>
</li>
</ul>
<p>class MyTest {
    // 초기화하지 않고 널이 될 수 없는 프로퍼티를 선언한다.
    private lateinit var myService: MyService</p>
<pre><code>@Before fun setUp() {
    myService = MyService()
}

// 널 검사를 수행하지 않고 프로퍼티를 사용한다.
@Test fun testAction() {
    Assert.assertEquals(&quot;foo&quot;, myService.performAction())
}</code></pre><p>}</p>
<pre><code>- 나중에 초기화하는 프로퍼티는 항상 var여야 한다. 
  - **WHY)** &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;val 프로퍼티는 final 필드로 컴파일되며, 생성자 안에서 반드시 초기화해야 한다. 따라서 생성자 밖에서 초기화해야 하는 나중에 초기화하는 프로퍼티는 항상 var여야 한다.
```kotlin
class Example {
    // 필드 선언시 초기값 설정 
    val name: String = &quot;DefaultName&quot;
}

// val 프로퍼티를 선언과 동시에 초기화하면 해당 초기화 구문이 생성자의 일부로 취급되어, 
// 컴파일러는 이를 내부적으로 생성된 생성자 코드로 변환
class Example {
    val name: String

    constructor() {
        this.name = &quot;DefaultName&quot;
    }
}</code></pre><h3 id="널이-될-수-있는-타입-확장">널이 될 수 있는 타입 확장</h3>
<ul>
<li><p>널이 될 수 있는 타입에 대한 확장 함수를 정의하면 null 값을 다루는 강력한 도구로 활용할 수 있다.</p>
<ul>
<li>어떤 메소드를 호출하기 전에 수신 객체 역할을 변수가 널이 될 수 없다고 보장하는 대신, 직접 변수에 대해 메소드를 호출해도 확장 함수인 메소드가 알아서 널을 처리해준다.</li>
</ul>
</li>
<li><p>일반 멤버 호출은 객체 인스턴스를 통해 디스패치되므로 그 인스턴스가 널인지 여부를 검사하지 않는다.</p>
<ul>
<li><strong>WHY)</strong> printMessage 함수를 호출할 때 널 체크(?.)를 사용하지 않고 일반적인 방식으로 호출하더라도 컴파일러에서는 디스패치를 인스턴스의 실제 타입으로 수행하기 때문에 널 체크가 필요하지 않습니다.</li>
</ul>
</li>
<li><p>이는 Kotlin에서 스마트 캐스트(Smart Cast)가 활용되기 때문입니다.</p>
</li>
</ul>
<pre><code class="language-kotlin">// 널이 될 수 있는 Int에 대한 확장 함수 정의
fun Int?.safeSquare(): Int {
    return this?.let { it * it } ?: 0
}

fun main() {
    val nullableNumber: Int? = null

    // 널이 될 수 있는 Int에 대한 확장 함수 호출
    val result = nullableNumber.safeSquare()

    println(&quot;Result: $result&quot;)  // Result: 0
}</code></pre>
<blockquote>
<p>✅ <strong>동적 디스패치</strong> : 객체지향 언어에서 객체의 동적 타입에 따라 적절한 메소드를 호출해주는 방식</p>
<p>✅ <strong>정적 디스패치</strong> : 반대로 컴파일러가 컴파일 시점에 어떤 메소드가 호출될지 결정해서 코드를 생성하는 방식</p>
<p>일반적으로 동적 디스패치를 처리할 때는 객체별로 자신의 메소드에 대한 테이블을 저장하는 방법을 가장 많이 사용한다. 물론 대부분의 객체지향 언어에서 같은 클래스에 속한 객체는 같은 메소드 테이블을 공유하므로 보통 메소드 테이블은 클래스마다 하나씩만 만들고 각 객체는 자신의 클래스에 대한 참조를 통해 그 메소드 테이블을 찾아보는 경우가 많다.</p>
</blockquote>
<pre><code class="language-kotlin">// null이 될 수 있는 수신 객체에 대해 확장 함수 호출하기
fun verifyUserInput(input: String?) {
    if (input.isNullOrBlank()) {
        println(&quot;Please fill in the required fields&quot;)
    }
}

verifyUserInput(&quot; &quot;) // Please fill in the required fields

// isNullOrBlank에 &quot;null&quot;을 수신 객체로 전달해도 아무런 예외가 발생하지 않는다.
verifyUserInput(null) // Please fill in the required fields</code></pre>
<ul>
<li>안전한 호출 없이도 널이 될 수 있는 수신 객체 타입에 대해 선언된 확장 함수를 호출 가능하다.<ul>
<li>isNullOrBlank는 널을 명시적으로 검사해서 널인 경우 true를 반환하고, 널이 아닌 경우 isBlank를 호출한다.<ul>
<li>isBlank는 널이 아닌 문자열 타입의 값에 대해서만 호출할 수 있다.<pre><code class="language-kotlin">// 널이 될 수 있는 String의 확장
fun String?.isNullOrBlank(): Boolean =
// 두 번째 &quot;this&quot;에는 스마트 캐스트가 적용된다.
this == null || this.isBlank()</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li>널이 될 수 있는 타입에 대한 확장을 정의하면 널이 될 수 있는 값에 대해 그 확장 함수를 호출할 수 있다.<ul>
<li>그 함수의 내부에서 this는 널이 될 수 있다. 따라서 명시적으로 널 여부를 검사해야 한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>자바에서는 메서드 안의 this는 그 메소드가 호출된 수신 객체를 가리키므로 항상 널이 아니다.</p>
<p>코틀린에서는 널이 될 수 있는 타입의 확장 함수 안에서는 this가 널이 될 수 있다는 점이 자바와 다르다.</p>
</blockquote>
<ul>
<li>let 함수도 널이 될 수 있는 타입의 값에 대해 호출할 수 있지만 let은 this가 널인지 검사하지 않는다.<ul>
<li>널이 될 수 있는 타입의 값에 대해 안전한 호출을 사용하지 않고 let을 호출하면 람다의 인자는 널이 될 수 있는 타입으로 추론된다.<pre><code class="language-kotlin">val person: Person? = ...
// 안전한 호출을 하지 않음. 따라서 &quot;it&quot;은 널이 될 수 있는 타입으로 취급됨
person.let { sendEmailTo(it) }
// 결과: Error: Type mismatch: inferred type is Person? but Person was expected
person?.let { sendEmailTo(it) } // 개선</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>직접 확장 함수를 작성한다면 그 확장 함수를 널이 될 수 있는 타입에 대해 정의할지 여부를 고민할 필요가 있다. 처음에는 널이 될 수 없는 타입에 대한 확장 함수를 정의하라. 나중에 대부분 널이 될 수 있는 타입에 대해 그 함수를 호출했다는 사실을 깨닫게 되면 확장 함수 안에서 널을 제대로 처리하게 하면(그 확장 함수를 사용하는 코드가 깨지지 않으므로) 안전하게 그 확장 함수를 널이 될 수 있는 타입에 대한 확장 함수로 바꿀 수 있다.</p>
</blockquote>
<h3 id="타입-파리미터의-널-가능성">타입 파리미터의 널 가능성</h3>
<ul>
<li>코틀린에서는 함수나 클래스의 모든 타입 파라미터는 기본적으로 널이 될 수 있다. 널이 될 수 있는 타입을 포함하는 어떤 타입이라도 타입 파라미터를 대신할 수 있다.</li>
<li>따라서 파라미터 T를 클래스나 함수 안에서 타입 이름으로 사용하면 이름 끝에 물음표가 없더라도 T가 널이 될 수 있는 타입이다.<pre><code class="language-kotlin">// 널이 될 수 있는 타입 파라미터 다루기
fun &lt;T&gt; printHashCode(t: T) {
  // &quot;t&quot;가 null이 될 수 있으므로 안전한 호출을 써야만 한다.
  println(t?.hashCode())
}
</code></pre>
</li>
</ul>
<p>// &quot;T&quot;의 타입은 &quot;Any?&quot;로 추론된다.
printHashCode(null) // null</p>
<pre><code>- printHashCode 호출에서 타입 파라미터 T에 대해 추론한 타입은 널이 될 수 있는 Any? 타입이다.
- t 파라미터의 타입 이름 T에는 물음표가 붙어있지 않지만 t는 null을 받을 수 있다.


- 타입 파라미터가 널이 아님을 확실히 하려면 널이 될 수 없는 타입 상한(upper bound)를 지정해야 한다. 이렇게 널이 될 수 없는 타입 상한을 지정하면 널이 될 수 있는 값을 거부하게 된다.
```kotlin
// 타입 파라미터에 대해 널이 될 수 없는 상한을 사용하기
fun &lt;T: Any&gt; printHashCode(t: T) { // 이제 &quot;T&quot;는 널이 될 수 없는 타입이다.
    println(t.hashCode())
}
printHashCode(null) // 널이 될 수 없는 타입의 파라미터에 널을 넘길 수 없다.
// Error: Type parameter bound for `T` is not satisfied
printHashCode(42) // 42</code></pre><h3 id="널-가능성과-자바">널 가능성과 자바</h3>
<ul>
<li><p>코틀린 컴파일러는 공개(public) 가시성인 코틀린 함수의 널이 아닌 타입인 파라미터와 수신 객체에 대한 널 검사를 추가해준다. 따라서 공개 가시성 함수에 널 값을 사용하면 즉시 예외가 발생한다.</p>
<ul>
<li><p>이런 파라미터 값 검사는 함수 내부에서 파라미터를 사용하는 시점이 아니라 함수 호출 시점에 이뤄진다.</p>
<pre><code class="language-java">// 널 가능성 애노테이션이 없는 자바 클래스
public class Person {
private final String name;

public Person(String name) {
    this.name = name;
}

public String getName() {
    return name;
}
}</code></pre>
</li>
</ul>
</li>
<li><p>코틀린 컴파일러는 String 타입의 널 가능성에 대해 전혀 알지 못한다. 따라서 널 가능성을 직접 처리해야만 한다.</p>
<pre><code class="language-kotlin">// 널 검사를 통해 자바 클래스 접근하기
fun yellAtSafe(person: Person) {
  println((person.name ?: &quot;Anyone&quot;).toUpperCase() + &quot;!!!&quot;)
}
</code></pre>
</li>
</ul>
<p>yellAtSafe(Person(null)) // ANYONE!!!</p>
<pre><code>- ! 표기 : String! 타입의 널 가능성에 대해 아무 정보도 없다는 뜻
```kotlin
val i: Int = person.name
// 결과: Error: Type mismatch: inferred type is String! but Int was expected

// 자바 프로퍼티를 널이 될 수 있는 타입으로 볼 수 있다.
val s: String? = person.name
// 자바 프로퍼티를 널이 될 수 없는 타입으로 볼 수 있다.
val s1: String = person.name</code></pre><h4 id="상속">상속</h4>
<pre><code class="language-java">// String 파라미터가 있는 자바 인터페이스
interface StringProcessor {
    void process(String value);
}</code></pre>
<pre><code class="language-kotlin">// 자바 인터페이스를 여러 다른 널 가능성으로 구현하기
class StringPrinter: StringProcessor {
    override fun process(value: String) {
        print(value)
    }
}

class NullableStringPrinter : StringProcessor {
    override fun process(value: String?) {
        if (value != null) {
            print(value)
        }
    }
}</code></pre>
<h2 id="코틀린의-원시-타입">코틀린의 원시 타입</h2>
<ul>
<li>코틀린은 원시 타입과 래퍼 타입을 구분하지 않는다.</li>
</ul>
<h3 id="원시-타입-int-boolean-등">원시 타입: Int, Boolean 등</h3>
<ul>
<li>자바는 참조 타입이 필요한 경우 특별한 래퍼 타입(java.lang.Integer 등)으로 원시 타입 값을 감싸서 사용한다.</li>
<li>원시 타입(Int 등)의 변수에는 그 값이 직접 들어가지만, 참조 타입(String 등)의 변수에는 메모리상의 객체 위치가 들어간다.</li>
<li>원시 타입의 값을 더 효율적으로 저장하고 여기저기 전달할 수 있다. 하지만 그런 값에 대해 메소드를 호출하거나 컬렉션에 원시 타입 값을 담을 수 없다.</li>
</ul>
<ul>
<li>코틀린은 원시 타입과 래퍼 타입을 구분하지 않으므로 항상 같은 타입을 사용한다.<pre><code class="language-kotlin">val i: Int = 1
val list: List&lt;Int&gt; = listOf(1, 2, 3)</code></pre>
</li>
<li>코틀린에서는 숫자 타입 등 원시 타입의 값에 대해 메소드를 호출할 수 있다.<pre><code class="language-kotlin">fun showProgress(progress: Int) {
  val percent = progress.coerceIn(0, 100)
  println(&quot;We&#39;re ${percent}% done!&quot;)
}
</code></pre>
</li>
</ul>
<p>showProgress(146) // We&#39;re 100% done!</p>
<pre><code>
### 널이 될 수 있는 원시 타입: Int?, Boolean? 등
- null 참조를 자바의 참조 타입의 변수에만 대입할 수 있기 때문에 널이 될 수 있는 코틀린 타입은 자바 원시 타입으로 표현할 수 없다.
  - 따라서 코틀린에서 널이 될 수 있는 원시 타입을 사용하면 그 타입은 자바의 래퍼 타입으로 컴파일된다.
```kotlin
// 널이 될 수 있는 원시 타입
data class Person(val name: String, val age: Int? = null) {
    fun isOlderThan(other: Person): Boolean? {
        if (age == null || other.age == null)
            return null
        return age &gt; other.age
    }
}

println(Person(&quot;Sam&quot;, 35).isOlderThan(Person(&quot;Amy&quot;, 42))) // false
println(Person(&quot;Sam&quot;, 35).isOlderThan(Person(&quot;Jane&quot;))) // null</code></pre><ul>
<li><a href="https://slow-and-steady-wins-the-race.tistory.com/104">제네릭 클래스</a>의 경우 래퍼 타입을 사용한다. 어떤 클래스의 타입 인자로 원시 타입을 넘기면 코틀린은 그 타입에 대한 박스 타입을 사용한다.</li>
</ul>
<h3 id="숫자-변환">숫자 변환</h3>
<ul>
<li>코틀린은 한 타입의 숫자를 다른 타입의 숫자로 자동 변환하지 않는다. 결과 타입이 허용하는 숫자의 범위가 원래 타입의 범위보다 넓은 경우조차도 자동 변환은 불가능하다.<pre><code class="language-kotlin">val i = 1
val l: Long = i // Error: type mismatch
</code></pre>
</li>
</ul>
<p>// 직접 변환 메소드를 호출
val i = 1
val l: Long = i.toLong()</p>
<pre><code>- 코틀린은 모든 원시 타입(단 Boolean 제외)에 대한 변환 함수를 제공한다. 양방향 변환 함수가 모두 제공한다.
```kotlin
val x = 1
println(x in listOf(1L, 2L, 3L)) // false
println(x.toLong() in listOf(1L, 2L, 3L)) // true</code></pre><ul>
<li>숫자 리터럴을 사용할 때는 보통 변환 함수를 호출할 필요가 없다.<pre><code class="language-kotlin">fun foo(l: Long) = println(l)
</code></pre>
</li>
</ul>
<p>val b: Byte = 1 // 상수 값은 적절한 타입으로 해석된다.
val l = b + 1L // +는 Byte와 Long을 인자로 받을 수 있다.
foo(42) // 컴파일러는 42를 Long 값으로 해석한다.</p>
<pre><code>- 코틀린 산술 연산자에서도 자바와 같이 숫자 연산 시 값 넘침(overflow)이 발생할 수 있다. 코틀린은 값 넘침을 검사하느라 추가 비용을 들이지 않는다.

### Any, Any?: 최상위 타입
- 자바에서 Object가 클래스 계층의 최상위 타입이듯 코틀린에는 Any 타입이 모든 널이 될 수 없는 타입의 조상 타입이다.
- 하지만 자바에서는 참조 타입만 Object를 정점으로 하는 타입 계층에 포함되며, 원시 타입은 그런 계층에 들어있지 않다.
  - 즉 자바에서 Object 타입의 객체가 필요할 경우 int와 같은 원시 타입을 java.lang.Integer 같은 래퍼 타입으로 감싸야 한다는 뜻이다.


- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;코틀린에서는 Any가 Int 등의 원시 타입을 포함한 모든 타입의 조상 타입이다.
- 자바와 마찬가지로 코틀린에서도 원시 타입 값을 Any 타입의 변수에 대입하면 자동으로 값을 객체로 감싼다.
```kotlin
val answer: Any = 42 // Any가 참조 타입이기 때문에 42가 박싱된다. </code></pre><h3 id="unit-타입-코틀린의-void">Unit 타입: 코틀린의 void</h3>
<ul>
<li>반환 타입 선언 없이 정의한 블록이 복문인 함수<pre><code class="language-kotlin">fun f(): Unit { ... }
</code></pre>
</li>
</ul>
<p>fun f() { ... }</p>
<pre><code>- 코틀린 함수의 반환 타입이 Unit이고 그 함수가 제네릭 함수를 오버라이드하지 않는다면 그 함수는 내부에서 자바 void 함수로 컴파일된다.
- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;Unit은 모든 기능을 갖는 일반적인 타입이며, void와 달리 Unit을 타입 인자로 쓸 수 있다.
```kotlin
interface Processor&lt;T&gt; {
    fun progress(): T

    fun progress2(value: T): T
}

class NoResultProcess : Processor&lt;Unit&gt; {
    override fun process() { // Unit을 반환하지만 타입을 지정할 필요가 없다.
        // 업무 처리 코드
        // 여기서 return을 명시할 필요가 없다.
    }
}

// 제네릭 함수를 오버라이드하는 경우
class resultProcess : Processor&lt;String&gt; {
    override fun progress2(value: String): String {
        // 업무 처리 코드
        // 여기서 return을 명시할 필요가 있다.
    }
}</code></pre><ul>
<li>인터페이스의 시그니처(인터페이스에 선언된 메서드, 프로퍼티, 또는 다른 멤버들의 형태)는 process 함수가 어떤 값을 반환하라고 요구한다. <ul>
<li>Unit 타입도 Unit 값을 제공하기 때문에 메소드에서 Unit 값을 반환하는 데는 아무 문제가 없다.</li>
</ul>
</li>
<li>NoResultProcessor에서 명시적으로 Unit을 반환할 필요는 없다. 컴파일러가 묵시적으로 return Unit을 넣어준다.<ul>
<li><span style='background-color: #fff5b1'/>함수형 프로그래밍에서 전통적으로 Unit은 &#39;단 하나의 인스턴스만 갖는 타입&#39;을 의미해왔고 바로 그 유일한 인스턴스의 유무가 자바 void와 코틀린 Unit을 구분하는 가장 큰 차이다.</li>
</ul>
</li>
</ul>
<h3 id="nothing-타입-이-함수는-결코-정상적으로-끝나지-않는다">Nothing 타입: 이 함수는 결코 정상적으로 끝나지 않는다</h3>
<ul>
<li>코틀린에는 결코 성공적으로 값을 돌려주는 일이 없으므로 &#39;반환 값&#39;이라는 개념 자체가 의미 없는 함수가 일부 존재한다.<pre><code class="language-kotlin">fun fail(message: String): Nothing {
  throw IllegalStateException(message)
}
</code></pre>
</li>
</ul>
<p>fun main() {
    try {
        val result: String = fail(&quot;Error message&quot;)
        // 이 부분은 실행되지 않음. throwError에서 예외가 던져짐.
        println(result)
    } catch (e: IllegalArgumentException) {
        println(&quot;Caught an exception: ${e.message}&quot;)
        // 결과: java.lang.IllegalStateException: Error message
    }
}</p>
<pre><code>- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;Nothing 타입은 아무 값도 포함하지 않는다. 따라서 Nothing은 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있다.
- 그 외의 다른 용도로 사용하는 경우 Nothing 타입의 변수를 선언하더라도 그 변수에 아무 값도 저장할 수 없으므로 아무 의미도 없다.
- Nothing을 반환하는 함수(fail 등)를 엘비스 연산자의 우항에 사용해서 전체 조건을 검사할 수 있다.
```kotlin
val address = company.address ?: fail(&quot;No address&quot;)
println(address.city)</code></pre><h2 id="컬렉션과-배열">컬렉션과 배열</h2>
<h3 id="널-가능성과-컬렉션">널 가능성과 컬렉션</h3>
<ul>
<li><p>널이 될 수 있는 값으로 이뤄진 컬렉션 만들기</p>
<pre><code class="language-kotlin">fun readNumbers(reader: BufferedReader): List&lt;Int?&gt; {
  // 널이 될 수 있는 Int 값으로 이뤄진 리스트를 만든다.
  val result = ArrayList&lt;Int?&gt;()

  for (line in reader.lineSequence()) {
      try {
          val number = line.toInt()
          result.add(number)
      }
      catch (e: NumberFormatException) {
          result.add(null)
      }
  }
  return result
}</code></pre>
</li>
<li><p>널이 될 수 있는 값으로 이뤄진 컬렉션 다루기</p>
<pre><code class="language-kotlin">fun addValidNumbers(numbers: List&lt;Int?&gt;) {
  var sumOfValidNumbers = 0
  var invalidNumbers = 0

  // 리스트에서 널이 될 수 있는 값을 읽는다.
  for (number in numbers) {
      // 널에 대한 값을 확인한다.
      if (number != null) {
          sumOfValidNumbers += number
      } else {
          invalidNumbers++
      }
  }
  println(&quot;Sum of valid numbers: $sumOfValidNumbers&quot;)
  println(&quot;Invalid numbers: $invalidNumbers&quot;)
}
</code></pre>
</li>
</ul>
<p>val reader = BufferedReader(StringBuilder(&quot;1\nabc\n42&quot;))
val numbers = readNumbers(reader)
addValidNumbers(numbers)</p>
<p>//// 결과
// Sum of valid numbers: 43
// Invalid numbers: 1</p>
<pre><code>- filterNotNull를 널이 될 수 있는 값으로 이뤄진 컬렉션에 대해 사용하기
```kotlin
fun addValidNumbers(numbers: List&lt;Int?&gt;) {
    val validNumbers = numbers.filterNotNull()
    println(&quot;Sum of valid numbers: ${validNumbers.sum()}&quot;)
    println(&quot;Invalid numbers: ${numbers.size - validNumbers.size}&quot;)
}</code></pre><ul>
<li>filterNotNull이 컬렉션 안에 널이 들어있지 않음을 보장해주므로 validNumbers는 List<Int> 타입이다.</li>
</ul>
<h3 id="읽기-전용과-변경-가능한-컬렉션">읽기 전용과 변경 가능한 컬렉션</h3>
<ul>
<li>코틀린에서는 컬렉션 안의 데이터에 접근하는 인터페이스와 컬렉션 안의 데이터를 변경하는 인터페이스를 분리했다.</li>
<li>kotlin.collections.Collection 인터페이스를 사용하면 컬렉션 안의 원소에 대해 이터레이션하고(iterator), 컬렉션의 크기를 얻고(size), 어떤 값이 컬렉션 안에 들어있는지 검사하고(contains), 컬렉션에서 데이터를 읽는 여러 다른 연산(filter, map, find 등)을 수행할 수 있다.<ul>
<li>하지만, Collection에는 원소를 추가하거나 제거하는 메소드가 없다.</li>
</ul>
</li>
<li>HOW) 컬렉션의 데이터를 수정하려면 kotlin.collections.MutableCollection 인터페이스를 사용해야한다.<ul>
<li>MutableCollection은 일반 인터페이스인 kotlin.collections.Collection을 확장하면서 원소를 추가하거나(add), 삭제하거나(remove), 컬렉션 안의 원소를 모두 지우는(clear) 등의 메소드를 더 제공한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>코드에서 가능하면 항상 읽기 전용 인터페이스를 사용하는 것을 일반적인 규칙으로 삼아라. 코드가 컬렉션을 변경할 필요가 있을 때만 변경 가능한 버전을 사용하라.</p>
</blockquote>
<ul>
<li>읽기 전용과 변경 가능한 컬렉션 인터페이스<pre><code class="language-kotlin">fun &lt;T&gt; copyElements(source: Collection&lt;T&gt;, 
                  target: MutableCollection&lt;T&gt;) {
  for (item in source) {
      target.add(item)
  }
}
</code></pre>
</li>
</ul>
<p>val source: Collections<Int> = arrayListOf(3, 5, 7)
val target: MutableCollection<Int> = arrayListOf(1)
copyElements(source, target)
println(target) // [1, 3, 5, 7]</p>
<pre><code>- target에 해당하는 인자로 읽기 전용 컬렉션을 넘길 수 없다. 실제 그 값(컬렉션)이 변경 가능한 컬렉션인지 여부와 관계없이 선언된 타입이 읽기 전용이라면 target에 넘기면 컴파일 오류가 난다.
```kotlin
val source: Collections&lt;Int&gt; = arrayListOf(3, 5, 7)
val target: Collections&lt;Int&gt; = arrayListOf(1)

copyElements(source, target)
// 결과: Error: Type mismatch: inferred type is Collection&lt;Int&gt; but MutableCollection&lt;Int&gt; was expected</code></pre><ul>
<li>읽기 전용 인터페이스 타입인 변수를 사용할 때 그 인터페이스는 실제로는 어떤 컬렉션 인스턴스를 가리키는 수많은 참조 중 하나일 수 있다.</li>
<li>이런 상황(어떤 동일한 컬렉션 객체를 가리키는 읽기 전용 컬렉션 타입의 참조와 변경 가능한 컬렉션 타입의 참조가 있는 경우)에서 이 컬렉션을 참조하는 다른 코드를 호출하거나 병렬 실행한다면 컬렉션을 사용하는 도중에 다른 컬렉션이 그 컬렉션의 내용을 변경하는 상황이 생길 수 있고, 이런 상황에서는 ConcurrentModificationException이나 다른 오류가 발생할 수 있다.<ul>
<li>따라서 읽기 전용 컬렉션이 항상 스레드 안전하지는 않다는 점을 명시해야 한다.</li>
</ul>
</li>
<li>다중 스레드 환경에서 데이터를 다루는 경우 그 데이터를 적절히 동기화하거나 동시 접근을 허용하는 데이터 구조를 활용해야 한다.</li>
</ul>
<h3 id="코틀린-컬렉션">코틀린 컬렉션</h3>
<ul>
<li>모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스의 인스턴스라는 점은 사실이다.</li>
<li>코틀린의 읽기 전용과 변경 가능 인터페이스의 기본 구조는 java.util 패키지에 있는 자바 컬렉션 인터페이스의 구조를 그대로 옮겨 놓았다. 추가로 변경 가능한 각 인터페이스는 자신과 대응하는 읽기 전용 인터페이스를 확장(상속)한다.</li>
<li>변경 가능한 인터페이스는 java.util 패키지에 있는 인터페이스와 직접적으로 연관되지만 읽기 전용 인터페이스에는 컬렉션을 변경할 수 있는 모든 요소가 빠져있다.</li>
</ul>
<h4 id="컬렉션-생성-함수">컬렉션 생성 함수</h4>
<table>
  <tr>
    <th>컬렉셔 타입</th>
    <th>읽기 전용 타입</th>
    <th>변경 가능 타입</th>
  </tr>
  <tr>
    <td>List</td>
    <td>listOf</td>
    <td>mutableListOf, arrayListOf</td>
  </tr>
  <tr>
    <td>Set</td>
    <td>setOf</td>
    <td>mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf</td>
  </tr>
  <tr>
    <td>Map</td>
    <td>mapOf</td>
    <td>mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf</td>
  </tr>
</table>

<pre><code class="language-kotlin">fun uppercaseAll(items: MutableList&lt;String&gt;): List&lt;String&gt; {
    for (i in 0 until items.size) {
        items[i] = items[i].toUpperCase()
    }
    return items
}

fun printInUppercase(list: MutableList&lt;String&gt;) {
    println(uppercaseAll(list)) // [A, B, C]
    println(list.first()) // A
}

fun main() {
    val list = mutableListOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
    printInUppercase(list)
}</code></pre>
<h3 id="객체의-배열과-원시-타입의-배열">객체의 배열과 원시 타입의 배열</h3>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    // 배열의 인덱스 값의 범위에 대해 이터레이션하기 위해
    // array.indices 확장 함수를 사용한다.
    for (i in args.indices) {
        // array[index]로 인덱스를 사용해 배열 원소에 접근한다.
        println(&quot;Argument $i is: ${args[i]}&quot;)
    }
}</code></pre>
<h4 id="코틀린에서-배열을-만드는-방법">코틀린에서 배열을 만드는 방법</h4>
<ul>
<li><p>arrayOf 함수에 원소를 넘기면 배열을 만들 수 있다.</p>
<pre><code class="language-kotlin">fun main() {
  // 문자열 배열을 생성하고 초기화
  val stringArray = arrayOf(&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;)
  println(stringArray.joinToString()) // apple, banana, orange

  // 정수 배열을 생성하고 초기화
  val intArray = arrayOf(1, 2, 3, 4, 5)
  println(intArray.joinToString()) // 1, 2, 3, 4, 5

  // 혼합 타입 배열을 생성하고 초기화
  val mixedArray = arrayOf(&quot;apple&quot;, 2, true)
  println(mixedArray.joinToString()) // apple, 2, true
} </code></pre>
</li>
<li><p>arrayOfNulls 함수에 정수 값을 인자로 넘기면 모든 원소가 null이고 인자로 넘긴 값과 크기가 같은 배열을 만들 수 있다.</p>
<ul>
<li><p>물론 원소 타입이 널이 될 수 있는 타입인 경우에만 이 함수를 쓸 수 있다.</p>
<pre><code class="language-kotlin">// 크기가 3이고 모든 원소가 null인 배열을 생성
val nullableArray = arrayOfNulls&lt;String&gt;(3)
println(nullableArray.joinToString()) // null, null, null

// 크기가 5이고 모든 원소가 null인 배열을 생성
val anotherNullableArray = arrayOfNulls&lt;Int&gt;(5)
println(anotherNullableArray.joinToString()) // null, null, null, null, null
}</code></pre>
</li>
</ul>
</li>
<li><p>Array 생성자는 배열 크기와 람다를 인자로 받아서 람다를 호출해서 각 배열 원소를 초기화해주다.</p>
<ul>
<li>arrayOf를 쓰지 않고 각 원소가 널이 아닌 배열을 만들어야 하는 경우 이 생성자를 사용한다.<pre><code class="language-kotlin">// 알파벳으로 이뤄진 배열 만들기
// val letters = Array&lt;String&gt;(26) { i -&gt; (&#39;a&#39; + i).toString() }
val letters = Array(26) { (&#39;a&#39; + it).toString() }
println(letters.joinToString(&quot;&quot;)) // abcdefghijklmnopqrstuvwxyz</code></pre>
</li>
</ul>
</li>
<li><p>람다는 배열 원소의 인덱스를 인자로 받아서 배열의 해당 위치에 들어갈 원소를 반환한다.</p>
</li>
<li><p>코틀린에서는 배열을 인자로 받는 자바 함수를 호출하거나 vararg 파라미터를 받는 코틀린 함수를 호출하기 위해 가장 자주 배열을 만든다.</p>
<pre><code class="language-kotlin">// 컬렉션을 vararg 메소드에게 넘기기
val strings = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
// vararg 인자를 넘기기 위해 스프레드 연산자(*)를 써야 한다.
println(&quot;%s/%s/%s&quot;.format(*strings.toTypedArray())) // a/b/c</code></pre>
</li>
<li><p>코틀린은 원시 타입의 배열을 표현하는 별도 클래스를 각 원시 타입마다 하나씩 제공한다.</p>
<h4 id="원시-타입의-배열을-만드는-방법">원시 타입의 배열을 만드는 방법</h4>
</li>
<li><p>각 배열 타입의 생성자는 size 인자를 받아서 해당 원시 타입의 디폴트 값(보통은 0)으로 초기화된 size 크기의 배열을 반환한다.</p>
<pre><code class="language-kotlin">fun main() {
  // Int 배열
  val intArray = IntArray(5)
  println(intArray.joinToString()) // 0, 0, 0, 0, 0

  // Double 배열
  val doubleArray = DoubleArray(3)
  println(doubleArray.joinToString()) // 0.0, 0.0, 0.0

  // Char 배열
  val charArray = CharArray(4)
  println(charArray.joinToString()) // \u0000, \u0000, \u0000, \u0000

  // Boolean 배열
  val booleanArray = BooleanArray(2)
  println(booleanArray.joinToString()) // false, false
} </code></pre>
</li>
<li><p>팩토리 함수(IntArray를 생성하는 intArrayOf 등)는 여러 값을 가변 인자로 받아서 그런 값이 들어간 배열을 반환한다.</p>
<pre><code class="language-kotlin">fun main() {
  // Int 배열을 생성하고 초기화
  val intArray = intArrayOf(1, 2, 3, 4, 5)
  println(intArray.joinToString()) // 1, 2, 3, 4, 5

  // Double 배열을 생성하고 초기화
  val doubleArray = doubleArrayOf(1.5, 2.0, 3.5)
  println(doubleArray.joinToString()) // 1.5, 2.0, 3.5

  // Char 배열을 생성하고 초기화
  val charArray = charArrayOf(&#39;a&#39;, &#39;b&#39;, &#39;c&#39;)
  println(charArray.joinToString()) // a, b, c

  // Boolean 배열을 생성하고 초기화
  val booleanArray = booleanArrayOf(true, false, true)
  println(booleanArray.joinToString()) // true, false, true
} </code></pre>
</li>
<li><p>(일반 배열과 마찬가지로) 크기와 람다를 인자로 받는 생성자를 사용한다.</p>
<pre><code class="language-kotlin">fun main() {
  val squares = IntArray(5) { i -&gt; (i + 1) * (i + 1) }
  println(squares.joinToString()) // 1, 4, 9, 16, 25
}</code></pre>
</li>
<li><p>이 밖에 박싱된 값이 들어있는 컬렉션이나 배열이 있다면 toIntArray 등의 변환 함수를 사용해 박싱하지 않은 값이 들어있는 배열로 변환할 수 있다.</p>
<pre><code class="language-kotlin">fun main() {
  // 박싱된 값이 들어있는 리스트
  val boxedList: List&lt;Int?&gt; = listOf(1, 2, null, 4, 5)

  // toIntArray를 사용하여 박싱하지 않은 값이 들어있는 배열로 변환
  val intArray: IntArray = boxedList.filterNotNull().toIntArray()

  for (value in intArray) {
      println(value)
  }
}</code></pre>
</li>
<li><p>코틀린 표준 라이브러리는 배열 기본 연산(배열 길이 구하기, 원소 설정하기, 원소 읽기)에 더해 컬렉션에 사용할 수 있는 모든 확장 함수를 배열에도 제공한다.</p>
<ul>
<li>원시 타입인 원소로 이뤄진 배열에도 그런 확장 함수를 똑같이 사용할 수 있다(다만 이런 함수가 반환하는 값은 배열이 아니라 리스트라는 점에 유의하라).</li>
</ul>
</li>
<li><p>forEachIndexed : 배열의 모든 원소를 갖고 인자로 받은 람다를 호출해준다. 이때 배열의 원소와 그 원소의 인덱스를 람다에게 인자로 전달한다.</p>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
  args.forEachIndexed { index, element -&gt;  
      println(&quot;Argument $index is: $element&quot;)
  }
} </code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 5장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-5%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-5%EC%9E%A5</guid>
            <pubDate>Mon, 13 Nov 2023 05:28:25 GMT</pubDate>
            <description><![CDATA[<h1 id="람다로-프로그래밍">람다로 프로그래밍</h1>
<blockquote>
<ul>
<li>람다 식과 멤버 참조</li>
<li>함수형 스타일로 컬렉션 다루기</li>
<li>시퀀스: 지연 컬렉션 연산</li>
<li>자바 함수형 인터페이스를 코틀린에서 사용</li>
<li>수신 객체 지정 람다 사용</li>
</ul>
</blockquote>
<hr>
<ul>
<li>람다 식 또는 람다 : 다른 함수에 넘길 수 있는 작은 코드 조각</li>
<li>람다를 사용하면 쉽게 공통 코드 구조를 라이브러리 함수로 뽑아낼 수 있다. 코틀린 표준 라이브러리는 람다를 아주 많이 사용한다. 람다를 자주 사용하는 경우로 컬렉션 처리를 들 수 있다.</li>
<li>수신 객체 람다(lambda with receiver)는 특별한 람다로, 람다 선언을 둘러싸고 있는 환경과는 다른 상황에서 람다 본문을 실행할 수 있다.</li>
</ul>
<h2 id="람다-식과-멤버-참조">람다 식과 멤버 참조</h2>
<blockquote>
<p>💡 익명 함수를 간단히 표현하는 함수로서, 값처럼 여러 곳에 전달할 수 있는 동작의 모음이다.</p>
</blockquote>
<h3 id="람다-소개-코드-블록을-함수-인자로-넘기기">람다 소개: 코드 블록을 함수 인자로 넘기기</h3>
<ul>
<li>&quot;이벤트가 발생하면 이 핸들러를 실행하자&quot;나 &quot;데이터 구조의 모든 원소에 이 연산을 적용하자&quot;와 같은 생각을 코드로 표현하기 위해 일련의 동작을 변수에 저장하거나 다른 함수에 넘겨야 하는 경우가 자주 있다.</li>
</ul>
<ul>
<li>자바는 무명 내부 클래스를 사용하여 코드를 함수에 넘기거나 변수에 저장할 수 있기는 하지만 상당히 번거롭다.<pre><code class="language-java">// 무명 내부 클래스로 리스너 구현하기
button.setOnClickListener(new OnClickListener() { 
  @Override
  public void onClick(View view) {
      /* 클릭 시 수행할 동작 */
  }   
})</code></pre>
<pre><code class="language-kotlin">button.setOnClickListener(object : View.OnClickListener {
  override fun onClick(view: View) {
      /* 클릭 시 수행할 동작 */
  }
})</code></pre>
</li>
<li>함수형 프로그래밍에서는 함수를 값처럼 다루는 접근 방법을 택함으로써 이 문제를 해결한다.<ul>
<li><span style='background-color: #fff5b1'/>클래스를 선언하고 그 클래스의 인스턴스를 함수에 넘기는 대신 함수형 언어에서는 함수를 직접 다른 함수에 전달할 수 있다.</li>
<li>람다 식을 사용하면 코드가 더욱 더 간결해진다. 함수를 선언할 필요가 없고 코드 블록을 직접 함수의 인자로 전달할 수 있다.<pre><code class="language-kotlin">// 람다로 리스너 구현하기
button.setOnClickListener { /* 클릭 시 수행할 동작 */ }</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="람다와-컬렉션">람다와 컬렉션</h3>
<ul>
<li>컬렉션을 직접 검색하기<pre><code class="language-kotlin">data class Person(val name: String, val age: Int)
</code></pre>
</li>
</ul>
<p>fun findTheOldest(people: List<Person>) {
    var maxAge = 0 // 가장 많은 나이를 저장한다.
    var theOldest: Person? = null // 가장 연장자인 사람을 저장한다.
    for (person in people) {
        if(person.age &gt; maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}</p>
<p>val people = list(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))
findTheOldest(people) // Person(name=Bob, age=31)</p>
<pre><code>- 람다를 사용해 컬렉션 검색하기
```kotlin
val people = listOf(Person(&quot;Alice&quot; 29), Person(&quot;Bob&quot;, 31))
println(people.maxBy { it.age }) // 나이 프로퍼티를 비교해서 값이 가장 큰 원소 찾기
// 결과: Person(name=Bob, age=31)</code></pre><ul>
<li>모든 컬렉션에 대해 maxBy 함수를 호출할 수 있다.</li>
<li>maxBy는 가장 큰 원소를 찾기 위해 비교에 사용할 값을 돌려주는 함수를 인자로 받는다.</li>
<li>중괄호로 둘러싸인 코드 <code>{ it.age }</code>는 비교에 사용할 값을 돌려주는 함수다.</li>
<li>이 예제에서는 컬렉션의 원소가 Person 객체이므로 이 함수가 반환하는 값은 Person 객체의 age 필드에 저장된 나이 정보다.</li>
</ul>
<ul>
<li>멤버 참조를 사용해 컬렉션 검색하기<pre><code class="language-kotlin">people.maxBy(Person::age)</code></pre>
</li>
</ul>
<h3 id="람다-식의-문법">람다 식의 문법</h3>
<ul>
<li>람다는 값처럼 여기저기 전달할 수 있는 동작의 모음이다. 람다를 따로 선언해서 변수에 저장할 수도 있다. 함수에 인자로 넘기면서 바로 람다를 정의하는 경우가 대부분이다.</li>
<li>코틀린 람다 식은 항상 중괄호로 둘러싸여 있다. 인자 목록 주변에 괄호가 없다는 사실을 기억하라. 화살표(-&gt;)가 인자 목록과 람다 본문을 구분해준다.<pre><code class="language-kotlin">val sum = { x: Int, y: Int -&gt; x + y }
println(sum(1, 2)) // 3
</code></pre>
</li>
</ul>
<p>{ println(42) }() // 42</p>
<pre><code>
&gt; 굳이 람다를 만들자마자 바로 호출하느니 람다 본문을 직접 실행하는 편이 낫다. 이렇게 코드의 일부분을 블록으로 둘러싸 실행할 필요가 있다면 run을 사용한다.

&gt; ✅ **람다 본문을 직접 실행하는 편이 낫다?** : 람다를 만들면 객체가 생성되어 가비지 컬렉션의 대상이 된다. 그러나 바로 실행하면 객체가 생성되지 않으므로 가비지 컬렉션 부하가 감소한다.

- [run](https://velog.io/@dev-baik/%EB%B2%94%EC%9C%84-%EC%A7%80%EC%A0%95-%ED%95%A8%EC%88%98#run-%ED%95%A8%EC%88%98)은 인자로 받은 람다를 실행해주는 라이브러리 함수다.
```kotlin
run { println(42) } // 42 </code></pre><ul>
<li>코틀린이 코드를 줄여 쓸 수 있게 제공했던 기능을 제거하고 정식으로 람다를 작성하면 다음과 같다.<pre><code class="language-kotlin">people.maxBy({ p: Person -&gt; p.age }) </code></pre>
</li>
<li>여기서 어떤 일이 벌어지고 있는지 더 명확히 알 수 있다. 하지만 이 코드는 번잡하다.<ul>
<li>구분자가 너무 많이 쓰여서 가독성이 떨어지고, 컴파일러가 문맥으로부터 유추할 수 있는 인자 타입을 굳이 적을 필요는 없다.</li>
<li>인자가 단 하나뿐인 경우 굳이 인자에 이름을 붙이지 않아도 된다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>코틀린에서는 함수 호출 시 맨 뒤에 있는 인자가 람다 식이라면 그 람다를 괄호 밖으로 빼낼 수 있다는 문법 관습이 있다.</p>
</blockquote>
<ul>
<li><p>이 예제에서는 람다가 유일한 인자이므로 마지막 인자이기도 하다. 따라서 괄호 뒤에 람다를 둘 수 있다.</p>
<pre><code class="language-kotlin">people.maxBy() { p: Person -&gt; p.age } </code></pre>
</li>
<li><p>람다가 어떤 함수의 유일한 인자이고 괄호 뒤에 람다를 썻다면 호출 시 빈 괄호를 없애도 된다.</p>
<pre><code class="language-kotlin">people.maxBy { p: Person -&gt; p.age } </code></pre>
</li>
<li><p>인자가 여럿 있는 경우에는 람다를 밖으로 빼낼 수도 있고 람다를 괄호 안에 유지해서 함수의 인자임을 분명히 할 수 있다. 두 방식 모두 정당하다.</p>
</li>
<li><p>둘 이상의 람다를 인자로 받는 함수라고 해도 인자 목록의 맨 마지막 람다만 밖으로 뺄 수 있다. 따라서 그런 경우에는 괄호를 사용하는 일반적인 함수 호출 구문을 사용하는 편이 낫다.</p>
<pre><code class="language-kotlin">fun operateOnNumbers(a: Int, b: Int, operation1: (Int, Int) -&gt; Int, operation2: (Int, Int) -&gt; Unit) {
  val result = operation1(a, b)
  println(&quot;Result of the operation: $result&quot;)

  operation2(a, b)
}
</code></pre>
</li>
</ul>
<p>fun main() {
    // trailing lambda syntax를 이용하여 두 번째 람다를 함수 호출 밖으로 뺄 수 있음
    operateOnNumbers(5, 3 ,{ x, y -&gt; x + y }) { x, y -&gt; println(&quot;Numbers: $x, $y&quot;) }</p>
<pre><code>// 일반적인 함수 호출 구문을 사용하여 두 람다를 전달
operateOnNumbers(5, 3,
    { x, y -&gt; x + y },
    { x, y -&gt; println(&quot;Numbers: $x, $y&quot;) }
)</code></pre><p>} </p>
<pre><code>
- 표준 라이브러리의 joinToString은 맨 마지막 인자로 함수를 더 받는다는 차이가 있다.
  - 리스트의 원소를 toString이 아닌 다른 방식을 통해 문자열로 변환하고 싶은 경우 이 인자를 활용한다.
```kotlin
// 이름 붙인 인자를 사용해 람다 넘기기
val people = listOf(Person(&quot;이몽룡&quot;, 29), Person(&quot;성춘향&quot;, 31))
val name = people.joinToString(separator = &quot; &quot;,
                    transform = { p: Person -&gt; p.name })
println(names) // 이몽룡 성춘향</code></pre><ul>
<li><p>이름 붙인 인자를 사용해 람다를 넘김으로써 람다를 어떤 용도로 쓰는 지 더 명확히 했다.</p>
<pre><code class="language-kotlin">// 람다를 괄호 밖에 전달하기
people.joinToString(&quot; &quot;) { p.person -&gt; p.name }</code></pre>
</li>
<li><p>더 간결하지만 람다의 용도를 분명히 알아볼 수는 없다.</p>
<pre><code class="language-kotlin">// 람다 파라미터 타입 제거하기
people.maxBy { p: Person -&gt; p.age } // 파라미터 타입을 명시
people.maxBy { p -&gt; p.age } // 파라마티 타입을 생략(컴파일러가 추론)</code></pre>
</li>
<li><p>로컬 변수처럼 컴파일러는 람다 파라미터의 타입도 추론할 수 있다. 따라서 파라미터 타입을 명시할 필요가 없다.</p>
<pre><code class="language-kotlin">// 디폴트 파라미터 이름 it 사용하기
people.maxBy { it.age }</code></pre>
<blockquote>
<p>it을 사용하는 관습은 코드를 아주 간단하게 만들어준다. 하지만 이를 남용하면 안된다. 특히 람다 안에 람다가 중첩되는 경우 각 람다의 파라미터를 명시하는 편이 낫다. 파라미터를 명시하지 않으면 각각의 it이 가리키는 파라미터가 어떤 람다에 속했는지 파악하기 어려울 수 있다. 문맥에서 람다 파라미터의 의미나 파라미터의 타입을 쉽게 알 수 없는 경우에도 파라미터를 명시적으로 선언하면 도움이 된다.</p>
</blockquote>
</li>
<li><p>람다를 변수에 저장할 때는 파라미터의 타입을 추론할 문맥이 존재하지 않는다. 따라서 파라미터 타입을 명시해야 한다.</p>
<pre><code class="language-kotlin">val getAge = { p: Person -&gt; p.age }
people.maxBy(getAge)</code></pre>
</li>
<li><p>본문이 여러 줄로 이뤄진 경우 본문의 맨 마지막에 있는 식이 람다의 결과 값이 된다.</p>
<pre><code class="language-kotlin">val sum = { x: Int, y: Int -&gt;
  println(&quot;Computing the sum of $x and $y...&quot;)
  x + y
}
println(sum(1, 2))
</code></pre>
</li>
</ul>
<p>// Computingthe sum of 1 and 2...
// 3</p>
<pre><code>
### 현재 영역에 있는 변수에 접근
- 람다를 함수 안에서 정의하면 파라미터뿐 아니라 람다 정의의 앞에 선언된 로컬 변수까지 람다에서 모두 사용할 수 있다.
- forEach는 컬렉션의 모든 원소에 대해 람다를 호출해준다. 일반적으로 for 루프보다 훨씬 간결하지만 그렇다고 다른 장점이 많지는 않아 기존 for 루프를 forEach로 모두 바꿀 필요는 없다.
```kotlin
// 함수 파라미터를 람다 안에서 사용하기
fun printMessagesWithPrefix(messages: Collection&lt;String&gt;, prefix: String) {
    messages.forEach { // 각 원소에 대해 수행할 작업을 람다로 받는다.
        println(&quot;$prefix $it&quot;) // 람다 안에서 함수의 &quot;prefix&quot; 파라미터를 사용한다.
    }
}

val errors = listOf(&quot;403 Forbidden&quot;, &quot;404 Not Found&quot;)
printMessagesWithPrefix(errors, &quot;Error:&quot;)
// Error: 403 Forbidden
// Error: 404 Not Found</code></pre><ul>
<li><p>코틀린에서는 자바와 달리 람다에서 람다 밖 함수에 있는 파이널이 아닌 변수에 접근할 수 있고, 그 변수를 변경할 수 있다.</p>
<pre><code class="language-kotlin">fun printProblemCount(responses: Collection&lt;String&gt;) {
  var clientErrors = 0
  var serverErrors = 0

  responses.forEach {
      if (it.startsWith(&quot;4&quot;)) {
          clientErrors++
      } else if (it.startsWith(&quot;5&quot;)) {
          serverErrors++
      }
  }
  println(&quot;$clientErrors client errors, $serverErrors server errors&quot;)
}
</code></pre>
</li>
</ul>
<p>val response = listOf(&quot;200 OK&quot;, &quot;418 I&#39;m a teapot&quot;, &quot;500 Internal Server Error&quot;)
printProblemCount(responses) // 1 client errors, 1 server errors</p>
<pre><code>- 이 예제의 prefix, clientErrors, serverErrors와 같이 람다 안에서 사용하는 외부 변수를 `람다가 포획한 변수`라고 부른다.
- 기본적으로 함수 안에 정의된 로컬 변수의 생명주기는 함수가 반환되면 끝난다. 하지만 &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;어떤 함수가 자신의 &#39;로컬 변수를 포획한 람다&#39;를 반환하거나 다른 변수에 저장한다면 로컬 변수의 생명주기와 함수의 생명주기가 달라질 수 있다.
  - **클로저** : 람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 구조다. 이 때 람다식 안에 있는 외부 변수는 값을 유지하기 위해 &#39;클로저가 포획한 변수&#39;라고 부른다.
    - 실행 시점에서 람다식의 모든 참조가 포함된 닫힌(closed) 객체를 람다 코드와 함께 저장한다. 이때 이러한 데이터 구조를 클로저(closure)라고 부르는 것이다. 
```kotlin
fun createClosure(): () -&gt; Unit {
    var counter = 0

    val closure: () -&gt; Unit = {
        println(&quot;Counter inside lambda: $counter&quot;)
        counter++
    }

    return closure
}

fun main() {
    val closureInstance = createClosure()

    closureInstance() // Counter inside lambda: 0
    closureInstance() // Counter inside lambda: 1

    // 외부 변수 counter의 생명주기가 끝났지만, 여전히 람다가 참조하고 있음
    closureInstance() // Counter inside lambda: 2
}</code></pre><ul>
<li><p><span style='background-color: #fff5b1'/>람다를 이벤트 핸들러나 다른 비동기적으로 실행되는 코드로 활용하는 경우 함수 호출이 끝난 다음에 로컬 변수가 변경될 수 있다.</p>
<pre><code class="language-kotlin">fun main() = runBlocking {
  var counter = 0

  // 비동기적으로 값을 변경하는 코루틴 런치
  val job = launch {
      delay(1000) // 시간이 오래 걸리는 작업 시뮬레이션
      counter = 42
  }

  // 함수 호출이 끝난 후에도 코루틴이 실행되고 로컬 변수가 변경될 수 있음
  println(&quot;Counter before job join: $counter&quot;)

  job.join() // 코루틴이 끝날 때까지 대기

  println(&quot;Counter after job join: $counter&quot;)
}
</code></pre>
</li>
</ul>
<p>// Counter before job join: 0
// Counter after job join: 42</p>
<pre><code>```kotlin
fun tryToCountButtonClicks(button: Button): Int {
    var clicks = 0
    button.onClick { clicks++ }
    return clicks
}</code></pre><ul>
<li>이 함수는 항상 0을 반환한다. onClick 핸들러는 호출될 때마다 clicks의 값을 증가시키지만 그 값의 변경을 관찰할 수는 없다. 핸들러는 tryToCountButtonClicks가 clicks를 반환한 다음에 호출되기 때문이다.<ul>
<li>이 함수를 제대로 구현하려면 클릭 횟수를 세는 카운터 변수를 함수의 내부가 아니라 클래스의 프로퍼티나 전역 프로퍼티 등의 위치로 빼내서 나중에 변수 변화를 살펴볼 수 있게 해야 한다.</li>
</ul>
</li>
</ul>
<h3 id="멤버-참조">멤버 참조</h3>
<blockquote>
<p>💡 특정 객체의 메소드나 프로퍼티를 참조하여 호출할 수 있는 함수를 생성하는 기능이다. 이를 통해 해당 멤버에 대한 참조를 함수처럼 전달하거나 저장할 수 있다.</p>
</blockquote>
<ul>
<li>코틀린에서는 함수를 값으로 바꿀 수 있는 <code>이중 콜론(::)</code>을 사용한다.<pre><code class="language-kotlin">val getAge = { person: Person -&gt; person.age }</code></pre>
<pre><code class="language-kotlin">val getAge = Person::age </code></pre>
</li>
<li><code>::</code>를 사용하는 식을 <code>멤버 참조</code>라고 부른다. 멤버 참조는 프로퍼티나 메소드를 단 하나만 호출하는 함수 값을 만들어준다.</li>
<li>클래스 이름과 여러분이 참조하려는 멤버(프로퍼티나 메소드) 이름 사이에 위치한다.</li>
<li>참조 대상이 함수인지 프로퍼티인지와는 관계없이 멤버 참조 뒤에는 괄호를 넣으면 안된다.</li>
<li>멤버 참조는 그 멤버를 호출하는 람다와 같은 타입이다<pre><code class="language-kotlin">people.maxBy(Person::age)
people.maxBy { p -&gt; p.age }
people.maxBy { it.age }</code></pre>
</li>
<li>최상위에 선언된(그리고 다른 클래스의 멤버가 아닌) 함수나 프로퍼티를 참조할 수도 있다.<pre><code class="language-kotlin">val salute = &quot;Salute!&quot;
</code></pre>
</li>
</ul>
<p>fun greet(name: String): String {
    return &quot;Hello, $name!&quot;
}</p>
<p>fun main() {
    val propertyReference: () -&gt; String = ::salute
    val result1 = propertyReference()</p>
<pre><code>println(result1)  // Salute!

val greetFunction: (String) -&gt; String = ::greet
val result2 = greetFunction(&quot;Alice&quot;)

println(result2)  // Hello, Alice!</code></pre><p>}</p>
<pre><code>- 클래스 이름을 생략하고 ::로 참조를 바로 시작한다. ::salute라는 멤버 참조를 run 라이브러리 함수에 넘긴다(run은 인자로 받은 람다를 호출한다).
```kotlin
fun salute() = println(&quot;Salute!&quot;)

fun main() {
    run(::salute) // Salute!
}</code></pre><ul>
<li>람다가 인자가 여럿인 다른 함수한테 작업을 위임하는 경우, 람다를 정의하지 않고 직접 위임 함수에 대한 참조를 제공하면 편리하다<pre><code class="language-kotlin">data class Person(val name: String, val email: String)
</code></pre>
</li>
</ul>
<p>fun sendEmail(person: Person, message: String) {
    println(&quot;Sending email to ${person.name} at ${person.email}: $message&quot;)
}</p>
<p>fun delegateOperation(person: Person, message: String, operation: (Person, String) -&gt; Unit) {
    operation(person, message)
}</p>
<p>fun main() {
    // 람다를 사용하여 sendEmail 함수에 작업을 위임
    val action: (Person, String) -&gt; Unit = { person, message -&gt;
        sendEmail(person, message)
    }</p>
<pre><code>// 멤버 참조를 사용하여 sendEmail 함수에 작업을 위임
val nextAction: (Person, String) -&gt; Unit = ::sendEmail

// Person 객체 생성
val alice = Person(&quot;Alice&quot;, &quot;alice@example.com&quot;)

// 작업을 위임한 함수 호출
delegateOperation(alice, &quot;Hello from lambda&quot;, action)
delegateOperation(alice, &quot;Hello from member reference&quot;, nextAction)</code></pre><p>} </p>
<pre><code>```kotlin
data class Person(val name: String, val email: String)

fun sendEmail(person: Person, message: String) {
    println(&quot;Sending email to ${person.name} at ${person.email}: $message&quot;)
}

fun main() {
    // 람다를 사용하여 sendEmail 함수에 작업을 위임
    val action = { person: Person, message: String -&gt;
        sendEmail(person, message)
    }

    // 멤버 참조를 사용하여 sendEmail 함수에 작업을 위임
    val nextAction = ::sendEmail

    // Person 객체 생성
    val alice = Person(&quot;Alice&quot;, &quot;alice@example.com&quot;)

    // 작업을 위임한 함수 호출
    action(alice, &quot;Hello from lambda&quot;)
    nextAction(alice, &quot;Hello from member reference&quot;)
}

// Sending email to Alice at alice@example.com: Hello from lambda
// Sending email to Alice at alice@example.com: Hello from member reference </code></pre><ul>
<li><span style='background-color: #fff5b1'/>생성자 참조를 사용하면 클래스 생성 작업을 연기하거나 저장해둘 수 있다. :: 뒤에 클래스 이름을 넣으면 생성자 참조를 만들 수 있다.<pre><code class="language-kotlin">val createPerson = ::Person // &quot;Person&quot;의 인스턴스를 만드는 동작을 값으로 저장한다.
val p = createPerson(&quot;Alice&quot;, 29)
println(p) // Person(name=Alice, age=29)</code></pre>
</li>
<li>확장 함수도 멤버 함수와 똑같은 방식으로 참조할 수 있다는 점을 기억하라.<pre><code class="language-kotlin">fun Person.isAdult() = age &gt;= 21
val predicate = Person::isAdult</code></pre>
</li>
<li>isAdult는 Person 클래스의 멤버가 아니고 확장 함수다. 그렇지만 isAdult를 호출할 때 person.isAdult()로 인스턴스 멤버 호출 구문을 쓸 수 있는 것처럼 Person::isAdult로 멤버 참조 구문을 사용해 이 확장 함수에 대한 참조를 얻을 수 있다.</li>
</ul>
<h4 id="바운드-멤버-참조">바운드 멤버 참조</h4>
<ul>
<li>코틀린 1.0에서는 클래스의 메소드나 프로퍼티에 대한 참조를 얻은 다음에 그 참조를 호출할 때 항상 인스턴스 객체를 제공해야 했다.</li>
<li>코틀린 1.1부터는 바운드 멤버 참조를 지원한다.<ul>
<li><span style='background-color: #fff5b1'/>바운드 멤버 참조를 사용하면 멤버 참조를 생성할 때 클래스 인스턴스를 함께 저장한 다음 나중에 그 인스턴스에 대해 멤버를 호출해준다. 따라서 호출 시 수신 대상 객체를 별도로 지정해 줄 필요가 없다.<pre><code class="language-kotlin">val p = Person(&quot;Dmitry&quot;, 34)
val personsAgeFunction = Person::age
println(personsAgeFunction(p))
</code></pre>
</li>
</ul>
</li>
</ul>
<p>val dmitrysAgeFunction = p::age
println(dmitrysAgeFunction())</p>
<pre><code>
&gt; personsAgeFunction은 인자가 하나(인자로 받은 사람의 나이를 반환)이지만, dmitrysAgeFunction은 인자가 없는(참조를 만들 때 p가 가리키던 사람의 나이를 반환) 함수라는 점에 유의하라.

- 코틀린 1.0에서는 p::age 대신에 { p.age }라고 직접 객체의 프로퍼티를 돌려주는 람다를 만들어야만 한다.

## 컬렉션 함수형 API
- 함수형 프로그래밍 스타일을 사용하면 컬렉션을 다룰 때 편리하다. 대부분의 작업에 라이브러리 함수를 활용할 수 있고 그로 인해 코드를 아주 간결하게 만들 수 있다.

### 필수적인 함수: filter와 map
- filter와 map은 컬렉션을 활용할 때 기반이 되는 함수다. 대부분의 컬렉션 연산을 이 두 함수를 통해 표현할 수 있다.

&gt; 함수형 프로그래밍에는 람다나 다른 함수를 인자로 받거나 함수를 반환하는 함수를 `고차 함수(HOF, Higt Order Function)`라고 부른다. 고차함수는 기본 함수를 조합해서 새로운 연산을 정의하거나, 다른 고차 함수를 통해 조합된 함수를 또 조합해서 더 복잡한 연산을 쉽게 정의할 수 있다는 장점이 있다. 이런 식으로 고차 함수와 단순한 함수를 이리저리 조합해서 코드를 작성하는 기법을 `컴비네이터 패턴(combinator pattern)`이라 부르고, 컴비네이터 패턴에서 복잡한 연산을 만들기 위해 값이나 함수를 조합할 때 사용하는 고차 함수를 `컴비네이터`라고 부른다.

- filter 함수는 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모은다.
```kotlin
data class Person(val name: String, val age: Int)

val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 }) // [2, 4]</code></pre><ul>
<li>결과는 입력 컬렉션의 원소 중에서 주어진 술어를 만족하는 원소만으로 이뤄진 새로운 컬렉션이다.<pre><code class="language-kotlin">val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))
println(people.filter { it.age &gt; 30 }) // [Person(name=Bob, age=31)]</code></pre>
</li>
</ul>
<blockquote>
<p>✅ <strong>술어(predicate)</strong> : 참/거짓을 반환하는 함수</p>
</blockquote>
<ul>
<li>filter 함수는 컬렉션에서 원치 않는 원소를 제거하지만, 원소를 변환할 수 없다.</li>
<li>map 함수는 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만든다.<pre><code class="language-kotlin">val list = listOf(1, 2, 3, 4)
println(list.map { it * it }) // [1, 4, 9, 16 ]</code></pre>
<pre><code class="language-kotlin">// 사람 이름의 리스트 출력
val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))
println(people.map { it.name }) // [Alice, Bob]</code></pre>
<pre><code class="language-kotlin">// 30살 이상인 사람의 이름 출력 // 멤버 참조 사용
// people.filter({it.age &gt; 30}).map(Person::name)
people.filter { it.age &gt; 30 }.map(Person::name)</code></pre>
<pre><code class="language-kotlin">// 가장 나이 많은 사람의 이름(들)
people.filter { it.age == people.maxBy(Person::age)!!.age }</code></pre>
</li>
<li>단점 : 최댓값을 구하는 작업을 계속 반복<pre><code class="language-kotlin">val maxAge = people.maxBy(Person::age)!!.age
people.filter { it.age == maxAge }</code></pre>
</li>
</ul>
<blockquote>
<p>꼭 필요하지 않은 경우 굳이 계산을 반복하지 말라! 람다를 인자로 받는 함수에 람다를 넘기면 겉으로 볼 때는 단순해 보이는 식이 내부 로직의 복잡도로 인해 실제로는 엄청나게 불합리한 계산식이 될 수 때가 있다.</p>
</blockquote>
<pre><code class="language-kotlin">val numbers = mapOf(0 to &quot;zero&quot;, 1 to &quot;one&quot;)
println(numbers.mapValues { it.value.toUpperCase() }) {0=ZERO, 1=ONE}</code></pre>
<ul>
<li>맵의 경우 키와 값을 처리하는 함수가 따로 존재한다. filterKeys와 mapKeys는 키를 걸러 내거나 반환하고, filterValues와 mapValues는 값을 걸러 내거나 반환한다.</li>
</ul>
<h3 id="all-any-count-find-컬렉션에-술어-적용">all, any, count, find: 컬렉션에 술어 적용</h3>
<ul>
<li>컬렉션에 대해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건을 만족하는 지 판단하는(또는 그 변종으로 컬렉션 안에 어떤 조건을 만족하는 원소가 있는지 판단하는) all과 any 연산이 있다.</li>
<li>count 함수는 조건을 만족하는 원소의 개수를 반환하며, find 함수는 조건을 만족하는 첫 번째 원소를 반환한다.<pre><code class="language-kotlin">// 모든 원소가 이 술어를 만족하는 지 궁금하다면 all 함수를 쓴다.
val canBeInClub27 = { p: Person -&gt; p.age &lt;= 27 }
</code></pre>
</li>
</ul>
<p>val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 31))
println(people.all(canBeInClub27)) // false</p>
<pre><code>```kotlin
// 술어를 만족하는 원소가 하나라도 있는지 궁금하다면 any를 쓴다.
println(people.any(canBeInClub27)) // true </code></pre><ul>
<li>어떤 조건에 대해 !all을 수행한 결과와 그 조건의 부정에 대해 any를 수행하는 결과는 같다(드 모르강의 법칙). 또 어떤 조건에 대해 !any를 수행한 결과와 그 조건의 부정에 대해 all을 수행한 결과도 같다.<pre><code class="language-kotlin">val list = listOf(1, 2, 3)
println(!list.all { it == 3 }) // true
</code></pre>
</li>
</ul>
<p>// 가독성을 높이려면 any와 all 앞에 !를 붙이지 않는 편이 낫다.
println(list.any { it != 3 }) // true</p>
<pre><code>```kotlin
// 술어를 만족하는 원소의 개수를 구하려면 count를 사용한다.
val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 31))
println(people.count(canBeInClub27)) // 1</code></pre><blockquote>
<p><strong>함수를 적재적소에 사용하라: count와 size</strong></p>
<p>count가 있다는 사실을 잊어버리고, 컬렉션을 필터링한 결과의 크기를 가져오는 경우가 있다.</p>
<pre><code class="language-kotlin">println(people.filter(canBeInClub27).size) // 1</code></pre>
<p>하지만 이렇게 처리하면 조건을 만족하는 모든 원소가 들어가는 <code>중간 컬렉션</code>이 생긴다. 반면 count는 조건을 만족하는 원소의 개수만을 추적하지 조건을 만족하는 원소를 따로 저장하지 않는다. 따라서 count가 훨씬 더 효율적이다.</p>
</blockquote>
<pre><code class="language-kotlin">// 술어를 만족하는 원소를 하나 찾고 싶으면 find 함수를 사용한다.
val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 31))
println(people.find(canBeInClub27)) // Person(name=Alice, age=27)</code></pre>
<ul>
<li>이 식은 조건을 만족하는 원소가 하나라도 있는 경우 가장 먼저 조건을 만족한다는 확인된 원소를 반환하며, 만족하는 원소가 전혀 없는 경우 null을 반환한다.</li>
<li>조건을 만족하는 원소가 없으면 null이 나온다는 사실을 더 명확히 하고 싶다면 <code>firstOrNull</code>을 쓸 수 있다.</li>
</ul>
<h3 id="groupby-리스트를-여러-그룹으로-이뤄진-맵으로-변경">groupBy: 리스트를 여러 그룹으로 이뤄진 맵으로 변경</h3>
<pre><code class="language-kotlin">val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 31), Person(&quot;Carol&quot;, 31))
println(people.groupBy { it.age })

// {29=[Person(name=Bob, age=29)],
// 31=[Person(name=Alice, age=31), Person(name=Carol, age=31)]}</code></pre>
<ul>
<li>각 그룹은 리스트다. 따라서 groupBy의 결과 타입은 <code>Map&lt;Int, List&lt;Person&gt;&gt;</code>이다. 필요하면 이 맵을 mapKeys나 mapValues 등을 사용해 변경할 수 있다.<pre><code class="language-kotlin">val list = listOf(&quot;a&quot;, &quot;ab&quot;, &quot;b&quot;)
println(list.groupBy(String::first)) // {a=[a, ab], b=[b]}</code></pre>
</li>
<li>first는 String의 멤버가 아니라 확장 함수지만, 여전히 멤버 참조를 사용해 first에 접근할 수 있다.</li>
</ul>
<h3 id="flapmap과-flatten-중첩된-컬렉션-안의-원소-처리">flapMap과 flatten: 중첩된 컬렉션 안의 원소 처리</h3>
<pre><code class="language-kotlin">class Book(val title: String, val authors: List&lt;String&gt;)

books.flatMap { it.authors }.toSet() // books 컬렉션에 있는 책을 쓴 모든 저자의 집합</code></pre>
<ul>
<li>flatMap 함수는 먼저 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고(또는 매핑하기(map)) 람다를 적용한 결과 얻어지는 여러 리스트를 한 리스트로 한데 모은다(또는 펼치기(flatten)).<pre><code class="language-kotlin">val strings = listOf(&quot;abc&quot;, &quot;def&quot;)
println(strings.flatMap { it.toList() }) // [a, b, c, d, e, f]</code></pre>
</li>
<li>toList 함수를 문자열에 적용하면 그 문자열에 속한 모든 문자로 이뤄진 리스트가 만들어진다.<pre><code class="language-kotlin">val books = listOf(Book(&quot;Thursday Next&quot;, listOf(&quot;Jasper Fforde&quot;)),
              Book(&quot;Mort&quot;, listOf(&quot;Terry Pratchett&quot;)), 
              Book(&quot;Good Omens&quot;, listOf(&quot;Terry Pratchett&quot;, &quot;Neil Gaiman&quot;)))
</code></pre>
</li>
</ul>
<p>println(books.flatMap { it.authors }.toSet())
// 결과: [Jasper Fforde, Terry Pratchett, Neil Gaiman]</p>
<pre><code>- toSet은 flatMap의 결과 리스트에서 중복을 없애고 집합을 만든다.
- 특별히 반환해야 할 내용이 없어 리스트의 리스트를 평평하게 펼치기만 하면 되는 경우 listOfLists.flatten()처럼 flatten 함수를 사용할 수 있다.

## 지연 계산(lazy) 컬렉션 연산
- map이나 filter 같은 몇가지 컬렉션 함수들은 결과 컬렉션을 즉시(eagerly) 생성한다.
  - &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;즉, 컬렉션 함수를 연쇄하면 매 단계마다 계산 중간 결과를 새로운 컬렉션에 임시로 담는다는 말이다.
- **시퀀스**를 사용하면 중간 임시 컬렉션을 사용하지 않고도 컬렉션 연산을 연쇄할 수 있다.
```kotlin
people.map(Person::name).filter { it.startsWith(&quot;A&quot;) } </code></pre><ul>
<li>코틀린 표준 라이브러리 참조 문서에는 filter와 map이 리스트를 반환한다고 써 있다. 이는 이 연쇄 호출이 리스트를 2개 만든다는 뜻이다.<ul>
<li>한 리스트는 filter의 결과를 담고, 다른 하나는 map의 결과를 담는다.</li>
</ul>
</li>
<li>이를 더 효율적으로 만들기 위해서는 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야 한다.<pre><code class="language-kotlin">people.asSequence() // 원본 컬렉션을 시퀀스로 변환한다.
  .map(Person::name)
  .filter { it.startsWith(&quot;A&quot;) }
  .toList() // 결과 시퀀스를 다시 리스트로 변환한다.</code></pre>
</li>
<li>중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 눈에 띄게 좋아진다.</li>
</ul>
<ul>
<li>코틀린 지연 계산 시퀀스는 Sequence 인터페이스에서 시작한다. 이 인터페이스는 단지 한 번에 하나씩 열거될 수 있는 원소의 시퀀스를 표현할 뿐이다.</li>
<li>Sequence 안에는 iterator라는 단 하나의 메소드가 있다. 그 메소드를 통해 시퀀스로부터 원소 값을 얻을 수 있다.</li>
<li>Sequence 인터페이스의 강점은 그 인터페이스 위에 구현된 연산이 계산을 수행하는 방법 때문에 생긴다.<ul>
<li><span style='background-color: #fff5b1'/>시퀀스의 원소는 필요할 때 비로소 계산된다. 따라서 중간 결과를 저장하지 않고도 연산을 연쇄적으로 적용해서 효율적으로 계산을 수행할 수 있다.</li>
</ul>
</li>
<li>asSequence 확장 함수를 호출하면 어떤 컬렉션이든 시퀀스로 바꿀 수 있다. 시퀀스를 리스트로 만들 때는 toList를 사용한다.</li>
</ul>
<blockquote>
<p><strong>왜 시퀀스를 다시 컬렉션으로 되돌려야 할까? 컬렉션보다 시퀀스가 훨씬 더 낫다면 그냥 시퀀스를 쓰는 편이 낫지 않을까?</strong></p>
<ul>
<li>항상 그렇지는 않다. 시퀀스의 원소를 차례로 이터레이션해야 한다면 시퀀스를 직접 써도 된다. 하지만 시퀀스 원소를 인덱스를 사용해 접근하는 등의 다른 API 메소드가 필요하다면 시퀀스를 리스트로 변환해야 한다.</li>
</ul>
</blockquote>
<blockquote>
<p>큰 컬렉션에 대해서 연산을 연쇄시킬 때는 시퀀스를 사용하는 것을 규칙으로 삼아라.</p>
<p>8장에서는 중간 컬렉션을 생성함에도 불구하고 코틀린에서 즉시 계산 컬렉션에 대한 연산이 더 효율적인 이유를 설명한다. 하지만 컬렉션에 들어있는 원소가 많으면 중간 원소를 재배열하는 비용이 커지기 때문에 지연 계산이 더 낫다.</p>
</blockquote>
<h3 id="시퀀스-연산-실행-중간-연산과-최종-연산">시퀀스 연산 실행: 중간 연산과 최종 연산</h3>
<ul>
<li>시퀀스에 대한 연산은 중간 연산과 최종 연산으로 나뉜다.<ul>
<li>중간 연산은 다른 시퀀스를 반환한다. 그 시퀀스는 최초 시퀀스의 원소를 변환하는 방법을 안다.</li>
<li>최종 연산은 결과를 반환한다. 결과는 최초 컬렉션에 대해 변환을 적용한 시퀀스로부터 일련의 계산을 수행해 얻을 수 있는 컬렉션이나 원소, 숫자 또는 객체다.</li>
</ul>
</li>
<li><span style='background-color: #fff5b1'/>중간 연산은 항상 지연 계산된다.<pre><code class="language-kotlin">listOf(1, 2, 3, 4).asSequence()
          .map { print(&quot;map($it) &quot;); it * it }
          .filter { print(&quot;filter($it) &quot;); it % 2 == 0 }</code></pre>
</li>
<li>이 코드를 실행하면 아무 내용도 출력되지 않는다. 이는 map과 filter 변환이 늦춰져서 결과를 얻을 필요가 있을 때(즉 최종 연산이 호출될 때) 적용된다는 뜻이다.<pre><code class="language-kotlin">listOf(1, 2, 3, 4).asSequence()
          .map { print(&quot;map($it) &quot;); it * it }
          .filter { print(&quot;filter($it) &quot;); it % 2 == 0 }
          .toList()
</code></pre>
</li>
</ul>
<p>// 결과: map(1) filter(1) map(2) filter(4) map(3) filter(9) map(4) filter(16)</p>
<pre><code>- 최종 연산을 호출하면 연기됐던 모든 계산이 수행된다.
- 직접 연산을 구현한다면 map 함수를 각 원소에 대해 먼저 수행해서 새 시퀀스를 얻고, 그 시퀀스에 대해 다시 filter를 수행할 것이다. 컬렉션에 대한 map과 filter는 그런 방식으로 작동한다.
- 시퀀스의 경우 모든 연산은 각 원소에 대해 순차적으로 적용된다. 즉 첫 번째 원소가 (변환된 다음에 걸러지면서) 처리되고, 다시 두 번째 원소가 처리되며, 이런 처리가 모든 원소에 대해 적용된다.
- 따라서 원소에 연산을 차례대로 적용하다가 결과가 얻어지면 그 이후의 원소에 대해서는 변환이 이뤄지지 않을 수도 있다.
```kotlin
println(listOf(1, 2, 3, 4).asSequence()
                          .map { it * it }.find { it &gt; 3 }) // 4        </code></pre><ul>
<li>컬렉션에 수행하면 map의 결과가 먼저 평가돼 최초 컬렉션의 모든 원소가 변환된다. 두 번째 단계에서는 map을 적용해서 얻은 중간 컬렉션으로부터 술어를 만족하는 원소를 찾는다.<ul>
<li>컬렉션을 사용하면 리스트가 다른 리스트로 변환된다.</li>
</ul>
</li>
</ul>
<ul>
<li>시퀀스를 사용하면 find 호출이 원소를 하나씩 처리하기 시작한다. 최초 시퀀스로부터 수를 하나 가져와서 map에 지정된 변환을 수행한 다음에 find에 지정된 술어를 만족하는지 검사한다.<ul>
<li>시퀀스를 사용하면 지연 계산으로 인해 원소 중 일부의 계산은 이뤄지지 않는다.</li>
</ul>
</li>
</ul>
<ul>
<li>컬렉션에 대해 수행하는 연산의 순서도 성능에 영향을 끼친다.<pre><code class="language-kotlin">val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31),
              Person(&quot;Charles&quot;, 31), Person(&quot;Dan&quot;, 21))
</code></pre>
</li>
</ul>
<p>println(people.asSequence().map(Person::name)
    .filter { it.length &lt; 4 }.toList() ) // [Bob, Dan]</p>
<p>println(people.asSequence().filter { it.lenth &lt; 4 }
    .map(Person::name).toList()) // [Bob, Dan]</p>
<pre><code>- map을 먼저 하면 모든 원소를 반환한다. 하지만 filter를 먼저 하면 부적절한 원소를 먼저 제외하기 때문에 그런 원소는 반환하지 않는다.

### 시퀀스 만들기
```kotlin
// 자연수의 시퀀스를 생성하고 사용하기
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it &lt;= 100 }
println(numbersTo100.sum()) // 5050</code></pre><ul>
<li>이 예제에서 naturalNumbers와 numbersTo100은 모두 시퀀스며, 연산을 지연 계산한다. 최종 연산을 수행하기 전까지는 시퀀스의 각 숫자는 계산되지 않는다(여기서는 sum이 최종 연산이다).</li>
</ul>
<pre><code class="language-kotlin">fun File.isInsideHiddenDirectory() =
    generateSequence(this) { it.parentFile }.any { it.isHidden }

val file = File(&quot;/Users/svtk/.HiddenDir/a.txt&quot;)
println(file.isInsideHiddenDirectory()) // true</code></pre>
<ul>
<li>여기서도 첫 번째 원소를 지정하고 시퀀스의 한 원소로부터 다음 원소를 제공함으로써 시퀀스를 만든다.</li>
<li>이렇게 시퀀스를 사용하면 조건을 만족하는 디렉터를 찾은 뒤에는 더 이상 상위 디렉터리를 뒤지지 않는다.</li>
</ul>
<h2 id="자바-함수형-인터페이스-활용">자바 함수형 인터페이스 활용</h2>
<pre><code class="language-kotlin">button.setOnClickListener { /* 클릭 시 수행할 동작 */ } // 람다를 인자로 넘김</code></pre>
<ul>
<li>Button 클래스는 setOnClickListener 메소드를 사용해 버튼의 리스너를 설정한다. 이때 인자의 타입은 OnClickListener다.<pre><code class="language-java">public class Button {
  public void setOnClickListener(OnClickListener l) { ... }
}</code></pre>
</li>
<li>OnClickListener 인터페이스는 onClick이라는 메서드만 선언된 인터페이스다.<pre><code class="language-java">public interface OnClickListener {
  void onClick(View v);
}</code></pre>
</li>
<li>자바 8 이전의 자바에서는 setOnClickListener 메서드에게 인자로 넘기기 위해 무명 클래스의 인스턴스를 만들어야만 했다.<pre><code class="language-java">button.setOnClickListener(new OnclickListener() {
  @Override
  public void onClick(View v) {
      ...
  }
})</code></pre>
</li>
<li>코틀린에서는 무명 클래스 인스턴스 대신 람다를 넘길 수 있다.<pre><code class="language-kotlin">button.setOnClickListener { view -&gt; ... }</code></pre>
</li>
<li>이런 코드가 작동하는 이유는 OnClickListener에 <span style='background-color: #fff5b1'/>추상 메소드가 단 하나만 있기 때문이다. 그런 인터페이스를 <code>함수형 인터페이스</code> 또는 <code>SAM(Single Abstract Method) 인터페이스</code>라고 한다.</li>
</ul>
<h3 id="자바-메소드에-람다를-인자로-전달">자바 메소드에 람다를 인자로 전달</h3>
<ul>
<li>함수형 인터페이스를 인자로 원하는 자바 메소드에 코틀린 람다를 전달할 수 있다.<pre><code class="language-java">void postponeComputation(int delay, Runnable computation);</code></pre>
</li>
<li>코틀린에서 람다를 이 함수에 넘길 수 있다. 컴파일러는 자동으로 람다를 Runnable(을 구현한 무명 클래스의) 인스턴스로 변환해준다.<pre><code class="language-kotlin">postponeComputation(1000) { println(42) }</code></pre>
</li>
<li>컴파일러는 자동으로 그런 무명 클래스와 인스턴스를 만들어준다. 이때 그 무명 클래스에 있는 유일한 추상 메소드를 구현할 때 람다 본문을 메소드 본문으로 사용한다. 여기서는 Runnable의 run이 그런 추상 메소드다.</li>
<li>Runnable을 구현하는 무명 객체를 명시적으로 만들어서 사용할 수도 있다.<pre><code class="language-kotlin">// 객체 식을 함수형 인터페이스 구현으로 넘긴다.
postponeComputation(1000, object: Runnable {
  override fun run() {
      println(42)
  }
}) </code></pre>
</li>
<li>하지만 람다와 무명 객체 사이에는 차이가 있다.<ul>
<li><span style='background-color: #fff5b1'/>객체를 명시적으로 선언하는 경우 메소드를 호출할 때마다 새로운 객체가 생성된다.</li>
<li><span style='background-color: #fff5b1'>람다는 다르다. <span style='background-color: #F7DDBE'>정의가 들어있는 함수의 변수에 접근하지 않는 람다에 대응하는</span> 무명 객체를 메소드를 호출할 때마다 반복 사용한다.</span></li>
</ul>
</li>
</ul>
<blockquote>
<p>✅ <strong>함수의 변수에 접근하지 않는 람다?</strong> : 클로저를 형성하지 않은 람다</p>
</blockquote>
<pre><code class="language-kotlin">// 프로그램 전체에서 Runnable의 인스턴스는 단 하나만 만들어진다.
postponeComputation(1000) { println(42) }</code></pre>
<ul>
<li>따라서 명시적인 object 선언을 사용하면서 람다와 동일한 코드는 다음과 같다. 이 경우 Runnable 인스턴스를 변수에 저장하고 메소드를 호출할 때마다 그 인스턴스를 재사용한다.<pre><code class="language-kotlin">// 전역 변수로 컴파일되므로 프로그램 안에 단 하나의 인스턴스만 존재한다.
val runnable = Runnable { println(42) }
</code></pre>
</li>
</ul>
<p>fun handleComputation() {
    // 모든 handleComputation 호출에 같은 객체를 사용한다.
    postponeComputation(1000, runnable) 
}</p>
<pre><code>- 람다가 주변 영역의 변수를 포획한다면 매 호출마다 같은 인스턴스를 사용할 수 없다. 그런 경우 컴파일러는 매번 주변 영역의 변수를 포획한 새로운 인스턴스를 생성해준다.
```kotlin
fun handleComputation(id: String) {
    // handleComputation을 호출할 때마다 새로 Runnable 인스턴스를 만든다.
    postponeComputation(1000, runnable)
} </code></pre><ul>
<li>람다에 대해 무명 클래스를 만들고 그 클래스의 인스턴스를 만들어서 메소드에 넘긴다는 설명은 함수형 인터페이스를 받는 자바 메소드를 코틀린에서 호출할 때 쓰는 방식을 설명해준다.</li>
<li>하지만, 컬렉션을 확장한 메소드에 람다를 넘기는 경우 코틀린은 그런 방식을 사용하지 않는다.<ul>
<li><span style='background-color: #fff5b1'/>코틀린 inline으로 표시된 코틀린 함수에게 람다를 넘기면 아무런 무명 클래스도 만들어지지 않는다.<ul>
<li>대부분의 코틀린 확장 함수들은 inline 표시가 붙어있다. (8장)</li>
</ul>
</li>
</ul>
</li>
<li>대부분의 경우 람다와 자바 함수형 인터페이스 사이의 변환은 자동으로 이뤄진다.</li>
</ul>
<h3 id="sam-생성자-람다를-함수형-인터페이스로-명시적으로-변경">SAM 생성자: 람다를 함수형 인터페이스로 명시적으로 변경</h3>
<blockquote>
<p>💡 람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수(람다 표현식)다.</p>
</blockquote>
<ul>
<li>컴파일러가 자동으로 람다를 함수형 인터페이스 무명 클래스로 바꾸지 못하는 경우 SAM 생성자를 사용할 수 있다.<ul>
<li>함수형 인터페이스의 인스턴스를 반환하는 메소드가 있다면 람다를 직접 반환할 수 없고, 반환하고픈 람다를 SAM 생성자로 감싸야 한다.<pre><code class="language-kotlin">fun createAllDoneRunnable(): Runnable {
return Runnable { println(&quot;All done!&quot;) }
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>createAllDoneRunnable().run() // All done!</p>
<pre><code>- SAM 생성자의 이름은 사용하려는 함수형 인터페이스의 이름과 같다.
- SAM 생성자는 그 함수형 인터페이스의 유일한 추상 메소드의 본문에 사용할 람다만을 인자로 받아서 함수형 인터페이스를 구현하는 클래스의 인스턴스를 반환한다.


- 람다로 생성한 함수형 인터페이스 인스턴스를 변수에 저장해야 하는 경우에도 SAM 생성자를 사용할 수 있다.
```kotlin
// SAM 생성자를 사용해 listener 인스턴스 재사용하기
val listener = OnClickListener { view -&gt; 
    val text = when (view.id) {
        R.id.button1 -&gt; &quot;First button&quot;
        R.id.button2 -&gt; &quot;Second button&quot;
        else -&gt; &quot;Unknown button&quot;
    }
    toast(text)
}

button1.setOnClickListener(listener)
button2.setOnClickListener(listener)</code></pre><ul>
<li>OnClickListener를 구현하는 객체 선언을 통해 리스너를 만들 수도 있지만 SAM 생성자를 쓰는 쪽이 더 간결하다.<pre><code class="language-kotlin">val listener = object : View.OnClickListener {
override fun onClick(view: View) {
  val text = when (view.id) {
    R.id.button1 -&gt; &quot;First button&quot;
    R.id.button2 -&gt; &quot;Second button&quot;
    else -&gt; &quot;Unknown button&quot;
  }
  toast(text)
}
} </code></pre>
</li>
</ul>
<blockquote>
<p><strong>람다와 리스너 등록/해제하기</strong></p>
<p>람다에는 무명 객체와 달리 인스턴스 자신을 가리키는 this가 없다는 사실에 유의하라. 따라서 람다를 변환한 무명 클래스의 인스턴스를 참조할 방법이 없다. 컴파일러 입장에서 보면 람다는 코드 블록일 뿐이고, 객체가 아니므로 객체처럼 람다를 참조할 수는 없다. 람다 안에서 this는 그 람다를 둘러싼 클래스의 인스턴스를 가리킨다.</p>
<pre><code class="language-kotlin">class MyClass {
   private val myProperty: Int = 42

   fun doSomething() {
       // 람다 표현식 내부에서 this는 MyClass의 인스턴스를 가리킴
       val myLambda: () -&gt; Unit = {
           println(&quot;Inside lambda: $this.myProperty&quot;)
       }

       myLambda()

       println(&quot;Outside lambda: $this.myProperty&quot;)
   }
}

fun main() {
    val myInstance = MyClass()
    myInstance.doSomething()
}

// Inside lambda: MyClass@506e1b77.myProperty
// Outside lambda: MyClass@506e1b77.myProperty</code></pre>
</blockquote>
<ul>
<li>이벤트 리스너가 이벤트를 처리하다가 자기 자신의 리스너 등록을 해제해야 한다면 람다를 사용할 수 없다. 그런 경우 람다 대신 무명 객체를 사용해 리스너를 구현한다. 무명 객체 안에서는 this가 그 무명 객체 인스턴스 자신을 가리킨다. 따라서 리스너를 해제하는 API 함수에게 this를 넘길 수 있다.<pre><code class="language-kotlin">import android.view.View
import android.widget.Button
</code></pre>
</li>
</ul>
<p>class MyActivity {</p>
<pre><code>private var button: Button? = null
private var myListener: View.OnClickListener? = null

init {
    // 무명 객체를 사용하여 OnClickListener 구현
    myListener = object : View.OnClickListener {
        override fun onClick(v: View?) {
            // 이벤트 처리 코드

            // 자기 자신의 리스너 등록 해제
            button?.setOnClickListener(null)
        }
    }
    // 버튼에 리스너 등록
    button?.setOnClickListener(myListener)
}</code></pre><p>}</p>
<pre><code>
- 함수형 인터페이스를 요구하는 메소드를 호출할 때 대부분의 SAM 변환을 컴파일러가 자동으로 수행할 수 있지만, 가끔 오버라이드한 메소드 중에서 어떤 타입의 메소드를 선택해 람다를 변환해 넘겨줘야 할지 모호한 때가 있다.
  - 그런 경우 명시적으로 SAM 생성자를 적용하면 컴파일 오류를 피할 수 있다.

## [수신 객체 지정 람다: with와 apply](https://velog.io/@dev-baik/%EB%B2%94%EC%9C%84-%EC%A7%80%EC%A0%95-%ED%95%A8%EC%88%98)

&gt; 💡 수신 객체를 명시하지 않고, 람다의 본문 안에서 해당 객체의 메서드를 호출할 수 있게 하는 람다

### with 함수
- 알파벳 만들기
```kotlin
fun alphabet(): String {
    val result = StringBuilder()

    for (letter in &#39;A&#39;..&#39;Z&#39;) {
        result.append(letter)
    }
    result.append(&quot;\nNow I know the alphabet!&quot;)
    return result.toString()
}

println(alphabet())

// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Now I know the alphabet!</code></pre><ul>
<li><p>with 사용해 알파벳 만들기</p>
<pre><code class="language-kotlin">fun alphabet(): String {
  val result = StringBuilder()

  return with(stringBuilder) { // 메소드를 호출하려는 수신 객체를 지정한다.
      for (letter in &#39;A&#39;..&#39;Z&#39;) {
          this.append(letter) // &quot;this&quot;를 명시해서 앞에서 지정한 수신 객체의 메소드를 호출한다.
      }
      append(&quot;\nNow I know the alphabet!&quot;) // &quot;this&quot;를 생략하고 메소드를 호출한다.
      this.toString() // 람다에서 값을 반환한다.
  }
}
</code></pre>
</li>
</ul>
<p>println(alphabet())</p>
<p>// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Now I know the alphabet!</p>
<pre><code>- with는 실제로 파라미터가 2개 있는 함수다. 첫 번째 파라미터는 stringBuilder이고, 두  번째 파라미터는 람다다.
- 람다를 괄호 밖으로 빼내는 관례를 사용함에 따라 전체 함수 호출이 언어가 제공하는 특별 구문처럼 보인다.
  - `with(stringBuilder, { ... })`라고 쓸 수 있지만 더 읽기 나빠진다.
- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;with 함수는 첫 번째 인자로 받은 객체를 두 번째 인자로 받은 람다의 수신 객체로 만든다.
  - 인자로 받은 람다 본문에서는 this를 사용해 그 수신 객체에 접근할 수 있다.

&gt; **수신 객체 지정 람다와 확장 함수 비교**
&gt;
&gt; 확장 함수 안에서 this는 그 함수가 확장하는 타입의 인스턴스를 가리킨다. 그리고 그 수신 객체 this의 멤버를 호출할 때는 this.를 생략할 수 있다. 어떤 의미에서는 확장 함수를 수신 객체 지정 함수라 할 수도 있다. 람다는 일반 함수와 비슷한 동작을 정의하는 한 방법이다. 수신 객체 지정 람다는 확장 함수와 비슷한 동작을 정의하는 한 방법이다.

- with와 식을 본문으로 하는 함수를 활용해 알파벳 만들기
```kotlin
fun alphabet() = with(StringBuilder()) {
    for (letter in &#39;A&#39;..&#39;Z&#39;) {
        append(letter)
    }
    append(&quot;\nNow I know the alphabet!&quot;)
    toString()
}</code></pre><ul>
<li>불필요한 stringBuilder 변수를 없애면 alphabet 함수가 식의 결과를 바로 반환하게 된다. 따라서 식을 본문으로 하는 함수로 표현할 수 있다.<ul>
<li>StringBuilder의 인스턴스를 만들고 즉시 with에게 인자로 넘기고, 람다 안에서 this를 사용해 그 인스턴스를 참조한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>메소드 이름 충돌</strong></p>
<p>with에게 인자로 넘긴 객체의 클래스와 with를 사용하는 코드가 들어있는 클래스 안에 이름이 같은 메소드가 있으면 무슨 일이 생길까? 그런 경우 this 참조 앞에 레이블을 붙이면 호출하고 싶은 메소드를 명확하게 정할 수 있다.</p>
<p>alphabet 함수가 OuterClass의 메소드라고 하자. StringBuilder가 아닌 바깥쪽 클래스(OuterClass)에 정의된 toString을 호출하고 싶다면 <code>this@OuterClass.toString()</code> 구문을 사용해야 한다.</p>
</blockquote>
<ul>
<li>with가 반환하는 값은 람다 코드를 실행한 결과며, 그 결과는 람다 식의 본문에 있는 마지막 식의 값이다.</li>
</ul>
<h3 id="apply-함수">apply 함수</h3>
<ul>
<li>apply 함수는 거의 with와 같다.</li>
<li>유일한 차이란 apply는 항상 자신에게 전달된 객체(즉 수신 객체)를 반환한다는 점뿐이다.<pre><code class="language-kotlin">fun alphabet() = StringBuilder().apply {
  for (letter in &#39;A&#39;..&#39;Z&#39;) {
      append(letter)
  }
  append(&quot;\nNow I know the alphabet&quot;)
}.toString()</code></pre>
</li>
<li>apply는 확장 함수로 정의돼 있다. apply의 수신 객체가 전달받은 람다의 수신 객체가 된다. 이 함수에서 apply를 실행한 결과는 StringBuilder 객체다. 따라서 그 객체의 toString을 호출해서 String 객체를 얻을 수 있다.</li>
<li><span style='background-color: #fff5b1'/>apply 함수는 객체의 인스턴스를 만들면서 즉시 프로퍼티 중 일부를 초기화해야 하는 경우 유용하다.<ul>
<li>자바에서는 보통 별도의 Builder 객체가 이런 역할을 담당한다.</li>
<li>코틀린에서는 어떤 클래스가 정의돼 있는 라이브러리의 특별한 지원 없이도 그 클래스 인스턴스에 대해 apply를 활용할 수 있다.<pre><code class="language-kotlin">// apply를 TextView 초기화에 사용하기
fun createViewWithCustomAttributes(context: Context) =
TextView(context).apply {
    text = &quot;Sample Text&quot;
    textSize = 20.0
    setPadding(10, 0, 0, 0)
}</code></pre>
</li>
</ul>
</li>
<li>새로운 TextView 인스턴스를 만들고 즉시 그 인스턴스를 apply에 넘긴다. apply에 전달된 람다 안에서는 TextView가 수신 객체가 된다. 따라서 원하는 대로 TextView의 메소드를 호출하거나 프로퍼티를 설정할 수 있다.</li>
<li>람다를 실행하고 나면 apply는 람다에 의해 초기화된 TextView 인스턴스를 반환한다. 그 인스턴스는 createViewWithCustomAttributes 함수의 결과가 된다.</li>
<li>표준 라이브러리의 buildString 함수를 사용하면 alphabet 함수를 더 단순화할 수 있다.<ul>
<li>buildString은 StringBuilder 객체를 만드는 일과 toString을 호출해주는 일을 알아서 해준다.</li>
<li>buildString의 인자는 수신 객체 지정 람다며, 수신 객체는 항상 StringBuilder가 된다.<pre><code class="language-kotlin">fun alphabet() = buildString {
for (letter in &#39;A&#39;..&#39;Z&#39;) {
    append(letter)
}
append(&quot;\nNow I know the alphabet!&quot;) 
}</code></pre>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 4장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-4%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-4%EC%9E%A5</guid>
            <pubDate>Fri, 03 Nov 2023 06:45:21 GMT</pubDate>
            <description><![CDATA[<h1 id="클래스-객체-인터페이스">클래스, 객체, 인터페이스</h1>
<blockquote>
<ul>
<li>클래스와 인터페이스</li>
<li>뻔하지 않은 생성자와 프로퍼티</li>
<li>데이터 클래스</li>
<li>클래스 위임</li>
<li>object 키워드 사용</li>
</ul>
</blockquote>
<hr>
<h2 id="클래스-계층-정의">클래스 계층 정의</h2>
<h3 id="코틀린-인터페이스">코틀린 인터페이스</h3>
<blockquote>
<p>💡 일종의 추상된 틀로, 클래스에서 구현해야 하는 메서드들의 집합을 정의한다. 다른 클래스들이 해당 인터페이스를 구현함으로써, 공통된 행동이나 규약을 정의하고 일관성 있는 구조를 갖도록 도와준다. 클래스는 하나의 클래스만을 상속받을 수 있지만, 인터페이스는 여러 개를 구현할 수 있다.</p>
</blockquote>
<ul>
<li>코틀린 인터페이스 안에는 추상 메서드뿐 아니라 구현이 있는 메소드도 정의할 수 있다.<ul>
<li>단, 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.</li>
</ul>
</li>
<li>인터페이스를 구현하는 모든 비추상 클래스(또는 구체적 클래스)는 인터페이스에 대한 구현을 제공해야 한다.</li>
</ul>
<pre><code class="language-kotlin">interface Clickable {
    fun click()
}

class Button : Clickable {
    override fun click() = println(&quot;I was clicked&quot;)
}

Button().click() // I was clicked</code></pre>
<ul>
<li>클래스 이름 뒤에 콜론(:)을 붙이고 인터페이스와 클래스 이름을 적는 것으로 클래스 확장과 인터페이스 구현을 모두 처리한다.</li>
<li>자바와 마찬가지로 클래스는 인터페이스를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있지만, 클래스는 오직 하나만 확장할 수 있다.</li>
<li><strong>override 변경자</strong> : 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시<ul>
<li><span style='background-color: #fff5b1'/>자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야 한다.<ul>
<li><strong>WHY)</strong> 실수로 상위 클래스의 메소드를 오버라이드하는 경우를 방지해준다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그냥 메소드 본문을 <a href="https://velog.io/@wlsrhkd4023/%EB%A9%94%EC%84%9C%EB%93%9C-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98Method-signature%EB%9E%80">메소드 시그니처</a> 뒤에 추가하면 된다.</li>
</ul>
<blockquote>
<p>✅ <strong>메서드 시그니처</strong> : 프로그래머가 디자인한 메서드 구조를 의미하며, 메서드 이름과 파라미터 리스트로 구성된다.</p>
</blockquote>
<pre><code class="language-kotlin">// 인터페이스 안에 본문이 있는 메소드 정의하기
interface Clickable {
    fun click() // 일반 메소드 선언
    fun showOff() = println(&quot;I&#39;m clickable&quot;) // 디폴트 구현이 있는 메소드
}

// 동일한 메소드를 구현하는 다른 인터페이스 정의하기
interface Focusable {
    fun setFocus(b: Boolean) =
        println(&quot;I ${if (b) &quot;got&quot; else &quot;lost&quot;} focus.&quot;)

    fun showOff() = println(&quot;I&#39;m focusable&quot;)
}</code></pre>
<ul>
<li><p>한 클래스에서 이 두 인터페이스(Clickable, Focusable)를 함께 구현하면 showOff 메소드가 중복되어 컴파일러 오류가 발생한다.</p>
</li>
<li><p><strong>해결책)</strong> 하위 클래스에 직접 구현하게 강제해야한다.</p>
<pre><code class="language-kotlin">// 상속한 인터페이스의 메소드 구현 호출하기
class Button : Clickable, Focusable {
  override fun click() = println(&quot;I was clicked&quot;)

  // 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우
  // 인터페이스를 구현하는 하위 클래스에서 명시적으로 새로운 구현을 제공해야 한다.
  override fun showOff() {
      // 상위 타입의 이름을 꺾쇠 괄호(&lt;&gt;) 사이에 넣어서 &quot;super&quot;를 지정하면
      // 어떤 상위 타입의 멤버 메소드를 호출할지 지정할 수 있다.
      super&lt;Clickable&gt;.showOff()
      super&lt;Focusable&gt;.showOff()
  }
}
</code></pre>
</li>
</ul>
<p>fun main(args: Array<String>) {
    val button = Button()
    button.showOff()
    button.setFocus(true)
    button.click()
}</p>
<p>//// 결과
// I&#39;m clickable!
// I&#39;m focusable!
// I got focus.
// I was clicked.</p>
<pre><code>- 상속한 구현 중 단 하나만 호출해도 된다면
```kotlin
override fun showOff() = super&lt;Clickable&gt;.showOff() </code></pre><h3 id="open-final-abstract-변경자-기본적으로-final">open, final, abstract 변경자: 기본적으로 final</h3>
<ul>
<li>취약한 기반 클래스 문제<ul>
<li><strong>WHEN)</strong> 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스를 변경함으로써 깨져버린 경우 발생</li>
<li><strong>WHY)</strong> 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙(어떤 메소드를 어떻게 오버라이드해야 하는지 등)을 제공하지 않는다면 그 클래스의 클라이언트는 기반 클래스를 변경하는 경우 하위 클래스의 동작이 예기치 않게 바뀔수도 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라 (Feat. Effective Java)</p>
<p>특별히 하위 클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻</p>
</blockquote>
<ul>
<li><p>코틀린도 마찬가지로 철학을 따른다.</p>
<ul>
<li><span style='background-color: #fff5b1'/>자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린의 클래스와 메소드는 기본적으로 final이다.</li>
</ul>
</li>
<li><p><span style='background-color: #fff5b1'/>어떤 클래스의 상속을 허용하려면 클래스 앞에 open 변경자를 붙여야 한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야 한다.</p>
<pre><code class="language-kotlin">// 열린 메소드를 포함하는 열린 클래스 정의하기
// 이 클래스는 열려있다. 다른 클래스가 이 클래스를 상속할 수 있다.
open class RichButton : Clickable {
  // 이 함수는 final이다. 하위 클래스가 이 메소드를 오버라이드할 수 없다.
  fun disable() {}

  // 이 함수는 열려있다. 하위 클래스에서 이 메소드를 오버라이드해도 된다.
  open fun animate() {}

  // 이 함수는 (상위 클래스에서 선언된) 열려있는 메소드를 오버라이드한다.
  // 오버라이드한 메소드는 기본적으로 열려있다.
  override fun click() {}
} </code></pre>
</li>
<li><p>기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.</p>
</li>
<li><p>오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야 한다.</p>
<pre><code class="language-kotlin">// 오버라이드 금지하기
open class RichButton : Clickable {
  // 여기 있는 &quot;final&quot;은 쓸데 없이 붙은 중복이 아니다.
  // &quot;final&quot;이 없는 &quot;override&quot; 메소드나 프로퍼티는 기본적으로 열려있다.
  final override fun click() {}
}</code></pre>
<blockquote>
<p><strong>열린 클래스와 스마트 캐스트</strong></p>
<p>클래스의 기본적인 상속 가능 상태를 final로 함으로써 얻을 수 있는 큰 이익은 다양한 경우에 스마트 캐스트가 가능하다는 점이다. 스마트 캐스트는 타입 검사 뒤에 변경될 수 없는 변수에만 적용 가능하다. 클래스의 프로퍼티의 경우 이는 val이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미다. 이 요구사항은 또한 프로퍼티가 final이어야만 한다는 뜻이기도 하다. 프로퍼티가 final이 아니라면 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구 사항을 깰 수 있다. 프로퍼티는 기본적으로 final이기 때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다. 이는 코드를 더 이해하기 쉽게 만든다.</p>
</blockquote>
</li>
<li><p>자바처럼 코틀린에서도 클래스를 abstract로 선언할 수 있다. abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.</p>
</li>
<li><p>추상 클래스에는 구현이 없는 추상 멤버가 있기 때문에 하위 클래스에서 그 추상 멤버를 오버라이드해야만 하는 게 보통이다.</p>
</li>
<li><p>추상 멤버는 항상 열려있다. 따라서 추상 멤버 앞에 open 변경자를 명시할 필요가 없다.</p>
<pre><code class="language-kotlin">// 이 클래스는 추상클래스다. 이 클래스의 인스턴스를 만들 수 없다.
abstract class Animated {
  // 이 함수는 추상함수다. 이 함수에는 구현이 없다. 하위 클래스에서는 이 함수를 반드시 오버라이드해야 한다.
  abstract fun animate()

  // 추상 클래스에 속했더라도 비추상 함수는 기본적으로 파이널이지만 원한다면 open으로 오버라이드를 허용할 수 있다.
  open fun stopAnimating() {}
  fun animateTwice() {}
}</code></pre>
</li>
<li><p>인터페이스 멤버의 경우 final, open, abstract를 사용하지 않는다.</p>
<ul>
<li>인터페이스 멤버는 항상 열려 있으며 final로 변경할 수 없다.</li>
<li>인터페이스 멤버에게 본문이 없으면 자동으로 추상 멤버가 되지만, 그렇더라도 따로 멤버 선언 앞에 abstract 키워드를 덧붙일 필요가 없다.</li>
</ul>
</li>
</ul>
<table>
  <tr>
    <th>변경자</th>
    <th>이 변경자가 붙은 멤버는...</th>
    <th>설명</th>
  </tr>
  <tr>
    <td>final</td>
    <td>오버라이드할 수 없음</td>
    <td>클래스 멤버의 기본 변경자다.</td>
  </tr>
  <tr>
    <td>open</td>
    <td>오버라이드할 수 있음</td>
    <td>반드시 open을 명시해야 오버라이드할 수 있다.</td>
  </tr>
  <tr>
    <td>abstract</td>
    <td>반드시 오버라이드해야 함</td>
    <td>추상 클래스의 멤버에만 이 변경자를 붙일 수 있다. 추상 멤버에는 구현이 있으면 안 된다.</td>
  </tr>
  <tr>
    <td>override</td>
    <td>상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중</td>
    <td>오버라이드하는 멤버는 기본적으로 열려있다. 하위 클래스의 오버라이드를 금지하려면 final을 명시해야 한다.</td>
  </tr>
</table>

<h3 id="가시성-변경자-기본적으로-공개">가시성 변경자: 기본적으로 공개</h3>
<ul>
<li>외부 코드가 클래스의 내부에 의존하는 경우<pre><code class="language-kotlin">class SecretClass {
  val secretData: String = &quot;This is a secret.&quot;
}
</code></pre>
</li>
</ul>
<p>class ExternalCode {
    fun useSecretData(secretInstance: SecretClass) {
        // 외부 코드에서 SecretClass의 내부 데이터에 직접 접근
        println(secretInstance.secretData)
    }
}</p>
<p>fun main() {
    val secretInstance = SecretClass()
    val externalCode = ExternalCode()</p>
<pre><code>// 외부 코드가 SecretClass의 내부 데이터에 직접 의존
externalCode.useSecretData(secretInstance)</code></pre><p>}</p>
<pre><code>- 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다. 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부 코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
  - 클래스 내부의 세부 구현을 숨겨서 외부 코드가 클래스의 내부에 의존하지 않도록 하는 것
```kotlin
class SecretClass {
    private val secretData: String = &quot;This is a secret.&quot;

    fun revealSecret() {
        println(secretData)
    }
}

class ExternalCode {
    fun useSecretClass(secretInstance: SecretClass) {
        // 외부 코드에서는 SecretClass의 revealSecret 함수만 사용 가능
        secretInstance.revealSecret()
        // secretInstance.secretData에는 접근 불가능
    }
}

fun main() {
    val secretInstance = SecretClass()
    val externalCode = ExternalCode()

    // 외부 코드는 SecretClass의 내부 구현에 직접 의존하지 않음
    externalCode.useSecretClass(secretInstance)
}</code></pre><ul>
<li><span style='background-color: #fff5b1'/>코틀린의 기본 가시성은 아무 변경자도 없는 경우 모두 공개(public)된다.</li>
<li>자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.</li>
<li>패키지 전용 가시성에 대한 대안으로 코틀린에서는 internal이라는 새로운 가시성 변경자를 도입했다.<ul>
<li>internal은 &quot;모듈 내부에서만 볼 수 있음&quot;이라는 뜻이다.<ul>
<li>모듈은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다. 인텔리J나 이클립스, 메이븐, 그레이들 등의 프로젝트가 모듈이 될 수 있고 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>모듈 내부 가시성은 여러분의 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다.</li>
</ul>
<blockquote>
<p>자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근할 수 있다. 그래스 모듈의 캡슐화가 쉽게 깨진다.</p>
</blockquote>
<ul>
<li>코틀린에서는 최상위 선언에 대해 private 가시성(비공개 가시성)을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.</li>
<li>비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용한 방법이다.</li>
</ul>
<table>
  <tr>
    <th>변경자</th>
    <th>클래스 멤버</th>
    <th>최상위 선언</th>
  </tr>
  <tr>
    <td>public(기본 가시성임)</td>
    <td>모든 곳에서 볼 수 있다.</td>
    <td>모든 곳에서 볼 수 있다.</td>
  </tr>
  <tr>
    <td>internal</td>
    <td>같은 모듈 안에서만 볼 수 있다.</td>
    <td>같은 모듈 안에서만 볼 수 있다.</td>
  </tr>
  <tr>
    <td>protected</td>
    <td>하위 클래스 안에서만 볼 수 있다.</td>
    <td>(최상위 선언에 적용할 수 없음)</td>
  </tr>
  <tr>
    <td>private</td>
    <td>하위 클래스 안에서만 볼 수 있다.</td>
    <td>같은 파일 안에서만 볼 수 있다.</td>
  </tr>
</table>

<pre><code class="language-kotlin">internal open class TalkativeButton : Focusable {
    private fun yell() = println(&quot;Hey!&quot;)
    protected fun whisper() = println(&quot;Let&#39;s talk&quot;)
}

// 오류: &quot;public&quot; 멤버가 자신의 &quot;internal&quot; 수신 타입인 &quot;TalkativeButton&quot;을 노출함
fun TalkativeButton.giveSpeech() {
    // 오류: &quot;yell&quot;에 접근할 수 없음: &quot;yell&quot;은 &quot;TalkativeButton&quot;의 &quot;private&quot; 멤버임
    yell()
    // 오류: &quot;whisper&quot;에 접근할 수 없음: &quot;whisper&quot;는 &quot;TalkativeButton&quot;의 &quot;protected&quot; 멤버임
    whisper()
}</code></pre>
<blockquote>
<p>어떤 클래스의 기반 타입 목록에 들어있는 타입이나 제네릭 클래스의 타입 파라미터에 들어있는 타입의 가시성은 그 클래스 자신의 가시성과 같거나 더 높아야 하고, 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야 한다는 규칙</p>
<p>어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근할 수 있게 보장해준다.</p>
</blockquote>
<ul>
<li>public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은(이 경우 internal) 타입인 TalkativeButton을 참조하지 못하게 한다.<ul>
<li><strong>해결책</strong> : giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야 한다.</li>
</ul>
</li>
<li>코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근할 수 없다.</li>
</ul>
<h3 id="내부inner-클래스와-중첩된nested-클래스-기본적으로-중첩-클래스"><a href="https://velog.io/@dev-baik/Inner-Nested-Class">내부(Inner) 클래스와 중첩된(Nested) 클래스: 기본적으로 중첩 클래스</a></h3>
<ul>
<li>클래스 안에 다른 클래스를 선언하면 <a href="https://minsone.github.io/programming/what-is-helper-class">도우미 클래스</a>를 캡슐화하거나 코드 정의를 그 코드를 사용한 곳 가까이에 두고 싶을 때 유용하다.</li>
</ul>
<blockquote>
<p>✅ <strong>도우미(helper) 클래스</strong> : 특정 클래스의 작업을 도와주는 역할을 하는 클래스이다. 모든 메소드가 정적 메소드인 Utility 클래스와 달리 모든 메소드는 정적 메소드가 아니며, 여러 개의 helper class의 인스턴스가 있을 수 있다. 특정 클래스를 도와주기 위한 클래스이므로 private으로 외부의 접근을 막는 것이 좋고, 다른 helper class와 의존성이 생기지 않도록 해야 한다. </p>
</blockquote>
<pre><code class="language-kotlin">class Car(val brand: String, val model: String, val year: Int) {

    // Engine 클래스를 Car 클래스 내에 캡슐화
    class Engine(val type: String, val horsepower: Int) {
        fun start() {
            println(&quot;Engine started&quot;)
        }
    }

    // Helper 클래스를 Car 클래스 내에 캡슐화
    private class Helper {
        fun performMaintenance() {
            println(&quot;Performing maintenance&quot;)
        }
    }

    // Car 클래스의 멤버 함수에서 Engine 및 Helper를 활용
    fun startCar() {
        val engine = Engine(&quot;V6&quot;, 300)
        engine.start()

        val helper = Helper()
        helper.performMaintenance()

        println(&quot;Car $brand $model started.&quot;)
    }
}

fun main() {
    val myCar = Car(&quot;Toyota&quot;, &quot;Camry&quot;, 2022)
    myCar.startCar()
}

//// 결과
// Engine started
// Performing maintenance
// Car Toyota Camry started.</code></pre>
<ul>
<li><span style='background-color: #fff5b1'/>코틀린의 중첩 클래스는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다.<pre><code class="language-kotlin">// 직렬화할 수 있는 상태가 있는 뷰 선언
interface State: Serializable
</code></pre>
</li>
</ul>
<p>interface View {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}</p>
<pre><code>```java
// 자바에서 내부 클래스를 사용해 View 구현하기
public class Button implements View {
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }

    @Override
    public void restoreState(State state) { /*...*/ }
    public class ButtonState implements State { /*...*/ }
}</code></pre><ul>
<li><p>java.io.NotSerializableException: Button 오류 발생</p>
<ul>
<li>ButtonState 타입의 state인 변수를 직렬화하는 데 Button을 직렬화할 수 없다는 예외가 발생<ul>
<li><strong>WHY)</strong> Serializable을 implement한 클래스를 상속받은 경우에만 직렬화가 가능하다.</li>
</ul>
</li>
</ul>
</li>
<li><p>자바에서 다른 클래스 안에 정의한 클래스는 자동으로 내부 클래스가 된다.</p>
<ul>
<li><p>이 예제의 ButtonState 클래스는 바깥쪽 Button 클래스에 대한 참조를 묵시적으로 포함한다.</p>
<ul>
<li><strong>원인)</strong> Button을 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState의 직렬화를 방해한다.</li>
<li><strong>해결책)</strong> ButtonState를 static 클래스로 선언하면 해당 클래스를 둘러싼 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.<pre><code class="language-kotlin">// 중첩 클래스를 사용해 코틀린에서 View 구현하기
class Button : View {
override fun getCurrentState(): State = ButtonState()
</code></pre>
</li>
</ul>
<p>override fun restoreState(state: State) { /<em>...</em>/ }</p>
<p>class ButtonState : State { /<em>...</em>/ }
}
```</p>
</li>
</ul>
</li>
<li><p>코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면드 자바 static 중첩 클래스와 같다.</p>
</li>
<li><p>이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner 변경자를 붙여야 한다.</p>
<table>
<tr>
  <th>클래스 B 안에 정의된 클래스 A</th>
  <th>자바에서는</th>
  <th>코틀린에서는</th>
</tr>
<tr>
  <td>중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음)</td>
  <td>static class A</td>
  <td>class A</td>
</tr>
<tr>
  <td>내부 클래스(바깥쪽 클래스에 대한 참조를 저장함)</td>
  <td>class A</td>
  <td>inner class A</td>
</tr>
</table>
</li>
<li><p><span style='background-color: #fff5b1'/>내부 클래스 Inner 안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.</p>
<pre><code class="language-kotlin">class Outer {
  val outerProperty: String = &quot;Outer Property&quot;

  inner class Inner {
      fun getOuterReference(): Outer = this@Outer
  }
}
</code></pre>
</li>
</ul>
<p>fun main() {
    val outer = Outer()
    val inner = outer.Inner()</p>
<pre><code>// Inner 클래스를 통해 Outer 클래스의 참조에 접근
val outerReference = inner.getOuterReference()
println(outerReference.outerProperty) // 출력: &quot;Outer Property&quot;</code></pre><p>}</p>
<pre><code>- 클래스 계층을 만들되 그 계층에 속한 클래스의 수를 제한하고 싶은 경우 중첩 클래스를 쓰면 편리하다.

### 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한

&gt; 💡 특정 클래스의 하위 클래스를 제한하는 클래스로, 상속 계층 구조에서 제한된 하위 클래스 집합을 정의할 때 사용된다. when 식과 같은 경우에 모든 하위 클래스를 처리하는지 여부를 컴파일러가 확인할 수 있다.

```kotlin
// 인터페이스 구현을 통해 식 표현하기
interface Expr

class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    when (e) {
        is Num -&gt; e.value
        is Sum -&gt; eval(e.right) + evel(e.left)
        else -&gt; // else 분기가 꼭 있어야 한다.
            throw IllegalArgumentException(&quot;Unknown expression&quot;)
    }</code></pre><ul>
<li>디폴트 분기(else)가 있으면 클래스 계층에 새로운 하위 클래스를 추가하더라도 컴파일러가 when이 모든 경우를 처리하는지 제대로 검사할 수 없다. 혹 실수로 새로운 클래스 처리를 잊어버렸더라도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.</li>
<li><strong>해결책)</strong> sealed 클래스<ul>
<li>상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위 클래스 정의를 제한할 수 있다.</li>
<li>sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 한다.<pre><code class="language-kotlin">// 기반 클래스를 sealed로 봉인한다.
sealed class Expr {
// 기반 클래스의 모든 하위 클래스를 중첩 클래스로 나열한다.
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>// &quot;When&quot; 식이 모든 하위 클래스를 검사하므로 별도의 &quot;else&quot; 분기가 없어도 된다.
fun eval(e: Expr): Int =
    when (e) {
        is Num -&gt; e.value
        is Sum -&gt; eval(e.right) + evel(e.left)
    }  </p>
<pre><code>- sealed로 표기된 클래스는 자동으로 open임을 기억하라.
- 내부적으로 Expr 클래스는 private 생성자를 가진다. 그 생성자는 클래스 내부에서만 호출할 수 있다.
```kotlin
sealed class Expr private constructor() {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
} </code></pre><ul>
<li>sealed 인터페이스를 정의할 수는 없다.</li>
</ul>
<h2 id="뻔하지-않은-생성자와-프로퍼티를-갖는-클래스-선언">뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언</h2>
<ul>
<li>코틀린은 주 생성자(보통 주 생성자는 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 클래스 본문 밖에서 정의한다)와 부 생성자(클래스 본문 안에서 정의한다)를 구분한다. 또한 초기화 블록을 통해 초기화 로직을 추가할 수 있다.<h3 id="클래스-초기화-주-생성자와-초기화-블록">클래스 초기화: 주 생성자와 초기화 블록</h3>
</li>
</ul>
<blockquote>
<p>💡 <strong>생성자</strong> : 객체 지향 프로그래밍에서 클래스로부터 객체를 생성할 때 호출되는 메소드로, 객체의 초기화 작업을 담당한다. 객체가 생성될 때 한 번만 호출되며, 해당 클래스의 인스턴스 변수들을 초기화하거나 다양한 설정 작업을 수행한다. </p>
</blockquote>
<blockquote>
<p>💡 <strong>주 생성자</strong> : 클래스 이름 옆에 괄호로 둘러싸인 코드로, 생성자 파라미터를 지정하고 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적에 쓰인다.</p>
</blockquote>
<blockquote>
<p>💡 <strong>초기화 블록</strong> : 클래스의 객체가 생성될 때 실행되는 초기화 코드를 작성하기 위한 블록으로, 주 생성자에서 직접적으로 처리하기 어려운 복잡한 초기화 작업이나 추가 로직을 처리하는 데 주로 사용된다.</p>
</blockquote>
<pre><code class="language-kotlin">class User(val nickname: String)</code></pre>
<pre><code class="language-kotlin">class User constructor(_nickname: String) {
    val nickname: String

    init {
        nickname = _nickname
    }
} </code></pre>
<ul>
<li>constructor 키워드는 주 생성자나 부 생성자 정의를 시작할 때 정의한다. init 키워드는 초기화 블록을 시작한다.</li>
<li>초기화 블록에는 클래스의 객체가 만들어질 때(인스턴스화될 때) 실행될 초기화 코드가 들어간다.</li>
<li>초기화 블록은 주 생성자와 함께 사용된다.</li>
<li>주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언할 수 있다.</li>
<li>생성자 파라미터 <em>nickname에서 맨 앞의 밑줄(\</em>)은 프로퍼티와 생성자 파라미터를 구분해준다.<ul>
<li>다른 방법으로 this.nickname = nickname 방식처럼 this를 써서 모호성을 없애도 된다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>주 생성자 앞에 별다른 애노테이션이나 가시성 변경자가 없다면 constructor를 생략해도 된다.</p>
<pre><code class="language-kotlin">class User(_nickname: String) { // 파라미터가 하나뿐인 주 생성자
  val nickname = _nickname // 프로퍼티를 주 생성자의 파라미터로 초기화한다.
} </code></pre>
<blockquote>
<p>프로퍼티를 초기화하는 식이나 초기화 블록 안에서만 주 생성자의 파라미터를 참조할 수 있다는 점에 유의하라.</p>
</blockquote>
</li>
<li><p>주 생성자 파라미터 이름 앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.</p>
<pre><code class="language-kotlin">class User(val nickname: String) // &quot;val&quot;은 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다. </code></pre>
</li>
<li><p>함수 파라미터와 마찬가지로 생성자 파라미터에도 디폴트 값을 정의할 수 있다.</p>
<pre><code class="language-kotlin">class User(val nickname: String, val isSubscribed: Boolean = true) // 생성자 파라미터에 대한 디폴트 값을 제공한다. </code></pre>
<pre><code class="language-java">public class User {
  private final String nickname;
  private final boolean isSubscribed;

  // 생성자 파라미터에 대한 디폴트 값을 제공한다.
  public User(String nickname) {
      this(nickname, true);
  }

  public User(String nickname, boolean isSubscribed) {
      this.nickname = nickname;
      this.isSubscribed = isSubscribed;
  }

  public String getNickname() {
      return nickname;
  }

  public boolean isSubscribed() {
      return isSubscribed;
  }
}</code></pre>
</li>
<li><p>클래스의 인스턴스를 만들려면 new 키워드 없이 생성자를 직접 호출하면 된다.</p>
<pre><code class="language-kotlin">val hynn = User(&quot;현석&quot;) // isSubscribed 파라미터에는 디폴트 값이 쓰인다.
println(hyun.isSubscribed) // true
</code></pre>
</li>
</ul>
<p>val gye = User(&quot;계영&quot;, false) // 모든 인자를 파라미터 선언 순서대로 지정할 수도 잇다.
println(gye.isSubscribed) // false</p>
<p>val hey = User(&quot;혜원&quot;, isSubscribed = false) // 생성자 인자 중 일부에 대해 이름을 지정할 수도 있다.
println(hey.isSubscribed) // false</p>
<pre><code>- 클래스에 기반 클래스가 있다면 주 생성자에서 기반 클래스의 생성자를 호출해야 할 필요가 있다. 기반 클래스를 초기화하려면 기반 클래스 이름 뒤에 괄호를 치고 생성자 인자를 넘긴다.
```kotlin
open class User(val nickname: String) { ... }

class TwitterUser(nickname: String) : User(nickname) { ... }</code></pre><ul>
<li>클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무 일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.<pre><code class="language-kotlin">open class Button // 인자가 없는 디폴트 생성자가 만들어진다.</code></pre>
</li>
<li>Button의 생성자는 아무 인자도 받지 않지만, Button 클래스를 상속한 하위 클래스는 반드시 Button 클래스의 생성자를 호출해야 한다.<pre><code class="language-kotlin">class RadioButton: Button() </code></pre>
</li>
<li>이 규칙으로 인해 기반 클래스의 이름 뒤에는 꼭 빈 괄호가 들어간다(물론 생성자 인자가 있다면 괄호 안에 인자가 들어간다).</li>
<li>반면 인터페이스는 생성자가 없기 때문에 어떤 클래스가 인터페이스를 구현하는 경우 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무 괄호도 없다.</li>
</ul>
<ul>
<li>어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.<pre><code class="language-kotlin">class Secretive private constructor() {} // 이 클래스의 (유일한) 주 생성자는 비공개다. </code></pre>
</li>
<li>Secretive 클래스 안에는 주 생성자밖에 없고 그 주 생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.</li>
</ul>
<blockquote>
<p>유틸리티 함수를 담아두는 역할만을 하는 클래스는 인스턴스화할 필요가 없고, 싱글턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야 한다. 코틀린에서는 이런 경우를 언어에서 기본 지원한다. 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글톤을 사용하고 싶으면 객체를 선언하면 된다.</p>
</blockquote>
<pre><code class="language-kotlin">class Singleton private constructor(private val data: String) {
    // 싱글톤 객체
    companion object {
        private var instance: Singleton? = null

        // 미리 정한 팩토리 메소드
        fun getInstance(): Singleton {
            if (instance == null) {
                instance = Singleton(&quot;Default Data&quot;)
            }
            return instance!!
        }
    }

    fun getData(): String {
        return data
    }
}

fun main() {
    // 객체 생성은 팩토리 메소드를 통해서만 허용
    val singletonInstance = Singleton.getInstance()

    // 객체의 메소드 호출
    println(singletonInstance.getData()) // Default Data
}</code></pre>
<h3 id="부-생성자-상위-클래스를-다른-방식으로-초기화">부 생성자: 상위 클래스를 다른 방식으로 초기화</h3>
<blockquote>
<p>💡 주 생성자를 보완하거나 다른 초기화 동작을 수행하기 위해 클래스에 추가적인 생성자를 정의할 때 사용되는 생성자다.</p>
</blockquote>
<pre><code class="language-kotlin">class Person {
    var name: String = &quot;&quot;
    var age: Int = 0

    constructor(name: String) {
        this.name = name
    }

    constructor(age: Int) {
        this.age = age
    }

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}</code></pre>
<pre><code class="language-kotlin">class Person(val name: String = &quot;&quot;, val age: Int = 0)</code></pre>
<blockquote>
<p>인자에 대한 디폴트 값을 제공하기 위해 부 생성자를 여럿 만들지 말라. 대신 파라미터의 디폴트 값을 생성자 시그니처에 직접 명시하라.</p>
</blockquote>
<pre><code class="language-kotlin">open class View {
    constructor(ctx: Context) { // 부 생성자
        // 코드
    }
    constructor(ctx: Context, attr: AttributeSet) { // 부 생성자
        // 코드
    }
}</code></pre>
<ul>
<li>이 클래스는 주 생성자를 선언하지 않고(클래스 헤더에 있는 클래스 이름 뒤에 괄호가 없다), 부 생성자만 2가지 선언한다.</li>
<li>부 생성자는 constructor 키워드로 시작한다.<pre><code class="language-kotlin">// 상위 클래스의 생성자를 호출한다.
class MyButton : View {
  constructor(ctx: Context) : super(ctx) {
      // ...
  }
  constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
      // ...
  }
} </code></pre>
</li>
<li>두 부 생성자는 <a href="https://velog.io/@dev-baik/super-%ED%82%A4%EC%9B%8C%EB%93%9C#super-1">super() 키워드</a>를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.</li>
<li>MyButton의 생성자가 상위 클래스(View) 생성자에게 객체 생성을 위임한다.</li>
</ul>
<ul>
<li>생성자에서 this()를 통해 클래스 자신의 다른 생성자를 호출할 수 있다.<pre><code class="language-kotlin">class MyButton: View {
  constructor(ctx: Context): this(ctx, MY_STYLE) { // 이 클래스의 다른 생성자에게 위임한다.
      // ...
  }
  constructor(ctx: Context, attr: AttributeSet): super(ctx, attr) {
      // ...
  }
} </code></pre>
</li>
<li>MyButton 클래스의 생성자 중 하나가 파라미터의 디폴트 값을 넘겨서 같은 클래스의 다른 생성자(this를 사용해 참조함)에게 생성을 위임한다. 두번째 생성자는 여전히 super()를 호출한다.</li>
<li>클래스에 주 생성자가 없다면 모든 부 생성자는 반드시 상위 클래스를 초기화하거나 다른 생성자에게 생성을 위임해야 한다.<ul>
<li>위 예시처럼 각 부 생성자에게 객체 생성을 위임하는 화살표를 따라가면 그 끝에는 상위 클래스 생성자를 호출하는 화살표가 있어야 한다는 뜻이다.</li>
</ul>
</li>
</ul>
<h3 id="인터페이스에-선언된-프로퍼티-구현">인터페이스에 선언된 프로퍼티 구현</h3>
<ul>
<li>코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.<pre><code class="language-kotlin">interface User {
  val nickname: String
}</code></pre>
</li>
<li>User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야 한다는 뜻이다.</li>
<li>인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터 등의 정보가 들어있지 않다.<ul>
<li>사실 인터페이스는 아무 상태도 포함할 수 없으므로 상태를 저장할 필요가 있다면 인터페이스를 구현한 하위 클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야 한다.<pre><code class="language-kotlin">// 인터페이스의 프로퍼티 구현하기
class PrivateUser(override val nickname: String) : User // 주 생성자에 있는 프로퍼티
</code></pre>
</li>
</ul>
</li>
</ul>
<p>class SubscribingUser(val email: String) : User {
    override val nickname: String
      get() = email.substringBefore(&#39;@&#39;) // 커스텀 게터
}</p>
<p>class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId) // 프로퍼티 초기화 식
}</p>
<p>println(PrivateUser(&quot;<a href="mailto:test@kotlinlang.org">test@kotlinlang.org</a>&quot;).nickname) // <a href="mailto:test@kotlinlang.org">test@kotlinlang.org</a>
println(SubscribingUser(&quot;<a href="mailto:test@kotlinlang.org">test@kotlinlang.org</a>&quot;).nickname) // test</p>
<pre><code>- PrivateUser는 주 생성자 안에 프로퍼티를 직접 선언하는 간결한 구문을 사용한다. 이 프로퍼티는 User의 추상 프로퍼티를 구현하고 있으므로 override를 표시해야 한다.
- SubscribingUser는 커스텀 게터로 nickname 프로퍼티를 설정한다. 이 프로퍼티는 뒷받침하는 필드에 값을 저장하지 않고 매번 이메일 주소에서 별명을 계산해 반환한다.
- FacebookUser에서는 초기화 식으로 nickname 값을 초기화한다. 이때 페이스북 사용자 ID를 받아서 그 사용자의 이름을 반환해주는 getFacebookName 함수를 호출해서 nickname을 초기화한다.

&gt; SubscribingUser와 FacebookUser의 nickname 구현 차이에 주의하라.

- SubscribingUser의 nickname은 매번 호출될 때마다 substringBefore를 호출해 계산하는 커스텀 게터를 활용한다.
- FacebookUser의 nickname은 객체 초기화 시 계산한 데이터를 뒷받침하는 필드에 저장했다가 불러오는 방식을 활용한다.


- 인터페이스에는 추상 프로퍼티뿐 아니라 게터와 세터가 있는 프로퍼티를 선언할 수도 있다.
- 물론 그런 게터와 세터는 뒷받침하는 필드를 참조할 수 없다(뒷받침하는 필드가 있다면 인터페이스에 상태를 추가하는 셈인데 인터페이스는 상태를 저장할 수 없다).
```kotlin
interface User {
    val email: String

    // 프로퍼티에 뒷받침하는 필드가 없다. 대신 매번 결과를 계산해 돌려준다.
    val nickname: String
      get() = email.substringBefore(&#39;@&#39;) 
} </code></pre><ul>
<li>하위 클래스는 추상 프로퍼티인 email을 반드시 오버라이드해야 한다. 반면 nickname은 오버라이드하지 않고 상속할 수 있다.<pre><code class="language-kotlin">class BasicUser(override val email: String) : User {
  // email 프로퍼티를 오버라이드해야 한다.
  // nickname은 이미 인터페이스에서 구현되어 있으므로 따로 오버라이드하지 않아도 된다.
}
</code></pre>
</li>
</ul>
<p>fun main() {
    val user = BasicUser(&quot;<a href="mailto:example@example.com">example@example.com</a>&quot;)</p>
<pre><code>// email 프로퍼티 사용
println(&quot;Email: ${user.email}&quot;)

// nickname 프로퍼티 사용 (자동으로 상속됨)
println(&quot;Nickname: ${user.nickname}&quot;)</code></pre><p>} </p>
<pre><code>
### 게터와 세터에서 뒷받침하는 필드에 접근
- 값을 저장하는 동시에 로직을 실행할 수 있게 하기 위해서는 접근자 안에서 프로퍼티를 뒷받침하는 필드에 접근할 수 있어야 한다.
```kotlin
// 세터에서 뒷받침하는 필드 접근하기
class User(val name: String) {
    var address: String = &quot;unspecified&quot;
      set(value: String) {
          println(&quot;&quot;&quot;
            Address was changed for $name:
            &quot;$field&quot; -&gt; &quot;$value&quot;.&quot;&quot;&quot;.trimIndent()) // 뒷받침하는 필드 값 읽기 
          field = value
      }
}

val user = User(&quot;Alice&quot;)
user.address = &quot;Elsenheimerstrasse 47, 80687 Muenchen&quot;</code></pre><ul>
<li>코틀린에서 프로퍼티의 값을 바꿀 때는 <code>user.address = &quot;new value&quot;</code>처럼 필드 설정 구문을 사용한다.<ul>
<li>이 구문은 내부적으로 address의 세터를 호출한다.</li>
</ul>
</li>
<li><span style='background-color: #fff5b1'/>접근자의 본문에서는 <code>field</code>라는 특별한 식별자를 통해 뒷받침하는 필드에 접근할 수 있다.<ul>
<li>게터에서는 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽거나 쓸 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>뒷받침하는 필드가 있는 프로퍼티와 그런 필드가 없는 프로퍼티에 어떤 차이가 있나?</strong></p>
<ul>
<li>클래스의 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는 방법이나 쓰는 방법은 뒷받침하는 필드의 유무와는 관계가 없다.</li>
<li>컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.</li>
<li>다만 field를 사용하지 않는 커스텀 접근자 구현을 정의한다면 뒷받침하는 필드는 존재하지 않는다.</li>
</ul>
</blockquote>
<ul>
<li><p>프로퍼티에 대한 뒷받침 필드가 어떻게 생성되는지 확인</p>
<pre><code class="language-kotlin">public final class CustomAccessorExample {
  private int counter;  // 이 부분이 뒷받침 필드를 나타냅니다.

  public final int getCounter() {
      return this.counter;
  }

  private final void setCounter(int var1) {
      this.counter = var1;
  }
  // doubledCounter의 경우에는 별도의 뒷받침 필드가 생성되지 않고, getter에서 직접 계산
  public final int getDoubledCounter() {
      return this.getCounter() * 2;
  }

  public final void incrementCounter() {
      this.setCounter(this.getCounter() + 1);
  }
}</code></pre>
<pre><code class="language-kotlin">class CustomAccessorExample {
  // 뒷받침 필드가 자동으로 생성됨
  var counter = 0
    private set

  // 뒷받침 필드가 자동으로 생성됨
  val doubledCounter: Int
    get() {
        // 여기서는 field를 사용하지 않고, 직접 계산하여 반환
        return counter * 2
    }

  fun incrementCounter() {
      counter++
  }
}
</code></pre>
</li>
</ul>
<p>fun main() {
    val example = CustomAccessorExample()</p>
<pre><code>println(example.counter) // 출력: 0
println(example.doubledCounter) // 출력: 0

example.incrementCounter()

println(example.counter) // 출력: 1
println(example.doubledCounter) // 출력: 2</code></pre><p>}</p>
<pre><code>
### 접근자의 가시성 변경
- 접근자의 가시성은 기본적으로는 프로퍼티의 가시성과 같다. 하지만 원한다면 get이나 set 앞에 가시성 변경자를 추가해서 접근자의 가시성을 변경할 수 있다.
```kotlin
// 비공개 세터가 있는 프로퍼티 선언하기
class LengthCounter {
    var counter: Int = 0
      private set // 이 클래스 밖에서 이 프로퍼티의 값을 바꿀 수 없다.

    fun addWord(word: String) {
        counter += word.length
    }
}

val lengthCounter = LengthCounter()
lengthCounter.addWord(&quot;Hi!&quot;)
println(lengthCounter.counter) // 3</code></pre><ul>
<li>이 클래스는 자신에게 추가된 모든 단어의 길을 합산한다. 전체 길이를 저장하는 프로퍼티는 클라이언트에게 제공하는 API의 일부분이므로 public으로 외부에 공개한다.</li>
<li>단어 길이의 합을 마음대로 바꾸지 못하게 기본 가시성을 가진 게터를 컴파일러가 생성하게 내버려 두는 대신 세터의 가시성을 private으로 지정한다.</li>
</ul>
<h2 id="컴파일러가-생성한-메소드-데이터-클래스와-클래스-위임">컴파일러가 생성한 메소드: <a href="https://velog.io/@dev-baik/Data-Class">데이터 클래스</a>와 클래스 위임</h2>
<h3 id="모든-클래스가-정의해야-하는-메소드">모든 클래스가 정의해야 하는 메소드</h3>
<pre><code class="language-kotlin">class Client(val name: String, val postalCode: Int)</code></pre>
<h4 id="문자열-표현-tostring">문자열 표현: toString()</h4>
<ul>
<li>자바처럼 코틀린이 모든 클래스도 인스턴스의 문자열 표현을 얻을 방법을 제공한다. 주로 디버깅과 로깅 시 이 메소드를 사용한다.</li>
</ul>
<pre><code class="language-kotlin">class Client(val name: String, val postalCode: Int) {
    override fun toString() = &quot;Client(name=$name, postalCode=$postalCode)&quot;
}

val client1 = Client(&quot;오현석&quot;, 4122)
println(client1) // Client(name=오현석, postalCode=4122)</code></pre>
<h4 id="객체의-동등성-equals">객체의 동등성: equals()</h4>
<ul>
<li>Client 클래스를 사용하는 모든 계산은 클래스 밖에서 이뤄진다. Client는 단지 데이터를 저장할 뿐이며, 그에 따라 구조도 단순하고 내부 정보를 투명하게 외부에 노출하게 설계됐다.<pre><code class="language-kotlin">val client1 = Client(&quot;오현석&quot;, 4122)
val client2 = Client(&quot;오현석&quot;, 4122)
</code></pre>
</li>
</ul>
<p>// 코틀린에서 == 연산자는 참조 동일성을 검사하지 않고 객체의 동등성을 검사한다.
// 따라서 == 연산은 equals를 호출하는 식으로 컴파일된다.
println(client1 == client2) // false</p>
<pre><code>
```kotlin
// Client에 equals() 구현하기
class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
            return false
        return name == other.name &amp;&amp;
              postalCode == other.postalCode
    }

    override fun toString() = &quot;Client(name=$name, postalCode=$postalCode)&quot;
}</code></pre><h4 id="해시-컨테이너-hashcode">해시 컨테이너: hashCode()</h4>
<pre><code class="language-kotlin">val processed = hashSetOf(Client(&quot;오현석&quot;, 4122))
println(processed.contains(Client(&quot;오현석&quot;, 4122))) // false</code></pre>
<ul>
<li>hashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시 코드를 비교하고 해시 코드가 같은 경우에만 실제 값을 비교한다.</li>
<li>원소 객체들이 해시 코드에 대한 규칙을 지키지 않는 경우 HashSet은 제대로 작동할 수 없다.<ul>
<li>해시 코드 규칙 : equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.<pre><code class="language-kotlin">// Client에 hashCode 구현하기
class Client(val name: String, val postalCode: Int) {
...
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}</code></pre>
</li>
</ul>
</li>
<li>코틀린 컴파일러는 이 모든 메소드를 자동으로 생성해줄 수 있다.</li>
</ul>
<h3 id="데이터-클래스-모든-클래스가-정의해야-하는-메소드-자동-생성">데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성</h3>
<blockquote>
<p>💡 데이터 관리에 최적화된 클래스로 toString(), equals(), hashCode(), copy(), componentN() 5가지 유용한 함수들을 내부적으로 컴파일러가 자동 생성해준다.</p>
</blockquote>
<ul>
<li>data라는 변경자를 클래스 앞에 붙이면 필요한 메소드(toString, equals, hashCode)를 컴파일러가 자동으로 만들어준다.</li>
<li>data 변경자가 붙은 클래스를 데이터 클래스라고 부른다.<pre><code class="language-kotlin">// Client를 데이터 클래스로 선언하기
data class Client(val name: String, val postalCode: Int)</code></pre>
<blockquote>
<ul>
<li>인스턴스 간 비교를 위한 equals</li>
<li>HashMap과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode</li>
<li>클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString</li>
</ul>
</blockquote>
</li>
</ul>
<h4 id="데이터-클래스와-불변성-copy-메소드">데이터 클래스와 불변성: copy() 메소드</h4>
<blockquote>
<p>데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.</p>
</blockquote>
<ul>
<li><p><strong>WHY)</strong> HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.</p>
<ul>
<li>데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.</li>
<li>게다가 불변 객체를 사용하면 프로그램에 대해 훨씬 쉽게 추론할 수 있다.</li>
<li>불변 객체를 주로 사용하는 프로그램에서는 스레드가 사용 중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야 할 필요가 줄어든다. (다중스레드 프로그램의 경우)</li>
</ul>
</li>
<li><p>데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용할 수 있게 코틀린 컴파일러는 한 가지 편의 메소드를 제공한다.</p>
<ul>
<li>객체를 복사하면서 일부 프로퍼티를 바꿀 수 있게 해주는 copy 메소드다.</li>
<li>복사본은 원본과 다른 생명주기를 가지며, 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.<pre><code class="language-kotlin">class Client(val name: String, val postalCode: Int) {
  ...
  fun copy(name: String = this.name,
          postalCode: Int = this.postalCode) =
     Client(name, postalCode)
}
</code></pre>
</li>
</ul>
<p>val lee = Client(&quot;이계영&quot;, 4122)
println(lee.copy(postalCode = 4000)) // Client(name=이계영, postalCode=4000)</p>
<pre><code></code></pre></li>
</ul>
<h3 id="클래스-위임-by-키워드-사용">클래스 위임: by 키워드 사용</h3>
<ul>
<li>대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에 의해 발생한다.<ul>
<li>하위 클래스가 상위 클래스의 메소드 중 일부를 오버라이드하면 하위 클래스는 상위 클래스의 세부 구현 사항에 의존하게 된다.<ul>
<li>시스템이 변함에 따라 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다. 그 과정에서 하위 클래스가 상위 클래스에 대해 갖고 있던 가정이 깨져서 코드가 정상적으로 작동하지 못하는 경우가 생길 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>모든 클래스를 기본적으로 final로 취급하면 상속을 염두에 두고 open 변경자로 열어둔 클래스만 확장할 수 있다. 열린 상위 클래스의 소스코드를 변경할 때는 open 변경자를 보고 해당 클래스를 다른 클래스가 상속하리라 예상할 수 있으므로, 변경 시 하위 클래스를 깨지 않기 위해 좀 더 조심할 수 있다.</p>
</li>
<li><p><strong>BUT)</strong> 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야 할 때가 있다.</p>
</li>
<li><p><strong>HOW)</strong> <span style='background-color: #fff5b1'/>데코레이터(Decorator) 패턴</p>
<ul>
<li><p><strong>핵심)</strong> 상속을 허용하지 않는 클래스(기존 클래스) 대신 사용할 수 있는 새로운 클래스(데코레이터)를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스를 데코레이터 내부에 필드로 유지하지는 것이다.</p>
</li>
<li><p>새로 정의해야 하는 기능은 데코레이터의 메소드에 새로 정의하고(물론 이때 기존 클래스의 메소드나 필드를 활용할 수 있다) 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청을 전달한다.</p>
</li>
<li><p><strong>단점)</strong> 준비 코드가 상당히 많이 필요하다. 그래서 IDE는 컴파일러를 통해 자동 생성해준다.</p>
<pre><code class="language-kotlin">class DelegatingCollection&lt;T&gt; : Collection&lt;T&gt; {
private val innerList = arrayListOf&lt;T&gt;()

override val size: Int get() = innerList.size()
override fun isEmpty(): Boolean = innerList.isEmpty()
override fun contains(element: T): Boolean = innerList.contains(element)
override fun iterator(): Iterator&lt;T&gt; = innerList.iterator()
override fun containsAll(elements: Collection&lt;T&gt;): Boolean =
    innerList.containsAll(elements)
}</code></pre>
</li>
</ul>
</li>
<li><p>이런 위임을 언어가 제공하는 일급 시민 기능으로 지원한다는 점이 코틀린의 장점이다.</p>
</li>
<li><p>인터페이스를 구현할 때 by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.</p>
<pre><code class="language-kotlin">class DelegatingCollection&lt;T&gt;(
  innerList: Collection&lt;T&gt; = ArrayList&lt;T&gt;()
) : Collection&lt;T&gt; by innerList { }</code></pre>
</li>
<li><p>메소드 중 일부의 동작을 변경하고 싶은 경우 메소드를 오버라이드하면 컴파일러가 생성한 메소드 대신 오버라이드한 메소드가 쓰인다.</p>
<pre><code class="language-kotlin">class CountingSet&lt;T&gt;(
  val innerSet: MutableCollection&lt;T&gt; = HashSet&lt;T&gt;()
) : MutableCollection&lt;T&gt; by innerSet {
  var objectsAdded = 0

  override fun add(element: T): Boolean {
      objectsAdded++
      return innerSet.add(element)
  }

  override fun addAll(c: Collection&lt;T&gt;): Boolean {
      objectsAdded += c.size
      return innerSet.addAll(c)
  }
}
</code></pre>
</li>
</ul>
<p>val cset = CountingSet<Int>()
cset.addAll(listOf(1, 1, 2))
println(&quot;${cset.objectsAdded} objects were added, ${cset.size} remain&quot;) </p>
<p>// 3 objects were added, 2 remain</p>
<pre><code>- add와 addAll을 오버라이드해서 카운터를 증가시키고, MutableCollection 인터페이스의 나머지 메소드는 내부 컨테이너(innerSet)에게 위임한다.
- 핵심) CountingSet에 MutableCollection의 구현 방식에 대한 의존관계가 생기지 않는다는 점이 중요하다.

## [object 키워드: 클래스 선언과 인스턴스 생성](https://velog.io/@dev-baik/Object-vs-Companion-Object)
- 코틀린에서는 object 키워드를 다양한 상황에서 사용하지만 모든 경우 클래스를 정의하면서 동시에 인스턴스(객체)를 생성한다는 공통점이 있다.
&gt; - 객체 선언은 싱글턴을 정의하는 방법 중 하나다.
&gt; - 동반 객체(companion object)는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 때 쓰인다. 동반 객체 메소드에 접근할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다.
&gt; - 객체 식은 자바의 무명 내부 클래스 대신 쓰인다.

### 객체 선언: 싱글턴을 쉽게 만들기
- 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
- 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴을 통해 이를 구현한다.
- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;코틀린은 객체 선언 기능을 통해 싱글턴을 언어에서 기본 지원한다. 객체 선언은 클래스 선언과 그 클래스에 속한 단일 인스턴스의 선언을 합친 선언이다.
```kotlin
object Payroll {
    val allEmployees = arrayListOf&lt;Person&gt;()

    fun calculateSalary() {
        for (person in allEmployees) {
            ...
        }
    }
} </code></pre><ul>
<li>객체 선언은 object 키워드로 시작한다. 객체 선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.</li>
<li>클래스와 마찬가지로 객체 선언 안에도 프로퍼티, 메소드, 초기화 블록 등이 들어갈 수 있다. 하지만 생성자는(주 생성자와 부 생성자 모두) 객체 선언에 쓸 수 없다.<ul>
<li><span style='background-color: #fff5b1'/>일반 클래스 인스턴스와 달리 싱글턴 객체는 객체 선언문이 있는 위치에서 생성자 호출 없이 즉시 만들어진다. 따라서 객체 선언에는 생성자 정의가 필요 없다.</li>
</ul>
</li>
<li>변수와 마찬가지로 객체 선언에 사용한 이름 뒤에 마침표(.)를 붙이면 객체에 속한 메소드나 프로퍼티에 접근할 수 있다.<pre><code class="language-kotlin">Payroll.allEmployees.add(Person(...))
Payroll.calculateSalary()</code></pre>
</li>
<li>객체 선언도 클래스나 인터페이스를 상속할 수 있다. 프레임워크를 사용하기 위해 특정 인터페이스를 구현해야 하는데, 그 구현 내부에 다른 상태가 필요하지 않은 경우에 이런 기능이 유용하다.<ul>
<li>어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다. 따라서 Comparator 인스턴스를 만드는 방법으로는 객체 선언이 가장 좋은 방법이다.<pre><code class="language-kotlin">object CaseInsensitiveFileComparator : Comparator&lt;File&gt; {
override fun compare(file1: File?, file2: File?): Int {
    return file1.path.compareTo(file2.path, ignoreCase = true)
}
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>println(CaseInsensitiveFileComparator.compare(File(&quot;/User&quot;), File(&quot;/user&quot;))) // 0</p>
<pre><code>- 일반 객체(클래스 인스턴스)를 사용할 수 있는 곳에서는 항상 싱글턴 객체를 사용할 수 있다.
```kotlin
val files = listOf(File(&quot;/Z&quot;), File(&quot;/a&quot;))
println(files.sortedWith(CaseInsensitiveFileComparator)) // [/a, /Z]</code></pre><blockquote>
<p>대규모 소프트웨어 시스템에서는 객체 생성을 제어할 방법이 없고 생성자 파라미터를 지정할 수 없어 객체 선언이 항상 적합하지는 않다. 단위 테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다. 따라서 의존관계 주입 프레임워크와 코틀린 클래스를 함께 사용해야 한다.</p>
</blockquote>
<ul>
<li>클래스 안에서 객체를 선언할 수도 있다. <span style='background-color: #fff5b1'/>그런 객체도 인스턴스는 단 하나뿐이다(바깥 클래스의 인스턴스마다 중첩 객체 선언에 해당하는 인스턴스가 하나씩 따로 생기는 것이 아니다).</li>
<li>어떤 클래스의 인스턴스를 비교하는 Comparator를 클래스 내부에 정의하는 게 더 바람직하다.<pre><code class="language-kotlin">// 중첩 객체를 사용해 Comparator 구현하기
data class Person(val name: String) {
  object NameComparator : Comparator&lt;Person&gt; {
      override fun compare(p1: Person?, p2: Person?): Int =
          p1.name.compareTo(p2.name)
  }
}
</code></pre>
</li>
</ul>
<p>val persons = listOf(Person(&quot;Bob&quot;), Person(&quot;Alice&quot;))
println(persons.sortedWith(Person.NameComparator)) // [Person(name=Alice), Person(name=Bob)]</p>
<pre><code>
### 동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
- 코틀린 클래스 안에는 정적인 멤버가 없다. 코틀린 언어는 자바 static 키워드를 지원하지 않는다.
- 그 대신 코틀린에서는 패키지 수준의 최상위 함수(자바의 정적 메소드 역할을 거의 대신 할 수 있다)와 객체 선언(자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신할 수 있다)을 활용한다.
- 대부분의 경우 최상위 함수를 활용하는 편을 더 권장한다.


- 최상위 함수는 private으로 표시된 클래스 비공개 멤버에 접근할 수 없다. 그래서 &lt;span style=&#39;background-color: #fff5b1&#39;&gt;클래스의 인스턴스와 관계없이 호출해야 하지만, 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야 한다.&lt;/span&gt; 그런 함수의 대표적인 예로 팩토리 메소드를 들 수 있다.
- 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체를 만들 수 있다.
- 동반 객체의 프로퍼티나 메소드에 접근하려면 그 동반 객체가 정의된 클래스의 이름을 사용한다. 이때 객체의 이름을 따로 지정할 필요가 없다.
- 그 결과 동반 객체의 멤버를 사용하는 구문은 자바의 정적 메소드 호출이나 정적 필드 사용 구문과 같아진다.
```kotlin
class A {
    companion object {
        fun bar() {
            println(&quot;Companion object called&quot;)
        }
    }
}

A.bar() // Companion object called</code></pre><ul>
<li><p>동반 객체가 private 생성자를 호출하기 좋은 위치다. <span style='background-color: #fff5b1'/>동반 객체는 자신을 둘러싼 클래스의 모든 private 멤버에 접근할 수 있다. 따라서 동반 객체는 바깥쪽 클래스의 private 생성자도 호출할 수 있다. 따라서 동반 객체는 팩토리 패턴을 구현하기 가장 적합한 위치다.</p>
<pre><code class="language-kotlin">// 부 생성자가 여럿 있는 클래스 정의하기
class User {
  val nickname: String

  constructor(email: String) {
      nickname = email.substringBefore(&#39;@&#39;)
  }

  constructor(facebookAccountId: Int) {
      nickname = getFacebookName(facebookAccountId)
  }
}</code></pre>
</li>
<li><p>이런 로직을 표현하는 더 유용한 방법으로 클래스의 인스턴스를 생성하는 팩토리 메소드가 있다.</p>
</li>
<li><p>생성자를 통해 User 인스턴스를 만들 수 없고 팩토리 메소드를 통해야만 한다.</p>
<pre><code class="language-kotlin">class User private constructor(val nickname: String) { // 주 생성자를 비공개로 만든다.
  companion object {
      fun newSubscribingUser(email: String) =
          User(email.substringBefore(&#39;@&#39;))

      fun newFacebookUser(accountId: Int) =
          User(getFacebookName(accountId))
  }
}
</code></pre>
</li>
</ul>
<p>val subscribingUser = User.newSubscribingUser(&quot;<a href="mailto:bob@gmail.com">bob@gmail.com</a>&quot;)
val facebookUser = User.newFacebookUser(4)
println(subscribingUser.nickname) // bob</p>
<pre><code>- 팩토리 메소드는 그 팩토리 메소드가 선언된 클래스의 하위 클래스 객체를 반환할 수 있다. SubscribingUser와 Facebookuser 클래스가 따로 존재한다면 그때그때 필요에 따라 적당한 클래스의 객체를 반환할 수 있다.

&gt; 클래스를 확장해야만 하는 경우에는 동반 객체 멤버를 하위 클래스에서 오버라이드할 수 없으므로 여러 생성자를 사용하는 편이 더 나은 해법이다.

### 동반 객체를 일반 객체처럼 사용
- 동반 객체는 클래스 안에 정의된 일반 객체다. 따라서 동반 객체에 이름을 붙이거나, 동반 객체가 인터페이스를 상속하거나, 동반 객체 안에 확장 함수와 프로퍼티를 정의할 수 잇다.
```kotlin
// 동반 객체에 이름 붙이기
class Person(val name: String) {
    companion object Loader { // 동반 객체에 이름을 붙인다.
        fun fromJSON(jsonText: String): Person = ...
    }
}

person = Person.Loader.fromJSON(&quot;{name: &#39;Dmitry&#39;}&quot;)
person.name // Dmitry

person2 = Person.Loader.fromJSON(&quot;{name: &#39;Brent&#39;}&quot;)
person2.name // Brent</code></pre><h4 id="동반-객체에서-인터페이스-구현">동반 객체에서 인터페이스 구현</h4>
<pre><code class="language-kotlin">interface JSONFactory&lt;T&gt; {
    fun fromJSON(jsonText: String): T
}

class Person(val name: String) {
    companion object : JSONFactory&lt;Person&gt; {
        override fun fromJSON(jsonText: String): Person = ... // 동반 객체가 인터페이스를 구현한다.
    }
}</code></pre>
<ul>
<li>이제 JSON으로부터 각 원소를 다시 만들어내는 추상 팩토리가 있다면 Person 객체를 그 팩토리에게 넘길 수 있다.<pre><code class="language-kotlin">fun loadFromJSON&lt;T&gt;(factory: JSONFactory&lt;T&gt;): T {
  ...
}
</code></pre>
</li>
</ul>
<p>loadFromJSON(Person) // 동반 객체의 인스턴스를 함수에 넘긴다.</p>
<pre><code>- 동반 객체가 구현한 JSONFactory의 인스턴스를 넘길 때 Person 클래스의 이름을 사용했다는 점에 유의하라.

#### 동반 객체 확장
```kotlin
// 동반 객체에 대한 확장 함수 정의하기
class Person(val firstName: String val lastName: String) {
    companion object { } // 비어잇는 동반 객체를 선언한다.
}

fun Person.Companion.fromJSON(json: String) : Person { // 확장 함수를 선언한다.
    ...
}

val p = Person.fromJSON(json)</code></pre><ul>
<li>마치 동반 객체 안에서 fromJSON 함수를 정의한 것처럼 fromJSON을 호출할 수 있다. 하지만 실제로 fromJSON은 클래스 밖에서 정의한 확장 함수다. 다른 보통 확장 함수처럼 fromJSON도 클래스 멤버 함수처럼 보이지만, 실제로는 멤버 함수가 아니다.</li>
</ul>
<blockquote>
<p>동반 객체에 대한 확장 함수를 작성할 수 있으려면 원래 클래스에 동반 객체를 꼭 선언해야 한다는 점에 주의하라. 설령 빈 객체라도 동반 객체가 꼭 있어야 한다.</p>
</blockquote>
<h3 id="객체-식-무명-내부-클래스를-다른-방식으로-작성">객체 식: 무명 내부 클래스를 다른 방식으로 작성</h3>
<ul>
<li><p>무명 객체는 자바의 무명 내부 클래스를 대신한다.</p>
<pre><code class="language-kotlin">// 무명 객체로 이벤트 리스너 구현하기
window.addMouseListener(
  object : MouseAdapter() { // MouseAdapter를 확장하는 무명 객체를 선언한다.
      // MouseAdapter의 메소드를 오버라이드한다.
      override fun mouseClicked(e: MouseEvent) {
          // ...
      }

      override fun mouseEntered(e: MouseEvent) {
          // ...
      }
  }
)</code></pre>
</li>
<li><p>객체 식은 클래스를 정의하고 그 클래스에 속한 인스턴스를 생성하지만, 그 클래스나 인스턴스에 이름을 붙이지는 않는다. </p>
</li>
<li><p>이런 경우 보통 함수를 호출하면서 인자로 무명 객체를 넘기기 때문에 클래스와 인스턴스 모두 이름이 필요하지 않다. 하지만 객체에 이름을 붙여야 한다면 변수에 무명 객체를 대입하면 된다.</p>
<pre><code class="language-kotlin">val listener = object : MouseAdapter() {
  override fun mouseClicked(e: MouseEvent) { ... }
  override fun mouseEntered(e: MouseEvent) { ... }
}</code></pre>
</li>
<li><p><span style='background-color: #fff5b1'/>한 인터페이스만 구현하거나 한 클래스만 확장할 수 있는 자바의 무명 내부 클래스와 달리 코틀린 무명 클래스는 여러 인터페이스를 구현하거나 클래스를 확장하면서 인터페이스를 구현할 수 있다.</p>
</li>
</ul>
<blockquote>
<p>객체 선언과 달리 무명 객체는 싱글턴이 아니다. 객체 식이 쓰일 때마다 새로운 인스턴스가 생성된다.</p>
</blockquote>
<ul>
<li><p>자바의 무명 클래스와 같이 객체 식 안의 코드는 그 식이 포함된 함수의 변수에 접근할 수 있다. 하지만 자바와 달리 final이 아닌 변수도 객체 식 안에서 사용할 수 있다. 따라서 객체 식 안에서 그 변수의 값을 변경할 수 있다.</p>
<pre><code class="language-kotlin">fun countClicks(window: Window) {
  var clickCount = 0

  window.addMouseListener(object: MouseAdapter() {
      override fun mouseClicked(e: MouseEvent) {
          clickCount++
      }
  })
} </code></pre>
<blockquote>
<p>객체 식은 무명 객체 안에서 여러 메소드를 오버라이드해야 하는 경우에 훨씬 더 유용하다. 메소드가 하나뿐인 인터페이스를 구현해야 한다면 코틀린의 SAM 변환(함수 리터럴을 변환해 SAM으로 만듦) 지원을 활용하는 편이 낫다. SAM 변환을 사용하려면 무명 객체 대신 함수 리터럴을 사용해야 한다.</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 3장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-3%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-3%EC%9E%A5</guid>
            <pubDate>Thu, 26 Oct 2023 02:47:43 GMT</pubDate>
            <description><![CDATA[<h1 id="함수-정의와-호출">함수 정의와 호출</h1>
<blockquote>
<ul>
<li>컬렉션, 문자열, 정규식을 다루기 위한 함수</li>
<li>이름 붙인 인자, 디폴트 파라미터 값, 중위 호출 문법 사용</li>
<li>확장 함수와 확장 프로퍼티를 사용해 자바 라이브러리 적용</li>
<li>최상위 및 로컬 함수와 프로퍼티를 사용해 코드 구조화</li>
</ul>
</blockquote>
<hr>
<h2 id="코틀린에서-컬렉션-만들기">코틀린에서 컬렉션 만들기</h2>
<pre><code class="language-kotlin">val set = hashSetOf(1, 7, 53)

val list = arrayListOf(1, 7, 53)

val map = hashMapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-threee&quot;)

println(set.javaClass) // class java.util.HashSet
println(list.javaClass) // class java.util.ArrayList
println(map.javaClass) // class java.util.HashMap</code></pre>
<ul>
<li>코틀린 컬렉션은 자바 컬렉션과 똑같은 클래스다. 이는 코틀린이 자신만의 컬렉션 기능을 제공하지 않는다는 뜻이다. 표준 자바 컬렉션을 활용하면 자바 코드와 상호작용하기가 훨씬 더 쉽다.</li>
<li>하지만 코틀린에서는 자바보다 더 많은 기능을 쓸 수 있다.<pre><code class="language-kotlin">val strings = listOf(&quot;first&quot;, &quot;second&quot;, &quot;fourteenth&quot;)
println(strings.last()) // fourteenth
</code></pre>
</li>
</ul>
<p>val numbers = setOf(1, 14, 2)
println(numbers.max()) // 14</p>
<pre><code>
## 함수를 호출하기 쉽게 만들기
- 자바 컬렉션에는 디폴트 toString 구현이 들어있다. 하지만 그 디폴트 toString의 출력 형식은 고정돼 있고 우리에게 필요한 형식이 아닐 수도 있다.
```kotlin
val list = listOf(1, 2, 3)
println(list) // [1, 2, 3]</code></pre><pre><code class="language-kotlin">// joitnToString() 함수의 초기 구현
fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
    separator: string,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in collection.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}</code></pre>
<ul>
<li>joinToString 함수는 컬렉션의 원소를 <a href="https://velog.io/@dev-baik/String-vs-StringBuilder-vs-StringBuffer">StringBuilder</a>의 뒤에 덧붙인다. 이때 원소 사이에 구분자(separator)를 추가하고, StringBuilder의 맨 앞과 맨 뒤에는 접두사(prefix)와 접미사(postfix)를 추가한다.</li>
<li>이 함수는 <a href="https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A0%9C%EB%84%A4%EB%A6%ADGenerics-%EA%B0%9C%EB%85%90-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0">제네릭(generic)</a>하다. 즉, 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다.</li>
</ul>
<blockquote>
<p>✅ 제네릭 : 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법</p>
</blockquote>
<pre><code class="language-kotlin">val list = listOf(1, 2, 3)
println(joinToString(list, &quot;; &quot;, &quot;(&quot;, &quot;)&quot;)) // (1; 2; 3)</code></pre>
<h3 id="이름-붙은-인자">이름 붙은 인자</h3>
<pre><code class="language-kotlin">joinToString(collection, separator = &quot; &quot;, prefix = &quot; &quot;, postfix = &quot;.&quot;)</code></pre>
<ul>
<li>코틀린으로 작성한 함수를 호출할 때는 함수에 전달하는 인자 중 일부(또는 전부)의 이름을 명시할 수 있다. 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.</li>
</ul>
<h3 id="디폴트-파라미터-값">디폴트 파라미터 값</h3>
<blockquote>
<p>💡 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있어 오버로드 중 상당수를 피할 수 있도록 도와준다.</p>
</blockquote>
<ul>
<li>자바에서는 일부 클래스에서 오버로딩(overloading)한 메서드가 너무 많아진다는 문제가 있다. java.lang.Thread에 8가지 생성자 메소드들은 하위 호환성을 유지하거나 API 사용자에게 편의를 더하는 등의 여러 가지 이유로 만들어진다. 하지만 어느 경우든 중복이라는 결과는 같다.</li>
<li>오버로딩 함수에 대해 대부분의 설명을 반복해 달아야하고, 인자 중 일부가 생략된 오버로드 함수를 호출할 때 어떤 함수가 불릴지 모호한 경우가 생긴다.<pre><code class="language-kotlin">// 디폴트 파라미터 값을 사용해 joinToString() 정의하기
fun &lt;T&gt; joinToString(
  collection: Collection&lt;T&gt;,
  separator: string = &quot;, &quot;,
  prefix: String = &quot;&quot;,
  postfix: String = &quot;&quot;
): String
</code></pre>
</li>
</ul>
<p>joinToString(list, &quot;, &quot;, &quot;&quot;, &quot;&quot;) // 1, 2, 3
joinToString(list) // 1, 2, 3
joinToString(list, &quot;; &quot;) // 1; 2; 3</p>
<pre><code>- 일반 호출 문법을 사용하려면 함수를 선언할 때와 같은 순서로 인자를 지정해야 한다. 그런 경우 일부를 생략하면 뒷부분의 인자들이 생략된다.
- 이름 붙인 인자를 사용하는 경우에는 인자 목록의 중간에 있는 인자를 생략하고, 지정하고 싶은 인자를 이름을 붙여서 순서와 관계없이 지정할 수 있다.
```kotlin
joinToString(list, postfix = &quot;;&quot;, prefix = &quot;# &quot;) // # 1, 2, 3; </code></pre><blockquote>
<p>함수의 디폴트 파라미터 값은 함수를 호출하는 쪽이 아니라 함수 선언 쪽에서 지정된다는 사실을 기억하라. 따라서 어떤 클래스 안에 정의된 함수의 디폴트 값을 바꾸고 그 클래스가 포함된 파일을 재컴파일하면 그 함수를 호출하는 코드 중에 값을 지정하지 않은 모든 인자는 자동으로 바뀐 디폴트 값을 적용받는다.</p>
</blockquote>
<h3 id="정적인-유틸리티-클래스-없애기-최상위-함수와-프로퍼티">정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티</h3>
<ul>
<li>객체지향 언어인 자바에서는 모든 코드를 클래스의 메소드를 작성해야 한다. 보통 그런 구조는 잘 작동하지만 실전에서는 어느 한 클래스에 포함시키기 어려운 코드가 많이 생긴다.</li>
<li>일부 연산에는 비슷하게 중요한 역할을 하는 클래스가 둘 이상 있을 수도 있다. 중요한 객체는 하나뿐이지만 그 연산을 객체의 인스턴스 API에 추가해서 API를 너무 크게 만들고 싶지는 않은 경우도 있다.<ul>
<li>그 결과 다양한 정적 메서드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메소드는 없는 클래스가 생겨난다.</li>
</ul>
</li>
</ul>
<p><code>객체의 인스턴스 API = 객체가 가지는 인스턴스 메서드와 프로퍼티</code></p>
<pre><code class="language-kotlin">// 비슷하게 중요한 역할을 하는 클래스가 둘 이상 있는 경우
// 다양한 정적 메서드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메소드는 없는 클래스가 생겨난다.

class Rectangle(val width: Double, val height: Double) {
    fun calculateArea(): Double {
        return width * height
    }
}

class Circle(val radius: Double) {
    fun calculateArea(): Double {
        return Math.PI * radius * radius
    }
}

// 정적 유틸리티 클래스
class GeometryUtil {
    companion object {
        fun calculateArea(shape: Any): Double {
            return when (shape) {
                is Rectangle -&gt; shape.calculateArea()
                is Circle -&gt; shape.calculateArea()
                else -&gt; throw IllegalArgumentException(&quot;Unknown shape&quot;)
            }
        }
    }
}

fun main() {
    val rectangle = Rectangle(5.0, 3.0)
    val circle = Circle(2.0)

    val area1 = GeometryUtil.calculateArea(rectangle)
    val area2 = GeometryUtil.calculateArea(circle)

    println(&quot;Area of rectangle: $area1&quot;)
    println(&quot;Area of circle: $area2&quot;)
}</code></pre>
<pre><code class="language-kotlin">// 중요한 객체는 하나뿐이지만 그 연산을 객체의 인스턴스 API에 추가해서 API를 너무 크게 만드는 경우
// 다양한 정적 메서드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메소드는 없는 클래스가 생겨난다.

class MathUtility {
    companion object {
        fun add(a: Int, b: Int): Int {
            return a + b
        }

        fun subtract(a: Int, b: Int): Int {
            return a - b
        }

        // 다른 수학 함수들도 추가 가능
    }
}

fun main() {
    val result1 = MathUtility.add(5, 3)
    val result2 = MathUtility.subtract(8, 2)

    println(&quot;Addition: $result1&quot;)      // 출력: Addition: 8
    println(&quot;Subtraction: $result2&quot;)   // 출력: Subtraction: 6
}</code></pre>
<ul>
<li>코틀린에서는 이런 무의미한 클래스가 필요없다. 대신 함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시키면 된다.</li>
<li>그런 함수들은 여전히 그 파일의 맨 앞에 정의된 패키지의 멤버 함수(클래스의 멤버로 선언되는 연산자 및 함수)이므로 다른 패키지에서 그 함수를 사용하고 싶을 때는 그 함수가 정의된 패키지를 임포트해야 한다. 하지만 임포트 시 유틸리티 클래스 이름이 추가로 들어갈 필요는 없다.<pre><code class="language-kotlin">// joinToString() 함수를 최상위 함수로 선언하기
package strings
</code></pre>
</li>
</ul>
<p>fun joinToString(...): String { ... }</p>
<pre><code>- JVM이 클래스 안에 들어있는 코드만을 실행할 수 있기 때문에 컴파일러는 이 파일을 컴파일할 때 새로운 클래스를 정의해준다.

&gt; **Q1. 새로운 클래스?** : package 내에 선언된 함수를 클래스의 정적 메서드로 만드는 별도의 유틸리티 클래스를 자동 생성해준다. (생성된 클래스명 = 해당 패키지의 이름 + &quot;Kt&quot; 접미사가 추가된 이름) 즉, strings 패키지에 속하는 클래스에서 joinToString 함수를 호출할 때 사용하는 별도의 유틸리티 클래스인 StringsKt 클래스를 생성한다.

#### 최상위 프로퍼티
- 함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다.
```kotlin
var opCount = 0
fun performOperation() {
    opCount++
    // ...
}

fun reportOperationCount() {
    println(&quot;Operation performed $opCount times&quot;)
}</code></pre><ul>
<li>이런 프로퍼티의 값은 정적 필드에 저장된다.</li>
<li>최상위 프로퍼티를 활용해 코드에 <a href="https://ko.wikipedia.org/wiki/%EC%83%81%EC%88%98">상수</a>를 추가할 수 있다.</li>
</ul>
<blockquote>
<p>✅ <strong>상수</strong> : 변하지 않고, 항상 일정한 값을 갖는 수</p>
</blockquote>
<pre><code class="language-kotlin">val UNIX_LINE_SEPARATOR = &quot;\n&quot; </code></pre>
<ul>
<li>기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메소드를 통해 자바 코드에 노출된다(val의 경우 게터, var의 경우 게터와 세터가 생긴다).</li>
<li>겉으론 상수처럼 보이는데, 실제로는 게터를 사용해야 한다면 자연스럽지 못하다.</li>
<li>더 자연스럽게 사용하려면 이 상수를 public static final 필드로 컴파일해야 한다.<ul>
<li><a href="https://velog.io/@dev-baik/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8#const">const</a> 변경자를 추가하면 프로퍼티를 public static final 필드로 컴파일하게 만들 수 있다(단, 원시 타입과 String 타입의 프로퍼티만 const로 지정할 수 있다).<pre><code class="language-kotlin">const val UNIX_LINE_SEPARATOR = &quot;\n&quot;</code></pre>
앞의 코드는 다음 자바 코드와 동등한 바이트코드를 만들어낸다.<pre><code class="language-java">public static final String UNIX_LINE_SEPARATOR = &quot;\n&quot;;</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="메소드를-다른-클래스에-추가-확장-함수와-확장-프로퍼티">메소드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티</h2>
<blockquote>
<p>💡 클래스의 멤버 함수처럼 호출되지만, 해당 클래스의 정의나 상태를 변경하지 않는 함수이다. 특별한 상태나 인스턴스 메소드가 없는 클래스가 생기지 않도록 모든 다른 클래스 밖에 선언되며, 수신 객체를 첫 번째 인자로 받는다.</p>
</blockquote>
<blockquote>
<p><strong>Q2. 확장 함수를 쓴다면?</strong> : 원본 객체를 수정하지 않고도 해당 객체에 새로운 기능을 추가할 수 있고, 관련된 함수들을 함께 묶어서 확장 함수로 정의하면 해당 객체와 관련된 기능들이 하나의 모듈로 묶여 있어 응집도가 높아진다.</p>
</blockquote>
<ul>
<li>확장함수는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다.<pre><code class="language-kotlin">package strings
</code></pre>
</li>
</ul>
<p>fun String.lastChar(): Char = this.get(this.length - 1)</p>
<p>println(&quot;Kotlin&quot;.lastChar()) // n</p>
<pre><code>- 확장 함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확장한 클래스의 이름을 덧붙이기만 하면 된다.
- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;클래스의 이름을 `수신 객체 타입(receiver type)`이라 부르며, 확장 함수가 호출되는 대상이 되는 값(객체)을 `수신 객체(receiver object)`라고 부른다.
  - 이 예제에서는 String이 수신 객체 타입이고 &quot;kotlin&quot;이 수신 객체다.
- 일반 메소드와 마찬가지로 확장 함수 본문에서도 [this](https://velog.io/@dev-baik/%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%A9%A4%EB%B2%84/this.#this)를 생략할 수 있다.
```kotlin
package strings

fun String.lastChar(): Char = get(length - 1) </code></pre><ul>
<li>확장 함수 내부에서는 일반적인 인스턴스 메소드의 내부에서와 마찬가지로 수신 객체의 메소드나 프로퍼티를 바로 사용할 수 있다. 하지만 확장 함수가 캡슐화를 깨지는 않는다.</li>
<li>클래스 안에서 정의한 메서드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 비공개(private) 멤버나 보호된(protected) 멤버를 사용할 수 없다.</li>
</ul>
<h3 id="임포트와-확장-함수">임포트와 확장 함수</h3>
<ul>
<li>확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 한다.<pre><code class="language-kotlin">import strings.lastChar
</code></pre>
</li>
</ul>
<p>val c = &quot;Kotlin&quot;.lastChar()</p>
<pre><code>```kotlin
import strings.*

val c = &quot;Kotlin&quot;.lastChar()</code></pre><ul>
<li>as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있다.<pre><code class="language-kotlin">import strings.lastChar as last
</code></pre>
</li>
</ul>
<p>val c = &quot;Kotlin&quot;.last() </p>
<pre><code>- 한 파일 안에서 다른 여러 패키지에 속해있는 이름이 같은 함수를 가져와 사용해야 하는 경우 이름을 바꿔서 임포트하면 이름 충돌을 막을 수 있다.

&gt; 물론 일반적인 클래스나 함수라면 그 전체 이름을 써도 된다. 하지만 코틀린 문법상 확장 함수는 반드시 짧은 이름을 써야 한다. 따라서 임포트할 때 이름을 바꾸는 것이 확장 함수 이름 충돌을 해결할 수 있는 유일한 방법이다.

### 자바에서 확장 함수 호출
- &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 [정적 메소드](https://velog.io/@dev-baik/%EC%A0%95%EC%A0%81-%EB%A9%A4%EB%B2%84#%EC%A0%95%EC%A0%81-%EB%A9%94%EC%86%8C%EB%93%9C)다. 그래서 확장 함수를 호출해도 다른 어댑터 객체나 실행 시점 부가 비용이 들지 않는다.

### 확장 함수로 유틸리티 함수 정의
```kotlin
fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
    separator: string = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index &gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

val list = listOf(1, 2, 3)
println(list.joinToString(separator = &quot;; &quot;, prefix = &quot;(&quot;, postfix = &quot;)&quot;)) // (1; 2; 3)</code></pre><ul>
<li>joinToString을 마치 클래스의 멤버인 것처럼 호출할 수 있다.</li>
<li>확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의일 뿐이다. 그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수도 있다.<pre><code class="language-kotlin">// 문자열 컬렉션에 대해서만 호출할 수 있는 join 함수 정의
fun Collection&lt;String&gt;.join(
  separator: string = &quot;, &quot;,
  prefix: String = &quot;&quot;,
  postfix: String = &quot;&quot;
) = joinToString(separator, prefix, postfix)
</code></pre>
</li>
</ul>
<p>println(listOf(&quot;one&quot;, &quot;two&quot;, &quot;eight&quot;).join(&quot; &quot;)) // one two eight</p>
<pre><code>
### 확장 함수는 오버라이드할 수 없다.
```kotlin
// 맴버 함수 오버라이드하기
open class View {
    open fun click() = println(&quot;View clicked&quot;)
}

class Button: View() {
    override fun click() = println(&quot;Button clicked&quot;)
}

val view: View = Button()
view.click() // Button clicked</code></pre><blockquote>
<p>확장 함수는 클래스의 일부가 아니다. 확장 함수는 클래스 밖에 선언된다.</p>
</blockquote>
<ul>
<li>이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되지, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않는다.</li>
</ul>
<pre><code class="language-kotlin">// 확장 함수는 오버라이드할 수 없다.
fun View.showOff() = println(&quot;I&#39;m a view!&quot;)

fun Button.showOff() = println(&quot;I&#39;m a button!&quot;)

val view: View = Button()
view.showOff() // I&#39;m a view!</code></pre>
<blockquote>
<p>어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출된다(멤버 함수의 우선 순위가 더 높다).</p>
</blockquote>
<h3 id="확장-프로퍼티">확장 프로퍼티</h3>
<blockquote>
<p>💡 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API(기능)을 추가할 수 있으며, 뒷받침하는 필드가 없기 때문에 최소한 게터를 꼭 정의해야 한다.</p>
</blockquote>
<ul>
<li>프로퍼티라는 이름으로 불리기는 하지만 상태를 저장할 적절한 방법이 없기 때문에(기존 클래스의 인스턴스 객체에 필드를 추가할 방법은 없다) 실제로 확장 프로퍼티는 아무 상태도 가질 수 없다.</li>
<li>하지만 프로퍼티 문법으로 더 짧게 코드를 작성할 수 있어서 편한 경우가 있다.<pre><code class="language-kotlin">// 확장 프로퍼티 선언하기
val String.lastChar: Char
get() = get(length - 1)</code></pre>
</li>
<li>확장 함수의 경우와 마찬가지로 확장 프로퍼티도 일반적인 프로퍼티와 같은데, 단지 수신 객체 클래스가 추가됐을 뿐이다.</li>
<li><span style='background-color: #fff5b1'/>뒷받침하는 필드가 없어서 기본 게터 구현을 제공할 수 없으므로 최소한 게터는 꼭 정의해야 한다. 마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없다.<pre><code class="language-kotlin">var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
    this.setCharAt(length - 1, value)
}
</code></pre>
</li>
</ul>
<p>println(&quot;Kotlin&quot;.lastChar) // n</p>
<p>val sb = StringBuilder(&quot;Kotlin?&quot;)
// sb.lastChar.set(&#39;!&#39;)
sb.lastChar = &#39;!&#39;
println(sb) // Kotlin!</p>
<pre><code>
## 컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
&gt; varage 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있다.
&gt;
&gt; 중위(infix) 함수 호출 구문을 사용하면 인자가 하나뿐인 메소드를 간편하게 호출할 수 있다.
&gt;
&gt; 구조 분해 선언을 사용하면 복합적인 값을 분해해서 여러 변수에 나눠 담을 수 있다.

### 자바 컬렉션 API 확장
```kotlin
val strings: List&lt;String&gt; = listOf(&quot;first&quot;, &quot;second&quot;, &quot;fourteenth&quot;)

strings.last() // fourteenth

val numbers: Collection&lt;Int&gt; = setOf(1, 14, 2)
numbers.max() // 14</code></pre><blockquote>
<p><strong>자바 라이브러리 클래스의 인스턴스인 컬렉션에 대해 코틀린에서는 어떻게 새로운 기능을 추가할 수 있었을까?</strong></p>
<ul>
<li>last와 max는 모두 확장 함수였던 것이다!<pre><code class="language-kotlin">// last는 List 클래스의 확장 함수다.
fun &lt;T&gt; List&lt;T&gt;.last(): T { /* 마지막 원소를 반환함 */ }
</code></pre>
</li>
</ul>
<p>fun Collection<Int>.max(): Int { /* 컬렉션의 최댓값을 찾음 */ }</p>
<pre><code></code></pre></blockquote>
<h3 id="가변-인자-함수-인자의-개수가-달라질-수-있는-함수-정의">가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의</h3>
<pre><code class="language-kotlin">var list = listOf(2, 3, 5, 7, 11)

fun listOf&lt;T&gt;(vararg values: T): List&lt;T&gt; { ... }</code></pre>
<blockquote>
<p>💡 <strong>가변 길이 인자(vararg)</strong> : 메소드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 자바 컴파일러가 배열에 그 값들을 넣어주는 기능이다.</p>
</blockquote>
<ul>
<li>코틀린에서는 이미 배열에 들어있는 원소를 가변 길이 인자로 넘길 때 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 해야 한다.</li>
<li>기술적으로는 스프레드 연산자가 그런 작업을 해준다. 하지만 실제로는 전달하려는 배열 앞에 *를 붙이기만 하면 된다.<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
  val list = listOf(&quot;args: &quot;, *args)
  println(list)
}</code></pre>
</li>
</ul>
<h3 id="값의-쌍-다루기-중위-호출과-구조-분해-선언">값의 쌍 다루기: 중위 호출과 구조 분해 선언</h3>
<pre><code class="language-kotlin">val map = mapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)</code></pre>
<ul>
<li>여기서 to라는 단어는 코틀린 키워드가 아니다. 이 코드는 <code>중위 호출(infix call)</code>이라는 특별한 방식으로 to라는 일반 메소드를 호출한 것이다.</li>
<li>중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다.
```kotlin</li>
</ul>
<p>1.to(&quot;one&quot;) //  &quot;to&quot; 메소드를 일반적인 방식으로 호출함
1 to &quot;one&quot; // &quot;to&quot; 메소드를 중위 호출 방식으로 호출함</p>
<pre><code>- 인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 함수에 중위 호출을 사용할 수 있다.
- 함수(메소드)를 중위 호출에 사용하게 허용하고 싶으면 infix 변경자를 함수(메소드) 선언 앞에 추가해야 한다.
```kotlin
infix fun Any.to(other: Any) = Pair(this, other) </code></pre><ul>
<li>이 to 함수는 Pair의 인스턴스를 반환한다. <code>Pair</code>는 코틀린 표준 라이브러리 클래스로 그 이름대로 두 원소로 이뤄진 순서쌍을 표현한다. 실제로 to는 제네릭 함수다.<pre><code class="language-kotlin">val (number, name) = 1 to &quot;one&quot; </code></pre>
</li>
<li>이런 기능을 <code>구조 분해 선언</code>이라고 부른다. Pair 인스턴스 외 다른 객체에도 구조 분해를 적용할 수 있다.</li>
<li>루프에서도 구조 분해 선언을 활용할 수 있다.<pre><code class="language-kotlin">for ((index, element) in collection.withIndex()) {
  println(&quot;$index: $element&quot;)
}</code></pre>
</li>
<li>to 함수는 확장 함수다. to를 사용하면 타입과 관계없이 임의의 순서쌍을 만들 수 있다. 이는 to의 수신 객체가 제네릭하다는 뜻이다.<pre><code class="language-kotlin">// mapOf 함수의 선언
fun &lt;K, V&gt; mapOf(vararg values: Pair&lt;K, V&gt;): Map&lt;K, V&gt; </code></pre>
</li>
</ul>
<h2 id="문자열과-정규식-다루기">문자열과 정규식 다루기</h2>
<ul>
<li>코틀린 문자열은 <a href="https://velog.io/@dev-baik/String-vs-StringBuilder-vs-StringBuffer#string-in-java">자바 문자열</a>과 같다. 특별한 변환도 필요 없고 자바 문자열을 감싸는 별도의 래퍼(wrapper)도 생기지 않는다.<h3 id="문자열-나누기">문자열 나누기</h3>
</li>
<li>자바 split 메소드의 구분 문자열은 정규식이다.<ul>
<li>일반적으로 점(.)을 사용해 문자열을 분리하면 마침표(.)는 모든 문자를 나타내는 정규식으로 해석되기 때문에 빈 배열을 반환한다.</li>
</ul>
</li>
</ul>
<ul>
<li>코틀린에서는 자바의 split 대신에 여러 가지 다른 조합의 파라미터를 받는 split 확장 함수를 제공함으로써 혼동을 야기하는 메소드를 감춘다.</li>
<li>정규식을 파라미터로 받는 함수는 String이 아닌 Regex 타입의 값을 받는다. 따라서 코틀린에서는 split 함수에 전달하는 값의 타입에 따라 정규식이나 일반 텍스트 중 어느 것으로 문자열을 분리하는 지 쉽게 알 수 있다.<pre><code class="language-kotlin">// 마침표나 대시(-)로 문자열을 분리하는 예
println(&quot;12.345-6.A&quot;.split(&quot;\\.|-&quot;.toRegex())) // [12, 345, 6, A] </code></pre>
</li>
<li>코틀린에서는 toRegex 확장 함수를 사용해 문자열을 정규식으로 변환할 수 있다.</li>
<li>split 확장 함수를 오버로딩한 버전 중에는 구분 문자열을 하나 이상 인자로 받는 함수가 있다.<pre><code class="language-kotlin">println(&quot;12.345-6.A&quot;.split(&quot;.&quot;, &quot;-&quot;)) // [12, 345, 6, A]</code></pre>
</li>
<li>이렇게 여러 문자를 받을 수 있는 코틀린 확장 함수는 자바에 있는 단 하나의 문자만 받을 수 있는 메소드를 대신한다.</li>
</ul>
<h3 id="정규식과-3중-따옴표로-묶은-문자열">정규식과 3중 따옴표로 묶은 문자열</h3>
<pre><code class="language-kotlin">// String 확장 함수를 사용해 경로 파싱하기
fun parsePath(path: String) {
    val directory = path.substringBeforeLast(&quot;/&quot;)
    val fullName = path.substringAfterLast(&quot;/&quot;)
    val fileName = fullName.substringBeforeLast(&quot;.&quot;)
    val extension = fullName.substringAfterLast(&quot;.&quot;)

    println(&quot;Dir: $directory, name: $fileName, ext: $extension&quot;)
}

parsePath(&quot;/Users/yole/kotlin-book/chapter.adoc&quot;)
// 디렉터리 경로 = Dir, 파일 이름 = name, 파일 확장자 = ext
// 결과: Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc</code></pre>
<ul>
<li><p>코틀린에서는 정규식을 사용하지 않고도 문자열을 쉽게 파싱할 수 있다. 정규식은 강력하기는 하지만 나중에 알아보기 힘든 경우가 많다.</p>
</li>
<li><p>정규식이 필요할 때는 코틀린 라이브러리를 사용하면 더 편하다.</p>
<pre><code class="language-kotlin">// 경로 파싱에 정규식 사용하기
fun parsePath(path: String) {
  val regex = &quot;&quot;&quot;(.+)/(.+)\.(.+)&quot;&quot;&quot;.toRegex()
  val matchResult = regex.matchEntire(path)

  if (matchResult != null) {
      // destructured 프로퍼티 : 그룹별로 분해한 매치 결과
      val (directory, filename, extension) = matchResult.destructured
      println(&quot;Dir: $directory, name: $filename, ext: $extension&quot;)
  }
}</code></pre>
</li>
<li><p>이 예제에서는 3중 따옴표 문자열을 사용해 정규식을 썻다. 3중 따옴표 문자열에서는 역슬래시(\)를 포함한 어떤 문자도 이스케이프할 필요가 없다.</p>
</li>
</ul>
<h3 id="여러-줄-3중-따옴표-문자열">여러 줄 3중 따옴표 문자열</h3>
<ul>
<li>3중 따옴표를 쓰면 줄 바꿈이 들어있는 프로그램 텍스트를 쉽게 문자열로 만들 수 있다.<pre><code class="language-kotlin">val kotlinLogo = &quot;&quot;&quot;|  //
                 .|  //
                 .|/ \&quot;&quot;&quot;
</code></pre>
</li>
</ul>
<p>// trimMargin : 해당 문자열과 그 직전의 공백을 제거
println(kotlinLogo.trimMargin(&quot;.&quot;))</p>
<p>//// 결과
// |  //
// |  //
// |/ \</p>
<pre><code>- 3중 따옴표 문자열 안에 문자열 템플릿을 사용할 수도 있다. 그러나 3중 따옴표 문자열 안에서는 이스케이프를 사용할 수 없다. 따라서 $ 를 넣어야 한다면 문자열 템플릿 안에 $ 문자를 넣어야 한다.
```kotlin
val price = &quot;&quot;&quot;${&#39;$&#39;}99.9&quot;&quot;&quot;</code></pre><h2 id="코드-다듬기-로컬-함수와-확장">코드 다듬기: 로컬 함수와 확장</h2>
<blockquote>
<p>반복하지 말라(DRY, Don&#39;t Repeat Yourself)</p>
</blockquote>
<ul>
<li>자바 코드를 작성할 때는 DRY 원칙을 피하기는 쉽지 않다. 많은 경우 <a href="https://aeunhi99.tistory.com/205">메소드 추출 리팩토링</a>을 적용해서 긴 메소드를 부분부분 나눠서 각 부분을 재활용할 수 있다. 하지만, 그렇게 코드를 리팩토링하면 클래스 안에 작은 메소드가 많아지고 각 메소드 사이의 관계를 파악하기 힘들어서 코드를 이해하기 더 어려워질 수도 있다.</li>
<li>리팩토링을 진행해서 추출한 메소드를 별도의 <a href="https://velog.io/@dev-baik/Inner-Nested-Class">내부 클래스(inner class)</a> 안에 넣으면 코드를 깔끔하게 조직할 수는 있지만, 그에 따른 불필요한 준비 코드가 늘어난다.</li>
</ul>
<blockquote>
<p>✅ <strong>메소드 추출 리팩토링</strong> : 한 메서드에 세세한 처리가 많을 때 그런 처리를 묶어서 나누고 독립된 메서드로 추출하는 것</p>
</blockquote>
<ul>
<li>코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수도 있다. 그렇게 하면 문법적인 부가 비용을 들이지 않고도 깔끔하게 코드를 조직할 수 있다.<pre><code class="language-kotlin">class User(val id: Int, val name: String, val address: String)
</code></pre>
</li>
</ul>
<p>fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            &quot;Can&#39;t save user ${user.id}: empty Name&quot;)
    }</p>
<pre><code>if (user.address.isEmpty()) {
    throw IllegalArgumentException(
        &quot;Can&#39;t save user ${user.id}: empty Address&quot;)
}

// user를 데이터베이스에 저장한다.</code></pre><p>}</p>
<p>saveUser(User(1, &quot;&quot;, &quot;&quot;)) 
// 결과: java.lang.IllegalArgumentException: Can&#39;t save user 1: empty Name</p>
<pre><code>- 클래스가 사용자의 필드를 검증할 때 필요한 여러 경우를 하나씩 처리하는 메소드로 넘쳐나기를 바라지는 않을 것이다. 이런 경우 검증 코드를 로컬 함수로 분리하면 중복을 없애는 동시에 코드 구조를 깔끔하게 유지할 수 있다.
```kotlin
// 로컬 함수를 사용해 코드 중복 줄이기
class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    fun validate(user: User, value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                &quot;Can&#39;t save user ${user.id}: empty $fieldName&quot;)
        }
    }

    // 로컬 함수를 호출해서 각 필드를 검증한다.
    validate(user, user.name, &quot;Name&quot;)
    validate(user, user.address, &quot;Address&quot;)

    // user를 데이터베이스에 저장한다.
}</code></pre><ul>
<li>검증 로직 중복은 사라졌고, 필요하면 User의 다른 필드에 대한 검증도 쉽게 추가할 수 있다. 하지만 User 객체를 로컬 함수에게 하나하나 전달해야 한다는 점은 아쉽다.</li>
<li>하지만 로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.<pre><code class="language-kotlin">// 로컬 함수에서 바깥 함수의 파라미터 접근하기
class User(val id: Int, val name: String, val address: String)
</code></pre>
</li>
</ul>
<p>fun saveUser(user: User) {
    // 이제 saveUser 함수의 user 파라미터를 중복 사용하지 않는다.
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
          throw IllegalArgumentException(
            // 바깥 함수의 하라미터에 직접 접근할 수 있다.
            &quot;Can&#39;t save user ${user.id}: empty $fieldName&quot;) 
        }
    }</p>
<pre><code>validate(user.name, &quot;Name&quot;)
validate(user.address, &quot;Address&quot;)

// user를 데이터베이스에 저장한다.</code></pre><p>}</p>
<pre><code>- 검증 로직을 User 클래스를 확장한 함수로 만들어 더 개선할 수 있다.
```kotlin
class User(val id: Int, val name: String, val address: String)

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
          throw IllegalArgumentException(
            // User의 프로퍼티를 직접 사용할 수 있다.
            &quot;Can&#39;t save user $id: empty $fieldName&quot;)
        }
    }

    validate(name, &quot;Name&quot;)
    validate(address, &quot;Address&quot;)
}

fun saveUser(user: User) {
    user.validateBeforeSave() // 확장 함수를 호출한다.

    // user를 데이터베이스 저장한다.
}</code></pre><blockquote>
<p>이 경우 검증 로직은 User를 사용하는 다른 곳에서는 쓰이지 않는 기능이기 때문에 User에 포함시키고 싶지는 않다. User를 간결하게 유지하면 생각해야 할 내용이 줄어들어서 더 쉽게 코드를 파악할 수 있다.</p>
</blockquote>
<blockquote>
<p>반면 한 객체만을 다루면서 객체의 비공개 데이터를 다룰 필요 없는 함수는 이와 같이 확장 함수로 만들면 <code>객체.멤버</code>처럼 수신 객체를 지정하지 않고도 공개된 멤버 프로퍼티나 메소드에 접근할 수 있다.</p>
</blockquote>
<ul>
<li>확장 함수를 로컬 함수로 정의할 수도 있다. 즉 User.validateBeforeSave를 saveUser 내부에 로컬 함수로 넣을 수 있다. 하지만 중첩된 함수의 깊이가 깊어지면 코드를 읽기가 상당히 어려워진다. 따라서 일반적으로는 한 단계만 함수를 중첩시키라고 권장한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 2장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-2%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-2%EC%9E%A5</guid>
            <pubDate>Mon, 23 Oct 2023 11:31:04 GMT</pubDate>
            <description><![CDATA[<h1 id="코틀린-기초">코틀린 기초</h1>
<blockquote>
<ul>
<li>함수, 변수, 클래스, enum, 프로퍼티를 선언하는 방법</li>
<li>제어 구조</li>
<li>스마트 캐스트</li>
<li>예외 던지기와 예외 잡기</li>
</ul>
</blockquote>
<hr>
<h2 id="기본-요소-함수와-변수">기본 요소: 함수와 변수</h2>
<h3 id="hello-world">Hello, World!</h3>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    println(&quot;Hello, world!&quot;)
}</code></pre>
<ul>
<li>함수를 선언할 때 fun 키워드를 사용한다. <ul>
<li><span style='background-color: #dcffe4'/><strong>실제로도 코틀린 프로그래밍은 수많은 fun을 만드는 재미있는 일이다!</strong></li>
</ul>
</li>
<li>파라미터 이름 뒤에 그 파라미터의 타입을 쓴다.</li>
<li>함수를 최상위 수준에 정의할 수 있다. (자바와 달리) 꼭 클래스 안에 함수를 넣어야 할 필요가 없다.</li>
<li>자바와 달리 배열 처리를 위한 문법이 따로 존재하지 않는다.</li>
<li>코틀린 표준 라이브러리는 여러 가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 감싼 래퍼를 제공한다.<ul>
<li>System.out.println ➡️ println</li>
</ul>
</li>
<li>최신 프로그래밍 언어 경향과 마찬가지로 줄 끝에 세미클론(;)을 붙이지 않아도 좋다.</li>
</ul>
<h3 id="함수">함수</h3>
<ul>
<li>함수 이름, 파라미터 목록, 반환 타입, 함수 본문</li>
</ul>
<ul>
<li>블록이 본문인 함수 : 본문이 중괄호로 둘러싸인 함수<pre><code class="language-kotlin">fun max(a: Int, b: Int): Int {
  return if (a &gt; b) a else b
}
</code></pre>
</li>
</ul>
<p>println(max(1, 2)) // 2</p>
<pre><code>
#### 문(statement)과 식(expression)의 구분
&gt; **코틀린에서 if는 식이지 문이 아니다.**
&gt; 
&gt; - &lt;span style=&#39;background-color: #fff5b1&#39;/&gt;식은 값을 만들어 내며 다른 식의 하위 요소로 계산해 참여할 수 있는 반면 문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않는다는 차이가 있다.
&gt; - 자바에서는 모든 제어 구조가 문인 반면 코틀린에서는 루프를 제외한 대부분의 제어 구조가 식이다.
&gt; - 제어 구조를 다른 식으로 엮어낼 수 있으면 여러 일반적인 패턴을 아주 간결하게 표현할 수 있다.
&gt; - 반면 대입문은 자바에서는 식이었으나 코틀린에서는 문이 됐다. 그로인해 자바와 달리 대입식과 비교식을 잘못 바꿔 써서 버그가 생기는 경우가 없다.

&gt; **Q1. 대입문이 자바에서 식일 때와 코틀린에서 문일 때 차이점** : 자바에서는 대입문이 식이었기 때문에 삼항 연산자 등과 함께 사용할 수 있다. 반면에 코틀린에서는 대입문이 문이 되었기 때문에 삼항 연산자 등의 식과는 사용이 제한되고, if문을 통한 대입문을 사용할 수 있다.

#### 식이 본문인 함수
- 식이 본문인 함수 : 등호와 식으로 이뤄진 함수
```kotlin
fun max(a: Int, b: Int): Int = if (a &gt; b) a else b</code></pre><blockquote>
<p><strong>반환 타입을 생략할 수 있는 이유는 무엇일까?</strong></p>
<p><strong>코틀린은 정적 타입 지정 언어이므로 컴파일 시점에 모든 식의 타입을 지정해야 하지 않는가?</strong></p>
<ul>
<li>실제로 모든 변수나 모든 식에는 타입이 있으며, 모든 함수는 반환 타입이 정해져야 한다.</li>
<li>하지만 식이 본문인 함수의 경우<ul>
<li>사용자가 반환 타입을 적지 않아도 컴파일러가 함수 본문 식을 분석해서 식의 결과 타입을 함수 반환 타입으로 정해준다.<ul>
<li><strong>타입 추론</strong> : 컴파일러가 타입을 분석해 프로그래머 대신 프로그램 구성 요소의 타입을 정해주는 기능</li>
</ul>
</li>
</ul>
</li>
</ul>
</blockquote>
<pre><code class="language-kotlin">fun max(a: Int, b: Int) = if (a &gt; b) a else b</code></pre>
<h3 id="변수"><a href="https://velog.io/@dev-baik/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8">변수</a></h3>
<ul>
<li>자바에서는 변수를 선언할 때 타입이 맨 앞에 온다.<ul>
<li>타입으로 변수 선언을 시작하면 타입을 생략할 경우 식과 변수 선언을 구별할 수 없다.</li>
</ul>
</li>
<li>코틀린에서는 타입 지정을 생략하는 경우가 흔하다.<ul>
<li>키워드로 변수 선언을 시작하는 대신 변수 이름 뒤에 타입을 명시하거나 생략하게 허용한다.<pre><code class="language-kotlin">val question = &quot;삶, 우주, 그리고 모든 것에 대한 궁극적인 질문&quot;
</code></pre>
</li>
</ul>
</li>
</ul>
<p>val answer = 42</p>
<p>val answer: Int = 42</p>
<p>val yearsToCompute = 7.5e5 // 7500000.0</p>
<p>val answer: Int
answer = 42</p>
<pre><code>- 식이 본문인 함수에서와 마찬가지로 여러분이 타입을 지정하지 않으면 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정한다.
- 초기화 식이 없다면 변수에 저장될 값에 대해 아무 정보가 없기 때문에 컴파일러가 타입을 추론할 수 없다.
  - 따라서 그런 경우 타입을 반드시 지정해야 한다.

#### 변경 가능한 변수와 변경 불가능한 변수
- val(value) : 변경 불가능한 참조를 저장하는 변수다. val로 선언된 변수는 일단 초기화하고 나면 재대입이 불가능하다. 자바로 말하자면 [final 변수](https://velog.io/@dev-baik/final-%ED%82%A4%EC%9B%8C%EB%93%9C)에 해당한다.
  - val 변수는 블록을 실행할 때 정확히 한 번만 초기화돼야 한다.
    - 하지만, 어떤 블록이 실행될 때 오직 한 초기화 문장만 실행됨을 컴파일러가 확인할 수 있다면 조건에 따라 val 값을 다른 여러 값으로 초기화할 수도 있다.
    ```kotlin
    val message: String
    if (canPerfromOperation()) {
        mesage = &quot;Success&quot;
    } else {
        message = &quot;Failed&quot;
    }
    ```
  - val 참조 자체는 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.
  ```kotlin
  val languages = arrayListOf(&quot;Java&quot;)
  languages.add(&quot;Kotlin&quot;)</code></pre><ul>
<li>var(variable) : 변경 가능한 참조다. 이런 변수의 값은 바뀔 수 있다. 자바의 일반 변수에 해당한다.</li>
</ul>
<blockquote>
<p>기본적으로는 모든 변수를 val 키워드를 사용해 불변 변수로 선언하고, 나중에 꼭 필요할 때에만 var로 변경하라.</p>
</blockquote>
<ul>
<li>변경 불가능한 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합해 사용하면 함수형 코드에 가까워진다.</li>
<li>변수의 값을 변경할 수 있지만 변수의 타입은 고정돼 바뀌지 않는다.<pre><code class="language-kotlin">var answer = 42
answer = &quot;no answer&quot; // Error: type mismatch</code></pre>
</li>
</ul>
<h3 id="더-쉽게-문자열-형식-지정-문자열-템플릿">더 쉽게 문자열 형식 지정: 문자열 템플릿</h3>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    val name = if (args.size &gt; 0) args[0] else &quot;Kotlin&quot;
    // println(&quot;Hello, &quot; + name + &quot;!&quot;)
    println(&quot;Hello, $name&quot;)

    // $ 문자를 문자열에 넣고 싶으면 $를 이스케이프 시켜야한다.
    println(&quot;\$x&quot;) // $x

    // 문자열 템플릿 안에 사용할 수 있는 대상은 간단한 변수 이름만으로 한정되지 않는다.
    if (args.size &gt; 0) {
        println(&quot;Hello, ${args[0]}!&quot;)
    }

    // 중괄호로 둘러싼 식 안에서 큰 따옴표를 사용할 수도 있다.
    println(&quot;Hello, ${if (args.size &gt; 0) args[0] else &quot;someone&quot;}!&quot;)
}</code></pre>
<ul>
<li>컴파일러는 각 식을 정적으로 (컴파일 시점에) 검사하기 때문에 존재하지 않는 변수를 문자열 템플릿 안에서 사용하면 컴파일 오류가 발생한다.</li>
</ul>
<h2 id="클래스와-프로퍼티">클래스와 프로퍼티</h2>
<ul>
<li><p>필드가 둘 이상으로 늘어나면 생성자인 Person(String name)의 본문에서 파라미터를 이름이 같은 필드에 대입하는 대입문의 수도 늘어난다.</p>
</li>
<li><p>자바에서는 생성자 본문에 이 같은 코드가 반복적으로 들어가는 경우가 많다.</p>
</li>
<li><p>자바에서는 데이터를 필드에 저장하며, 멤버 필드의 가시성은 보통 비공개(private)이다.</p>
<pre><code class="language-java">public class Person {
  // 멤버 변수(멤버 필드)를 비공개로 설정
  private String name;
  private int age;

  // 생성자를 사용하여 객체 초기화
  public Person(String name, int age) {
      this.name = name;
      this.age = age;
  }

  // 게터 메서드를 사용하여 데이터에 접근
  public String getName() {
      return name;
  }

  // 세터 메서드를 사용하여 데이터 수정
  public void setName(String name) {
      this.name = name;
  }

  public int getAge() {
      return age;
  }

  public void setAge(int age) {
      this.age = age;
  }
}</code></pre>
</li>
<li><p>코틀린에서는 그런 필드 대입 로직을 훨씬 더 적은 코드로 작성할 수 있다.</p>
<pre><code class="language-kotlin">// 코틀린으로 변환한 Person 클래스
class Person(var name: String, var age: Int)</code></pre>
</li>
<li><p>값 객체 : 코드가 없이 데이터만 저장하는 클래스</p>
</li>
<li><p>코틀린의 기본 가시성은 public 이므로 이런 경우 변경자를 생략해도 된다.</p>
</li>
</ul>
<h3 id="프로퍼티">프로퍼티</h3>
<blockquote>
<p><strong>클래스 목적</strong> : 데이터를 캡슐화하고 캡슐화한 데이터를 다루는 코드를 한 주체 아래 가두는 것이다.</p>
</blockquote>
<ul>
<li>클래스는 자신을 사용하는 클라이언트가 그 데이터에 접근하는 통로로 쓸 수 있는 접근자 메서드를 제공한다.<ul>
<li>보통은 필드를 읽기 위한 게터를 제공하고 필드를 변경하게 허용해야 할 경우 세터를 추가로 제공할 수 있다.</li>
</ul>
</li>
<li>자바에서는 필드와 접근자를 한데 묶어 <code>프로퍼티</code>라고 부르며, 프로퍼티라는 개념을 활용하는 프레임워크가 많다.</li>
<li>코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 코틀린 프로퍼티는 자바의 필드와 접근자 메서드를 완전히 대신한다.<ul>
<li>val로 선언한 프로퍼티는 읽기 전용이며, var로 선언한 프로퍼티는 변경 가능하다.<pre><code class="language-kotlin">// 클래스 안에서 변경 가능한 프로퍼티 선언하기
class Person(
// 읽기 전용 프로퍼티로, 코틀린은 (비공개) 필드와 필드를 읽는 단순한 (공개) 게터를 만들어낸다.
val name: String,
// 쓸 수 있는 프로퍼티로, 코틀린은 (비공개) 필드, (공개) 세터, (공개) 세터를 만들어낸다.
var isMarried: Boolean
) </code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>코틀린은 값을 저장하기 위한 비공개 필드와 그 필드에 값을 저장하기 위한 세터, 필드의 값을 읽기 위한 게터로 이뤄진 간단한 디폴트 접근자 구현을 제공한다.</p>
</blockquote>
<pre><code class="language-java">// 자바에서 Person 클래스를 사용하는 방법
Person person = new Person(&quot;Bob&quot;, true);
System.out.println(person.getName()); // Bob
System.out.println(person.isMarried()); // true</code></pre>
<ul>
<li>게터와 세터의 이름을 정하는 규칙에는 예외가 있다.<ul>
<li><span style='background-color: #F7DDBE'/>이름이 is로 시작하는 프로퍼티의 게터에는 get이 붙지 않고 원래 이름을 그대로 사용하며, 세터에는 is를 set으로 바꾼 이름을 사용한다.<ul>
<li>따라서 자바에서 isMarried 프로퍼티의 게터를 호출하려면 isMarried()를 사용해야 한다.<pre><code class="language-kotlin">// 코틀린에서 Person 클래스 사용하기
val person = Person(&quot;Bob&quot;, true) // new 키워드를 사용하지 않고 생성자 호출한다.
// 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 게터를 호출해준다.
println(person.name) // Bob
println(person.isMarried) // true</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="커스텀-접근자--프로퍼티의-접근자를-직접-작성하는-방법">커스텀 접근자 : 프로퍼티의 접근자를 직접 작성하는 방법</h3>
<ul>
<li><span style='background-color: #fff5b1'/>대부분의 프로퍼티에는 그 프로퍼티의 값을 저장하기 위한 필드가 있다. 이를 프로퍼티를 <code>뒷받침하는 필드</code>라고 부른다. </li>
<li>하지만 원한다면 프로퍼티 값을 그때그때 계산(예를 들어 다른 프로퍼티들로부터 값을 계산할 수도 있다)할 수 도 있다. 커스텀 게터를 작성하면 그런 프로퍼티를 만들 수 있다.<pre><code class="language-kotlin">class Rectangle(val height: Int, val width: Int) {
  // 직사각형이 정사각형인지를 별도의 필드에 저장할 필요가 없다.
  val isSquare: Boolean
    // get() {
    //    return height == width
    // }
    get() = height == width
}
</code></pre>
</li>
</ul>
<p>val rectangle = Rectangle(41, 43)
println(rectangle.isSquare)</p>
<pre><code>&gt; 파라미터가 없는 함수를 정의하는 방식과 커스텀 게터를 정의하는 방식은 구현이나 성능상 차이는 없다. 차이가 나는 부분은 가독성뿐이다. 일반적으로 클래스의 특성을 정의하고 싶다면 프로퍼티로 그 특성을 정의해야 한다.

### 코틀린 소스코드 구조: 디렉터리와 패키지
- 자바의 경우 모든 클래스를 패키지 단위로 관리한다. 코틀린에서도 자바와 비슷한 개념의 패키지가 있다.
- 모든 코틀린 파일의 맨 앞에 package 문을 넣을 수 있다. 그러면 그 파일 안에 있는 모든 선언(클래스, 함수, 프로퍼티 등)이 해당 패키지에 들어간다.
- 같은 패키지에 속해있다면 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있다.
- 반면 다른 패키지에서 정의한 선언을 사용하려면 임포트를 통해 선언을 불러와야 한다.
  - 자바와 마찬가지로 임포트문은 파일의 맨 앞에 와야 하며 import 키워드를 사용한다.
  ```kotlin
  // 클래스와 함수 선언을 패키지에 넣기
  package geometry.shape // 패키지 선언

  import java.util.Random // 표준 자바 라이브러리 클래스를 임포트한다.

  class Rectangle(val height: Int, val width: Int) {
      val isSquare: Boolean
        get() = height == width
  }

  fun createRandomRectangle(): Rectangle {
      val random = Random()
      return Rectangle(random.nextInt(), random.nextInt())
  }</code></pre><ul>
<li><p>코틀린에서는 클래스 임포트와 함수 임포트에 차이가 없으며, 모든 선언을 import 키워드로 가져올 수 있다. 최상위 함수는 그 이름을 써서 임포트할 수 있다.</p>
<pre><code class="language-kotlin">// 다른 패키지에 있는 함수 임포트하기
package geometry.example

import geometry.shapes.createRandomRectangle // 이름으로 함수 임포트하기

fun main(args: Array&lt;String&gt;) {
  println(createRandomRectangle().isSquare)
} </code></pre>
</li>
<li><p>패키지 이름 뒤에 .*를 추가하면 패키지 안의 모든 선언을 임포트할 수 있다.</p>
</li>
<li><p>이런 스타 임포트를 사용하면 패키지 안에 있는 모든 클래스뿐 아니라 최상위에 정의된 함수나 프로퍼티까지 모두 불러온다는 점에 유의하라</p>
<ul>
<li>자바에서는 패키지의 구조와 일치하는 디렉터리 계층 구조를 만들고 클래스의 소스코드를 그 클래스가 속한 패키지와 같은 디렉터리에 위치시켜야 한다.</li>
<li>코틀린에서는 원하는 대로 소스코드를 구성할 수 있다.</li>
</ul>
</li>
<li><p>여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있다.</p>
</li>
<li><p>디스크상의 어느 디렉터리에 소스코드 파일을 위치시키든 관계없다.</p>
</li>
</ul>
<blockquote>
<p>하지만 대부분의 경우 자바와 같이 패키지별로 디렉터리를 구성하는 편이 낫다. 특히 자바와 코틀린을 함께 사용하는 프로젝트에서는 자바의 방식을 따르는 게 중요하다. 자바의 방식을 따르지 않으면 자바 클래스를 코틀린 클래스로 마이그레이션할 때 문제가 생길 수 있다.</p>
</blockquote>
<blockquote>
<p>하지만 여러 클래스를 한 파일에 넣는 것을 주저해서는 안 된다. 특히 각 클래스를 정의하는 소스코드 크기가 아주 작은 경우 더욱 그렇다(코틀린에서는 클래스 소스코드 크기가 작은 경우가 자주 있다).</p>
</blockquote>
<h2 id="선택-표현과-처리-enum과-when">선택 표현과 처리: enum과 when</h2>
<h3 id="enum-클래스-정의">enum 클래스 정의</h3>
<blockquote>
<p>💡 서로 연관된 상수의 집합을 정의하기 위한 클래스로, 상수 값의 타입 안전성이 보장되며 특정 상수값에 대한 동작을 빼먹는 것을 방지할 수 있다. </p>
</blockquote>
<pre><code class="language-kotlin">enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}</code></pre>
<ul>
<li><p>코틀린에서 enum은 소프트 키워드라 부르는 존재다. class 앞에 있을 때는 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있다.</p>
</li>
<li><p>반면 class는 키워드다. 따라서 class라는 이름을 사용할 수 없으므로 클래스를 표현하는 변수 등을 정의할 때는 clazz나 aClass와 같은 이름을 사용해야 한다.</p>
</li>
<li><p>자바와 마찬가지로 enum은 단순히 값만 열거하는 존재가 아니다. enum 클래스 안에도 프로퍼티나 메서드를 정의할 수 있다.</p>
<pre><code class="language-kotlin">// 프로퍼티와 메소드가 있는 enum 클래스 선언하기
enum class Color(
  val r: Int, val g: Int, val b: Int // 상수의 프로퍼티를 정의한다.
) {
  RED(255, 0, 0), ORANGE(255, 165, 0), // 각 상수를 생성할 때, 그에 대한 프로퍼티 값을 지정한다.
  YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
  INDIGO(75, 0, 130), VIOLET(238, 130, 238); // 여기 반드시 세미콜론을 사용해야 한다.

  fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 안에서 메소드를 정의한다.
}
</code></pre>
</li>
</ul>
<p>println(Color.BLUE.rgb()) // 255</p>
<pre><code>- enum 클래스 안에 메소드를 정의하는 경우 반드시 enum 상수 목록과 메소드 정의 사이에 세미콜론을 넣어야 한다.

### when으로 enum 클래스 다루기
```kotlin
// when을 사용해 올바른 enum 값 찾기
fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -&gt; &quot;Richard&quot;
        Color.ORANGE -&gt; &quot;Of&quot;
        Color.YELLOW -&gt; &quot;York&quot;
        Color.GREEN -&gt; &quot;GAVE&quot;
        Color.BLUE -&gt; &quot;Battle&quot;
        Color.INDIGO -&gt; &quot;In&quot;
        Color.VIOLET -&gt; &quot;Vain&quot;
    }

println(getMnemonic(Color.BLUE)) // Battle</code></pre><ul>
<li>자바의 switch에 해당하는 코틀린 구성 요소는 when이다.</li>
<li>color로 전달된 값과 같은 분기를 찾는다. 자바와 달리 각 분기의 끝에 break을 넣지 않아도 된다.<pre><code class="language-kotlin">// 한 when 분기 안에 여러 값 사용하기
fun getWarmth(color: Color) = when(color) {
  Color.RED, Color.ORANGE, Color.YELLOW -&gt; &quot;warm&quot;
  Color.GREEN -&gt; &quot;neutral&quot;
  Color.BLUE, Color.INDIGO, Color.VIOLET -&gt; &quot;cold&quot;
}
</code></pre>
</li>
</ul>
<p>println(getWarmth(Color.ORANGE)) // warm</p>
<pre><code>```kotlin
// enum 상수 값을 임포트해서 enum 클래스 수식자 없이 enum 사용하기
import ch02.colors.Color // 다른 패키지에서 정의한 Color 클래스를 임포트한다.
import ch02.colors.Color.* // 짧은 이름으로 사용하기 위해 enum 상수를 모두 임포트한다.

fun getWarmth(color: Color) = when(color) {
    // 임포트한 enum 상수를 이름만으로 사용한다.
    RED, ORANGE, YELLOW -&gt; &quot;warm&quot;
    GREEN -&gt; &quot;neutral&quot;
    BLUE, INDIGO, VIOLET -&gt; &quot;cold&quot;
}</code></pre><h3 id="when과-임의의-객체를-함께-사용">when과 임의의 객체를 함께 사용</h3>
<ul>
<li>코틀린에서 when은 자바의 switch보다 훨씬 강력하다. 분기 조건에 상수(enum 상수나 숫자 리터럴)만을 사용할 수 있는 자바 switch와 달리 코틀린 when의 분기 조건은 임의의 객체를 허용한다.<pre><code class="language-kotlin">// when의 분기 조건에 여러 다른 객체 사용하기
fun mix(c1: Color, c2: Color) =
  when (setOf(c1, c2)) {
      setOf(RED, YELLOW) -&gt; ORANGE
      setOf(YELLOW, BLUE) -&gt; GREEN
      setOf(BLUE, VIOLET) -&gt; INDIGO
      else -&gt; throw Exception(&quot;Dirty color&quot;)
  }
</code></pre>
</li>
</ul>
<p>println(mix(BLUE, YELLOW)) // GREEN</p>
<pre><code>&gt; 위 함수는 호출될 때마다 함수 인자로 주어진 두 색이 when의 분기 조건에 있는 다른 두 색과 같은지 비교하기 위해 여러 Set 인스턴스를 생성한다. 보통은 이런 비효율성이 크게 문제가 되지 않는다.

&gt; 하지만 이 함수가 아주 자주 호출된다면 불필요한 가비지 객체가 늘어나는 것을 방지하기 위해 함수를 고쳐쓰는 편이 낫다.

### 인자 없는 when 사용
- (전달)인자 : 함수에서 받는 값중 실제로 값을 가지고 오는 input의 값.
- 매개변수 : 함수 정의에서 함수 안에서 사용되는 변수.
- 코드는 약간 읽기 어려워지지만 성능을 더 향상시키기 위해 그 정도 비용을 감수해야 하는 경우도 자주 있다.
```kotlin
// 인자가 없는 when
fun mixOptimized(c1: Color, c2: Color) =
    when {
      (c1 == RED &amp;&amp; c2 == YELLOW) ||
              (c1 == YELLOW &amp;&amp; c2 == RED) -&gt; ORANGE
      (c1 == YELLOW &amp;&amp; c2 == BLUE) ||
              (c1 == BLUE &amp;&amp; c2 == YELLOW) -&gt; GREEN
      (c1 == BLUE &amp;&amp; c2 == VIOLET) ||
              (c1 == VIOLET &amp;&amp; c2 == BLUE) -&gt; INDIGO
      else -&gt; throw Exception(&quot;Dirty color&quot;)
    }

println(mixOptimized(BLUE, YELLOW)) // GREEN</code></pre><blockquote>
<p>추가 객체를 만들지 않는다는 장점이 있지만 가독성은 더 떨어진다.</p>
</blockquote>
<h3 id="스마트-캐스트-타입-검사와-타입-캐스트를-조합">스마트 캐스트: 타입 검사와 타입 캐스트를 조합</h3>
<blockquote>
<p>💡 is 연산자로 변수의 타입을 검사한 다음, 그 값이 해당 범위 내에서 더 이상 바뀌지 않을 것으로 컴파일러가 추론되는 경우, 자동으로 타입 캐스트를 수행하는 것</p>
</blockquote>
<pre><code class="language-kotlin">// 식을 표현하는 클래스 계층 : 우선 식을 인코딩하는 방법을 생각해야 한다. 식을 트리 구조로 저장하자.
// 아무 메서드도 선언하지 않으며, 단지 여러 타입의 식 객체를 아우르는 공통 타입 역할만 수행한다.
interface Expr
// value라는 프로퍼티만 존재하는 단순한 클래스로 Expr 인터페이스를 구현한다.
class Num(val value: Int) : Expr
// Expr 타입의 객체라면 어떤 것이나 Sum 연산의 인자가 될 수 있다. 따라서 Num이나 다른 Sum이 인자로 올 수 있다.
class Sum(val left: Expr, val right: Expr) : Expr

println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) // 7</code></pre>
<ul>
<li>Expr 인터페이스에는 두 가지 구현 클래스가 존재한다. 따라서 식을 평가하려면 두 가지 경우를 고려해야 한다.<ul>
<li>어떤 식이 수라면 그 값을 반환한다.</li>
<li>어떤 식이 합계라면 좌항과 우항의 값을 계산한 다음에 그 두 값을 합한 값을 반환한다.<pre><code class="language-kotlin">// if 연쇄를 사용해 식을 계산하기 (자바 스타일)
fun eval(e: Expr): Int {
if (e is Num) {
    val n = e as Num // 여기서 Num으로 타입을 변환하는 데 이는 불필요한 중복이다.
    return n.value
}
if (e is Sum) {
    return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException(&quot;Unknown expression&quot;)
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) // 7</p>
<pre><code>- 코틀린에서는 is를 사용해 변수 타입을 검사한다. is 검사는 자바의 instanceof와 비슷한다.
- 하지만 자바에서 어떤 변수의 타입을 instanceof로 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 한다.
  - 이런 멤버 접근을 여러 번 수행해야 한다면 변수에 따로 캐스팅한 결과를 저장한 후 사용해야 한다.
- 코틀린에서는 프로그래머 대신 컴파일러가 캐스팅을 수행해준다. 이를 `스마트 캐스트`라고 부른다.
- 스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.
  - 예를들어 앞에서 본 예제처럼 클래스의 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val이어야 하며 커스텀 접근자를 사용한 것이어도 안된다.
    - val이 아니거나 val이지만 커스텀 접근자를 사용하는 경우에는 해당 프로퍼티에 대한 접근이 항상 같은 값을 내놓는다고 확신할 수 없기 때문이다.
      - 원하는 타입으로 명시적으로 타빙 캐스팅하려면 as 키워드를 사용한다.

&gt; ✅ **[IllegalArumentException](https://help.acmicpc.net/judge/rte/IllegalArgumentException)** : 적합하지 않거나(illegal) 적절하지 못한(inappropriate) 인자를 메소드에 넘겨주었을 때 발생한다.

### 리팩토링: if를 when으로 변경
- 코틀린에서는 if가 값을 만들어내기 때문에 자바와 달리 3항 연산자가 따로 없다.
```kotlin
// 값을 만들어내는 if 식
fun evel(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.right) + evel(e.left)
    } else {
        throw IllegalArgumentException(&quot;Unknown expression&quot;)
    }

println(evel(Sum(Num(1), Num(2))))</code></pre><ul>
<li><p>if의 분기에 식이 하나밖에 없다면 중괄호를 생략해도 된다.</p>
</li>
<li><p>if 분기에 블록을 사용하는 경우 그 블록의 마지막 식이 그 분기의 결과 값이다.</p>
<pre><code class="language-kotlin">// if 중첩 대신 when 사용하기
fun evel(e: Expr): Int =
  when (e) {
      is Num -&gt;
          e.value
      is Sum -&gt;
          eval(e.right) + evel(e.left)
      else -&gt;
          throw IllegalArgumentException(&quot;Unknown expression&quot;)
  }</code></pre>
<blockquote>
<p>타입을 검사하고 나면 스마트 캐스트가 이뤄진다. 따라서 Num이나 Sum의 멤버에 접근할 때 변수를 강제로 캐스팅할 필요가 없다.</p>
</blockquote>
</li>
<li><p>if나 when의 각 분기에서 수행해야 하는 로직이 복잡해지면 분기 본문에 블록을 사용할 수 있다. 그런 경우 블록의 마지막 문장이 블록 전체의 결과가 된다.</p>
<pre><code class="language-kotlin">fun evalWithLogging(e: Expr): Int =
  when (e) {
      is Num -&gt; {
          println(&quot;num: ${e.value}&quot;)
          e.value
      }
      is Sum -&gt; {
          val left = evalWithLogging(e.left)
          val right = evalWithLogging(e.right)
          println(&quot;sum: $left + $right&quot;)
          left + right
      }
      else -&gt; throw IllegalArgumentException(&quot;Unknown expression&quot;)
  }
</code></pre>
</li>
</ul>
<p>println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4))))
//// 결과
// num: 1
// num: 2
// sum: 1 + 2
// num: 4
// sum: 3 + 4
// num: 7</p>
<pre><code>- &#39;블록의 마지막 식이 블록의 결과&#39;라는 규칙은 블록이 값을 만들어내야 하는 경우 항상 성립한다.
- 이 규칙은 함수에 대해서는 성립하지 않는다. 식이 본문인 함수는 블록을 본문으로 가질 수 없고 블록이 본문인 함수는 내부에 return문이 반드시 있어야 한다.

## 대상을 이터레이션: while과 for 루프
### while 루프
- 두 루프의 문법은 자바와 다르지 않다.
```kotlin
while (조건) { // 조건이 참인 동안 본문을 반복 실행한다.
    /*...*/
}

do {
   /*...*/ 
} while (조건) // 맨 처음에 무조건 본문을 한 번 실행한 다음, 조건이 참인 동안 본문을 반복 실행한다.</code></pre><h3 id="수에-대한-이터레이션-범위와-수열">수에 대한 이터레이션: 범위와 수열</h3>
<ul>
<li>코틀린에서는 자바의 for 루프(어떤 변수를 초기화하고 그 변수를 루프를 한 번 실행할 때마다 갱신하고 루프 조건이 거짓이 될 때 반복을 마치는 형태의 루프)에 해당하는 요소가 없다.</li>
<li>이런 루프의 가장 흔한 용례인 초깃값, 증가 값, 최종 값을 사용한 루프를 대신하기 위해 코틀린에서는 범위(range)를 사용한다.</li>
<li>범위는 기본적으로 두 값으로 이뤄진 구간이다. 보통은 그 두 값은 정수 등의 숫자 타입의 값이며, .. 연산자로 시작 값과 끝 값을 연결해서 범위를 만든다.</li>
<li>코틀린의 범위는 폐구간(닫힌 구간) 또는 양끝을 포함하는 구간이다</li>
<li>이런 식으로 어떤 범위에 속한 값을 일정한 순서로 <a href="https://ko.wikipedia.org/wiki/%EB%B0%98%EB%B3%B5%EB%AC%B8">이터레이션</a>하는 경우를 <a href="https://ko.wikipedia.org/wiki/%EC%88%98%EC%97%B4">수열</a>이라고 부른다.</li>
</ul>
<blockquote>
<p>✅ <strong>이터레이션</strong> : 반복문으로 프로그램 소스 코드 내에서 특정한 부분의 코드가 반복적으로 수행될 수 있도록 하는 구문</p>
<p>✅ <strong>수열</strong> : 수 또는 다른 대상의 순서 있는 나열</p>
</blockquote>
<pre><code class="language-kotlin">// when을 사용해 피즈버즈 게임 구현하기
fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -&gt; &quot;FizzBuzz &quot;
    i % 3 == 0 -&gt; &quot;Fizz &quot;
    i % 5 == 0 -&gt; &quot;Buzz &quot;
    else -&gt; &quot;$i &quot;
}

// 결과: 1 2 Fizz 4 Buzz Fizz 7 ...
for (i in 1..100) {
    print(fizzBuzz(i))
}

// 결과: Buzz 98 Fizz 94 92 Fizz Buzz 88 ...
for (i in 100 downTo 1 step 2) {
    print(fizzBuzz(i))
}</code></pre>
<ul>
<li>끝 값을 포함하지 않는 반만 닫힌 범위(반폐구간 또는 반개 구간)에 대해 이터레이션하면 편할 때가 자주 있다.<ul>
<li>그런 범위를 만들고 싶다면 until 함수를 사용하라.</li>
<li><code>for (x in 0 until size)</code>라는 루프는 <code>for (x in 0..size-1)</code>과 같지만 좀 더 명확하게 개념을 표현한다.</li>
</ul>
</li>
</ul>
<h3 id="맵에-대한-이터레이션">맵에 대한 이터레이션</h3>
<pre><code class="language-kotlin">// 맵을 초기화하고 이터레이션하기
val binaryReps = TreeMap&lt;Char, String&gt;() // 키에 대해 정렬하기 위해 TreeMap을 사용한다.

for (c in &#39;A&#39;..&#39;F&#39;) {
    val binary = Integer.toBinaryString(c.toInt()) // 아스키 코드를 2진 표현으로 바꾼다.
    binaryReps[c] = binary // c를 키로 c의 2진 표현을 맵에 넣는다.
}

// 맵에 대해 이터레이션한다. 맵의 키와 값을 두 변수에 각각 대입한다.
for ((letter, binary) in binaryReps) {
    println(&quot;$letter = $binary&quot;)
}</code></pre>
<ul>
<li>맵에 사용햇던 구조 분해 구문을 맵이 아닌 컬렉션에도 활용할 수 있다. 그런 구조 분해 구문을 사용하면 원소의 현재 인덱스를 유지하면서 컬렉션을 이터레이션할 수 있다.<pre><code class="language-kotlin">val list = arrayListOf(&quot;10&quot;, &quot;11&quot;, &quot;1001&quot;)
for ((index, element) in list.withIndex()) {
  println(&quot;$index: $element&quot;)
}
</code></pre>
</li>
</ul>
<p>//// 결과
// 0: 10
// 1: 11
// 2: 1001</p>
<pre><code>
### in으로 컬렉션이나 범위의 원소 검사
- in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있다. 반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
```kotlin
// in을 사용해 값이 범위에 속하는 검사하기
fun isLetter(c: Char) = c in &#39;a&#39;..&#39;z&#39; || c in &#39;A&#39;..&#39;Z&#39;

fun isNotDigit(c: Char) = c !in &#39;0&#39;..&#39;9&#39;

println(isLetter(&#39;q&#39;)) // true

println(isNotDigit(&#39;x&#39;)) // true</code></pre><pre><code class="language-kotlin">fun recognize(c: Char) = when (c) {
    in &#39;0&#39;..&#39;9&#39; -&gt; &quot;It&#39;s a digit!&quot;
    in &#39;a&#39;..&#39;z&#39;, in &#39;A&#39;..&#39;Z&#39; -&gt; &quot;It&#39;s a letter!&quot;
    else -&gt; &quot;I don&#39;t know...&quot;
}

println(recognize(&#39;8&#39;)) // It&#39;s a digit!</code></pre>
<ul>
<li>범위는 문자에만 국한되지 않는다. 비교가 가능한 클래스라면(<a href="https://tcpschool.com/java/java_collectionFramework_comparable">java.lang.Comparable 인터페이스</a>를 구현한 클래스라면) 그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있다.</li>
</ul>
<blockquote>
<p>✅ <strong>Comparable&lt;T&gt; 인터페이스</strong> : 객체를 정렬하는 데 사용되는 메소드인 compareTo() 메소드를 정의하고 있다. 같은 타입의 인스턴스를 서로 비교해야만 하는 클래스들은 모두 Comparable 인터페이스를 구현하고 있다. 따라서 String, Time, Date와 같은 클래스의 인스턴스도 모두 정렬 가능하다.</p>
</blockquote>
<ul>
<li>Comparable을 사용하는 범위의 경우 그 범위 내의 모든 객체를 항상 이터레이션하지는 못한다.<ul>
<li>&#39;Java&#39;와 &#39;Kotlin&#39; 사이의 모든 문자열을 이터레이션 할 수 없다.<ul>
<li>하지만 in 연산자를 사용하면 값이 범위 안에 속하는지 항상 결정할 수 있다.<pre><code class="language-kotlin">// String에 있는 Comparable 구현이 두 문자열을 알파벳 순서로 비교하기 때문에 
// 여기 있는 in 검사에서도 문자열을 알파벳 순서로 비교한다.
println(&quot;Kotlin&quot; in &quot;Java&quot;..&quot;Scale&quot;) // true </code></pre>
</li>
</ul>
</li>
<li>컬렉션에도 마찬가지로 in 연산을 사용할 수 있다.<pre><code class="language-kotlin">println(&quot;Kotlin&quot; in setOf(&quot;Java&quot;, &quot;Scale&quot;)) // false</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="코틀린의-예외-처리">코틀린의 예외 처리</h2>
<ul>
<li>함수는 정상적으로 종료할 수 있지만 오류가 발생하면 예외를 던질 수 있다.</li>
<li>함수를 호출하는 쪽에서는 그 예외를 잡아 처리할 수 있다.<ul>
<li>발생한 예외를 함수 호출 단에서 처리하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시 던진다.<pre><code class="language-kotlin">if (percentage !in 0..100) {
throw IllegalArgumentException(
    &quot;A percentage value must be between 0 and 100: $percentage&quot;
)
}</code></pre>
</li>
</ul>
</li>
<li>다른 클래스와 마찬가지로 예외 인스턴스를 만들 때도 new를 붙일 필요가 없다.</li>
<li>자바와 달리 코틀린의 throw는 식이므로 다른 식에 포함될 수 있다.<pre><code class="language-kotlin">val percentage =
  if (number in 0..100)
      number
  else
      throw IllegalArgumentException(
        &quot;A percentage value must be between 0 and 100: $percentage&quot;)</code></pre>
</li>
</ul>
<h3 id="try-catch-finally">try, catch, finally</h3>
<pre><code class="language-kotlin">fun readNumber(reader: BufferedReader): Int? { // 함수가 던질 수 있는 예외를 명시할 필요가 없다.
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    }
    catch (e: NumberFormatException) {
        return null
    }
    finally {
        reader.close()
    }
}

val reader = BufferedReader(StringReader(&quot;239&quot;))
println(readNumber(reader)) // 239</code></pre>
<ul>
<li>자바 코드와 가장 큰 차이는 throws 절이 코드에 없다는 점이다.</li>
<li>자바에서는 함수를 작성할 때 함수 선언 뒤에 throws IOException을 붙여야 한다.<ul>
<li>이유는 <a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.io.ioexception?view=net-7.0">IOException</a>이 체크 예외이기 때문이다.</li>
<li>자바에서는 체크 예외를 명시적으로 처리해야 한다.<ul>
<li>어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시해야 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>✅ <strong>IOException</strong> : 스트림, 파일 및 디렉터리를 사용하여 정보에 액세스하는 동안 throw된 예외에 대한 기본 클래스</p>
</blockquote>
<ul>
<li>코틀린은 <a href="https://mangkyu.tistory.com/152">체크 예외나 언체크 예외</a>를 구별하지 않는다.<ul>
<li>함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>✅ <strong>예외</strong> : 애플리케이션 코드에서 예외가 발생하였을 경우에 사용된다. 그리고 Exception은 다시 체크 예외와 언체크 예외로 구분된다.</p>
<p>✅ <strong>체크 예외</strong> : RuntimeException 클래스를 상속받지 않은 예외 클래스들이다. 복구 가능성이 있는 예외이므로 반드시 예외를 처리하는 코드를 함께 작성해야 한다. IOException, SQLException 등이 있으며, 예외를 처리하기 위해서는 catch 문으로 잡거나 throws를 통해 메소드 밖으로 던질 수 있다. 만약 예외를 처리하지 않으면 컴파일 에러가 발생한다.</p>
<p>✅ <strong>언체크 예외</strong> : RuntimeException 클래스를 상속받는 예외 클래스이다. 복구 가능성이 없는 예외들이므로 컴파일러가 예외처리를 강제하지 않는다.</p>
<p>☑️ <strong>에러</strong> : 예외와는 달리 메모리가 부족하는 등과 같이 시스템이 비정상적인 상황인 경우에 사용한다. 주로 JVM에서 발생시키기 때문에 애플리케이션 코드에서 잡아서는 안되며, 잡아서 대응할 수 있는 방법도 없다.</p>
</blockquote>
<ul>
<li>자바는 체크 예외 처리를 강제한다.<ul>
<li>하지만 프로그래머들이 의미 없이 예외를 다시 던지거나, 예외를 잡되 처리하지는 않고 그냥 무시하는 코드를 작성하는 경우가 흔하다.</li>
<li>그로 인해 예외 처리 규칙이 실제로는 오류 발생을 방지하지 못하는 경우가 자주 있다.</li>
</ul>
</li>
</ul>
<h3 id="try를-식으로-사용">try를 식으로 사용</h3>
<pre><code class="language-kotlin">fun readNumber(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        return
    }

    println(number)
}

val reader = BufferedReader(StringReader(&quot;not a number&quot;))
readNumber(reader) // 아무것도 출력되지 않는다.</code></pre>
<ul>
<li>코틀린의 try 키워드는 if나 when과 마찬가지로 식이다. 따라서 try의 값을 변수에 대입할 수 있다.</li>
<li>if와 달리 try의 본문을 반드시 중괄호 {}로 둘러싸야 한다.</li>
<li>다른 문장과 마찬가지로 try의 본문도 내부에 여러 문장이 있으면 마지막 식의 값이 전체 결과 값이다.</li>
</ul>
<blockquote>
<p>✅ <strong><a href="https://dev-jwblog.tistory.com/75">NumberFormatException</a></strong> : 문자형을 숫자형으로 변경 시 발생하는 에러  </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin In Action 1장]]></title>
            <link>https://velog.io/@dev-baik/Kotlin-In-Action-1%EC%9E%A5</link>
            <guid>https://velog.io/@dev-baik/Kotlin-In-Action-1%EC%9E%A5</guid>
            <pubDate>Fri, 20 Oct 2023 06:37:52 GMT</pubDate>
            <description><![CDATA[<h1 id="코틀린이란-무엇이며-왜-필요한가">코틀린이란 무엇이며, 왜 필요한가?</h1>
<blockquote>
<ul>
<li>코틀린 기본 기능 데모</li>
<li>코틀린 언어의 주요 특성</li>
<li>코틀린이 다른 언어보다 더 나은 점</li>
<li>코틀린으로 코드를 작성하고 실행하는 방법</li>
</ul>
</blockquote>
<hr>
<h2 id="코틀린은-무엇인가"><a href="https://velog.io/@dev-baik/Kotlin">코틀린은 무엇인가?</a></h2>
<blockquote>
<p>자바 플랫폼에서 돌아가는 새로운 프로그래밍 언어다.
간결하고 실용적이며, 자바 코드와의 상호운용성을 중시한다.
현재 자바가 사용 중인 곳이라면 거의 대부분 코틀린을 활용할 수 있다.
기존 자바 라이브러리 프레임워크와 함께 잘 작동하며, 성능도 자바와 같은 수준이다.</p>
</blockquote>
<h2 id="코틀린-맛보기">코틀린 맛보기</h2>
<pre><code class="language-kotlin">// Person 이라는 클래스 정의하기
data class Person(val name: String?, val age: Int? = null)

fun main(args: Array&lt;String&gt;) {
    // Person 클래스를 사용해 사람을 모아둔 컬렉션을 만들기
    val persons = listOf(Person(&quot;영희&quot;), Person(&quot;철수&quot;, age = 29))

    // 가장 나이가 많은 사람을 찾아 결과를 출력하기
    val oldest = persons.maxBy { it.age ?: 0 }
    println(&quot;나이가 가장 많은 사람: $oldest&quot;)
}

// 결과: 나이가 가장 많은 사람: Person(name=철수, age=29)</code></pre>
<ol>
<li>name과 age라는 프로퍼티가 들어간 간단한 데이터 클래스를 정의한다.</li>
<li>age 프로퍼티의 디폴트 값은 (따로 지정하지 않은 경우) null 이다.<ul>
<li>사람 리스트를 만들면서 영희의 나이를 지정하지 않았기 때문에 null이 대신 쓰인다.</li>
</ul>
</li>
<li>리스트에서 가장 나이가 많은 사람을 찾기 위해 maxBy 함수를 사용한다.<ul>
<li>maxBy 함수에 전달한 람다 식은 파라미터를 하나 받는다.<ul>
<li>it이라는 이름을 사용하면 (별도로 파라미터 이름을 정의하지 않아도) 람다식의 유일한 인자를 사용할 수 있다.</li>
<li>엘비스 연산자라고 부르는 ?:는 age가 null인 경우 0을 반환하고, 그렇지 않은 경우 age의 값을 반환한다.</li>
</ul>
</li>
</ul>
</li>
<li>영희의 나이를 지정하지는 않았지만 엘비스 연산자 null을 0으로 변환해주기 때문에 철수가 가장 나이가 많은 사람으로 선정될 수 있다.</li>
</ol>
<h2 id="코틀린의-주요-특성">코틀린의 주요 특성</h2>
<h3 id="코틀린의-주-목적">코틀린의 주 목적</h3>
<blockquote>
<p>현재 자바가 사용되고 있는 모든 용도에 적합하면서도 더 간결하고 생산적이며 안전한 대체 언어를 제공하는 것</p>
</blockquote>
<h3 id="정적-타입-지정-언어">정적 타입 지정 언어</h3>
<h4 id="자바와-마찬가지로-코틀린도-정적-타입-지정-언어다">자바와 마찬가지로 코틀린도 정적 타입 지정 언어다.</h4>
<ul>
<li>정적 타입 지정 : 모든 프로그램 구성 요소의 타입을 컴파일 시점에 알 수 있고 프로그램 안에서 객체의 필드나 메서드를 사용할 때마다 컴파일러가 타입을 검증해준다는 뜻</li>
<li>동적 타입 지정<ul>
<li>타입과 관계없이 모든 값을 변수에 넣을 수 있고, 메서드나 필드 접근에 대한 검증이 실행 시점에 일어나며, 그에 따라 코드가 더 짧아지고 데이터 구조를 더 유연하게 생성하고 사용할 수 있다.<ul>
<li>BUT) 이름을 잘못 입력하는 등의 실수도 컴파일 시 걸러내지 못하고 실행 시점에 오류가 발생한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="자바와-달리-코틀린에서는-모든-변수의-타입을-프로그래머가-직접-명시할-필요가-없다">자바와 달리 코틀린에서는 모든 변수의 타입을 프로그래머가 직접 명시할 필요가 없다.</h4>
<ul>
<li>대부분의 경우 코틀린 컴파일러가 문맥으로부터 변수 타입을 자동으로 유추할 수 있기 때문에 타입 선언을 생략해도 된다. (타입 추론)</li>
</ul>
<h3 id="정적-타입-지정의-장점">정적 타입 지정의 장점</h3>
<ul>
<li><p><strong>성능</strong> : 실행 시점에 어떤 메서드를 호출할지 알아내는 과정이 필요없으므로 메서드 호출이 더 빠르다.</p>
</li>
<li><p><strong>신뢰성</strong> : 컴파일러가 프로그램의 정확성을 검증하기 때문에 실행 시 프로그램이 오류로 중단될 가능성이 더 작아진다.</p>
</li>
<li><p><strong>유지 보수성</strong> : 코드에서 다루는 객체가 어떤 타입에 속하는지 알 수 있기 때문에 처음 보는 코드를 다룰 때도 더 쉽다.</p>
</li>
<li><p><strong>도구 지원</strong> : 정적 타입 지정을 활용하면 더 안전하게 리팩토링 할 수 있고, 도구는 더 정확한 코드 완성 기능을 제공할 수 있으며, IDE의 다른 지원 기능도 더 잘 만들 수 있다.</p>
</li>
</ul>
<h3 id="자바와-달리-코틀린의-중요한-특징">자바와 달리 코틀린의 중요한 특징</h3>
<blockquote>
<p>널이 될 수 있는 타입을 지원함에 따라 컴파일 시점에 널 포인터 예외가 발생할 수 있는지 여부를 검사할 수 있어서 좀 더 프로그램의 신뢰성을 높일 수 있다.</p>
</blockquote>
<h3 id="함수형-프로그래밍의-핵심-개념">함수형 프로그래밍의 핵심 개념</h3>
<h4 id="일급-시민인-함수">일급 시민인 함수</h4>
<p>함수(프로그램의 행동을 나타내는 코드 조각)를 일반 값처럼 다룰 수 있다. 함수를 변수에 저장할 수 있고, 함수를 인자로 다른 함수에 전달할 수 있으며, 함수에서 새로운 함수를 만들어서 반환할 수 있다.</p>
<pre><code class="language-kotlin">// 함수를 변수에 저장
val add: (Int, Int) -&gt; Int = { a, b -&gt; a + b }

// 함수를 인자로 다른 함수에 전달
fun calculate(x: Int, y: Int, operation: (Int, Int) -&gt; Int): Int {
    return operation(x, y)
}

val result = calculate(10, 5, add)
println(result) // 출력: 15</code></pre>
<pre><code class="language-kotlin">// 함수에서 새로운 함수를 만들어서 반환
fun addX(x: Int): (Int) -&gt; Int {
    // x를 받아들이고 x를 더하는 함수를 반환
    return { y -&gt; x + y }
}

val add5 = addX(5) // addX 함수를 호출하여 x=5인 함수를 생성
val result = add5(3) // 반환된 함수를 호출하여 5 + 3 계산
println(result) // 출력: 8</code></pre>
<h4 id="불변성">불변성</h4>
<p>함수형 프로그래밍에서는 일단 만들어지고 나면 내부 상태가 절대로 바뀌지 않는 불변 객체를 사용해 프로그램을 작성한다.</p>
<pre><code class="language-kotlin">data class Person(val name: String, val age: Int)

fun main() {
    val person1 = Person(&quot;Alice&quot;, 30)
    val person2 = person1.copy(name = &quot;Bob&quot;)

    println(person1) // 출력: Person(name=Alice, age=30)
    println(person2) // 출력: Person(name=Bob, age=30)
}</code></pre>
<h4 id="부수-효과-없음">부수 효과 없음</h4>
<p>함수형 프로그래밍에서는 입력이 같으면 항상 같은 출력을 내놓고 다른 객체의 상태를 변경하지 않으며, 함수 외부나 다른 바깥 환경과 상호작용하지 않는 순수 함수를 사용한다.</p>
<pre><code class="language-kotlin">// 순수하지 않은 함수: 외부 상태를 변경하는 함수
var total = 0

fun impureAdd(a: Int) {
    total += a
}

fun main() {
    impureAdd(5) // 외부 상태 변경
    println(total) // 출력: 5

    impureAdd(3) // 다시 호출하여 외부 상태 변경
    println(total) // 출력: 8
}</code></pre>
<h3 id="함수형-프로그래밍의-이점">함수형 프로그래밍의 이점</h3>
<h4 id="간결성">간결성</h4>
<p>함수형 코드는 그에 상응하는 명령형 코드에 비해 더 간결하며 우아하다. (순수) 함수를 값처럼 활용할 수 있으면 더 강력한 추상화를 할 수 있고 강력한 추상화를 사용해 코드 중복을 막을 수 있다.</p>
<pre><code class="language-kotlin">// 고차 함수 예시 1: 다른 함수를 인자로 받는 함수
fun calculate(a: Int, b: Int, operation: (Int, Int) -&gt; Int): Int {
    return operation(a, b)
}

fun add(x: Int, y: Int): Int {
    return x + y
}

fun subtract(x: Int, y: Int): Int {
    return x - y
}

fun main() {
    val result1 = calculate(10, 5, ::add)
    println(&quot;Result of addition: $result1&quot;)

    val result2 = calculate(10, 5, ::subtract)
    println(&quot;Result of subtraction: $result2&quot;)
}</code></pre>
<pre><code class="language-kotlin">// 고차 함수 예시 2: 함수를 반환하는 함수
fun operationChoice(operator: String): (Int, Int) -&gt; Int {
    return when (operator) {
        &quot;add&quot; -&gt; ::add
        &quot;subtract&quot; -&gt; ::subtract
        else -&gt; { a, b -&gt; a * b }
    }
}

fun main() {
    val operation = operationChoice(&quot;add&quot;)
    val result = operation(10, 5)
    println(&quot;Result of chosen operation: $result&quot;)
}</code></pre>
<pre><code class="language-kotlin">fun List&lt;Int&gt;.filterAndSum(predicate: (Int) -&gt; Boolean): Int {
    return this.filter { predicate(it) }.sum()
}

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val evenSum = numbers.filterAndSum { it % 2 == 0 }
    val oddSum = numbers.filterAndSum { it % 2 != 0 }

    println(&quot;Sum of even numbers: $evenSum&quot;) // 출력: Sum of even numbers: 30
    println(&quot;Sum of odd numbers: $oddSum&quot;)   // 출력: Sum of odd numbers: 25
}</code></pre>
<h4 id="다중-스레드를-사용해도-안전하다">다중 스레드를 사용해도 안전하다</h4>
<p>다중 스레드 프로그램에서는 적절한 동기화 없이 같은 데이터를 여러 스레드가 변경하는 경우 가장 많은 문제가 생긴다. 하지만, 불변 데이터 구조를 사용하고 순수 함수를 그 데이터 구조에 적용한다면 다중 스레드 환경에서 같은 데이터를 여러 스레드가 변경할 수 없다. 즉, 복잡한 동기화를 적용하지 않아도 된다.</p>
<pre><code class="language-kotlin">fun main() {
    // 불변한 데이터 구조인 리스트를 생성
    val numbers = listOf(1, 2, 3, 4, 5)

    // 여러 스레드가 이 데이터를 동시에 읽어도 문제 없음

    // 순수 함수를 사용하여 리스트를 변경하지 않고 결과를 계산
    val sum = calculateSum(numbers)
    val product = calculateProduct(numbers)

    println(&quot;Sum: $sum&quot;)       // 출력: Sum: 15
    println(&quot;Product: $product&quot;) // 출력: Product: 120
}

fun calculateSum(numbers: List&lt;Int&gt;): Int {
    return numbers.sum()
}

fun calculateProduct(numbers: List&lt;Int&gt;): Int {
    // reduce : 컬랙션 내의 데이터를 모두 모으는 함수, 초기값이 없이 첫번째 요소로 시작
    return numbers.reduce { acc, num -&gt; acc * num }
}</code></pre>
<h4 id="테스트하기-쉽다">테스트하기 쉽다</h4>
<p>부수 효과가 있는 함수는 그 함수를 실행할 때 필요한 전체 환경을 구성하는 준비 코드가 따로 필요하지만, 순수 함수는 그런 준비 코드 없이 독립적으로 테스트할 수 있다.</p>
<h3 id="함수형-프로그래밍을-위한-지원">함수형 프로그래밍을 위한 지원</h3>
<ul>
<li><p>함수 타입을 지원함에 따라 어떤 함수가 다른 함수를 파라미터로 받거나 함수가 새로운 함수를 반환할 수 있다.</p>
</li>
<li><p>람다 식을 지원함에 따라 번거로운 준비 코드를 작성하지 않아도 코드 블록을 쉽게 정의하고 여기저기 전달할 수 있다.</p>
</li>
<li><p>데이터 클래스는 불변적인 값 객체를 간편하게 만들 수 있는 구문을 제공한다.</p>
</li>
<li><p>코틀린 표준 라이브러리는 객체와 컬렉션을 함수형 스타일로 다룰 수 있는 API를 제공한다.</p>
</li>
</ul>
<h2 id="코틀린의-철학">코틀린의 철학</h2>
<h3 id="실용성">실용성</h3>
<p>다른 프로그래밍 언어가 채택한 이미 성공적으로 검증된 해법과 기능에 의존한다. 이로 인해 언어의 복잡도가 줄어들고 이미 알고 기존 개념을 통해 코틀린을 더 쉽게 배울 수 있다.</p>
<h3 id="간결성-1">간결성</h3>
<p>코틀린을 만들면서 의미가 없는 부분을 줄이고, 언어가 요구하는 구조를 만족시키기 위해 프로그램에 꼭 넣어야 하는 부수적인 요소를 줄이기 위해 많은 노력을 기울였다. 게터, 세터, 생성자 파라미터를 필드에 대입하기 위한 로직 등 묵시적으로 제공한다.</p>
<h3 id="안전성">안전성</h3>
<h4 id="안전성과-생산성-사이에는-트레이드오프-관계가-성립한다">안전성과 생산성 사이에는 <a href="https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%93%9C%EC%98%A4%ED%94%84">트레이드오프 관계</a>가 성립한다.</h4>
<p>코틀린을 만들면서 자바보다 더 높은 수준의 안전성을 달성하되 전체 비용은 더 적게 지불하고 싶었다. 타입 안전성, 한걸음 더 나아가 실행 시점에 오류를 발생시키는 대신 컴파일 시점 검사를 통해 오류를 더 많이 방지해준다. 코틀린의 타입 시스템은 null이 될 수 없는 값을 추적하며, 실행 시점에 NullPointerException이 발생할 수 있는 연산을 사용하는 코드를 금지한다.</p>
<p>또한, 어떤 객체를 다른 타입으로 캐스트하기 전에 타입을 미리 검사하지 않아 발생할 수 있는 ClassCastException을 방지해준다. 타입 검사와 캐스트가 한 연산자에 의해 이뤄진다.</p>
<pre><code class="language-kotlin">if (value is String)
    println(value.toUpperCase())</code></pre>
<h3 id="상호운용성">상호운용성</h3>
<p>기존 자바 라이브러리를 그대로 사용할 수 있다. 자바 메서드를 호출하거나 자바 클래스를 상속하거나 인터페이스를 구현하거나 자바 애노테이션을 코틀린 코드에 적용하는 등의 일이 모두 가능하다.</p>
<p>자체 컬렉션 라이브러리를 제공하지 않는다. 자바 표준 라이브러리 클래스에 의존한다. 다만 코틀린에서 더 쉽게 활용할 수 있게 몇 가지 기능을 더할 뿐이다.</p>
]]></description>
        </item>
    </channel>
</rss>