<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발 일기</title>
    <link>https://dev-hui.tistory.com/</link>
    <description>High hope</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 16:35:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hui._.</managingEditor>
    <image>
      <title>개발 일기</title>
      <url>https://tistory1.daumcdn.net/tistory/6868262/attach/034d60c601ae43698520ac88796c7f94</url>
      <link>https://dev-hui.tistory.com</link>
    </image>
    <item>
      <title>Java와 다른 Kotlin의 인터페이스</title>
      <link>https://dev-hui.tistory.com/70</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론: 자바 개발자가 코틀린을 공부하면서&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린을 공부하면서 자바와는 다른 방식으로 인터페이스를 활용하는 점이 상당히 생소하게 느껴졌습니다. 자바의 인터페이스는 주로 구현해야 할 메서드를 정의하는 추상적인 설계도, 즉 규격의 역할을 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Spring AOP를 적용할 때 특정 인터페이스 기준으로 Aspect를 설정하고, 해당 인터페이스를 구현한 클래스들에만 선택적으로 공통 로직을 적용하는 용도로 활용할 수 있습니다. 이 경우 인터페이스는 구조를 식별하기 위한 경계로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 코틀린의 인터페이스는 이러한 규격의 역할에 머무르지 않습니다. 자바의 인터페이스는 &quot;무엇을 구현해야 하는가&quot;에 초점을 맞췄다면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;코틀린의 인터페이스는&lt;/b&gt; &lt;b&gt;어떤 행위를 조합할 수 있는 가&lt;/b&gt;&lt;/span&gt;에 초첨을 두어 한 단계 확장되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 OAuth2를 구현 함에 있어 코틀린의 인터페이스를 적극적으로 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바 방식&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 OAuth2를 구현하면 다음과 같은 흐름일 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. `OAuth2UserInfo` 인터페이스를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. `OAuth2UserInfo`를 implements한 `GoogleUserInfo`, `NaverUserInfo` 클래스를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 각 클래스에서 `getEmail()`, `getName()`등 직접 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식의 단점은 &lt;b&gt;중복&lt;/b&gt;입니다. 구글이나 네이버의 데이터를 가져와서 정제하는 로직이 비슷해도, 각각 클래스 안에 중복해서 코드를 짜야 합니다. 아니면 중복을 피하기 위해 `Provider` 클래스를 생각할 수 있습니다. 그러나 해당 클래스에 각 서비스 별 메서드를 선언해야 한다는 점은 피할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코틀린 방식&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 핵심은 `by` 키워드를 이용한 위임입니다.&amp;nbsp;만약 OAuth2의 JSON 응답을 파싱하는 로직이 담긴 `Map` 기반의 구현체가 있다고 가정해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1767933133064&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 공통 규격 선언
interface OAuth2UserInfo {
    val attributes: Map&amp;lt;String, Any&amp;gt;
    fun getEmail(): String
    fun getName(): String
}

// 공통 로직을 담은 구현체
class DefaultOAuth2UserInfo(
	override val attributes: Map&amp;lt;String, Any&amp;gt;
) : OAuth2UserInfo {
    override fun getEmail() = attributes[&quot;email&quot;] as? String ?: &quot;&quot;
    override fun getName() = attributes[&quot;name&quot;] as? String ?: &quot;&quot;
}

// 실제 서비스별 구현 (직접 구현하지 않고 위임)
class GoogleUserInfo(
    attributes: Map&amp;lt;String, Any&amp;gt;
) : OAuth2UserInfo by DefaultOAuth2UserInfo(attributes) {
    // 추가로 구글에만 특화된 로직이 필요할 때만 override
    override fun getName() = TODO(...)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 사용하면 `GoogleUserInfo`는 `getEmail()`을 직접 구현하지 않아도 외부에서 이를 호출할 수 있습니다. 상속의 제약(단일 상속)에서 벗어나 여러 개의 구현체를 내 것처럼 조립할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 조립의 의미&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 인터페이스는 로직(Default Method)를 가질 수 있으며, 추상 프로퍼티를 정의할 수 있습니다. 이는 마치 필요한 기능을 블록처럼 클래스에 붙이는 경험을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767933526502&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Validatable {
    fun validateEmail(email: String) = email.contains(&quot;@&quot;)
}

interface Loggable {
    val logTag: String
    fun log(msg: String) = println(&quot;log[$logTag]: $msg&quot;)
}

// 여러 개의 인터페이스를 조립해서 기능을 확장
class OAuth2Service : Validatable, Loggable {
    override val logTag = &quot;OAuth2Service&quot;
    
    fun process(email: String) {
        if (validateEmail(email) { // Validatable에서 가져온 기능
            log(&quot;processing $email&quot;) // Loggable에서 가져온 기능
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서도 위처럼 다중 구현이 가능하지만 인터페이스의 상태에 가까운 프로퍼티를 정의하거나 위임을 사용하는 것이 훨씬 번거롭기 때문에 보통 `Service` 클래스에 모든 로직을 구현하거나, `Util`을 호출하는 방식을 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 코틀린은 인터페이스로 기능을 쪼개서 조립하는 방식을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: 자바와 코틀린 관점 차이&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바를&amp;nbsp;주로&amp;nbsp;사용했던&amp;nbsp;개발자&amp;nbsp;입장에서&amp;nbsp;코틀린의&amp;nbsp;프로젝트의&amp;nbsp;인터페이스의&amp;nbsp;사용은&amp;nbsp;어색할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;하지만&amp;nbsp;상속보다는&amp;nbsp;조립이라는&amp;nbsp;현대적인&amp;nbsp;객체지향&amp;nbsp;철학이&amp;nbsp;담겨있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서&amp;nbsp;공통&amp;nbsp;로직의&amp;nbsp;중복을&amp;nbsp;피하기&amp;nbsp;위해&amp;nbsp;추상&amp;nbsp;클래스를&amp;nbsp;설계하거나&amp;nbsp;별도의&amp;nbsp;클래스로&amp;nbsp;로직을&amp;nbsp;빼서&amp;nbsp;관리했다면,&amp;nbsp;코틀린은&amp;nbsp;인터페이스&amp;nbsp;위임을&amp;nbsp;통해&amp;nbsp;모든&amp;nbsp;과정을&amp;nbsp;더&amp;nbsp;유연하게&amp;nbsp;구현할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;인터페이스를&amp;nbsp;단순한&amp;nbsp;껍데기로&amp;nbsp;보는&amp;nbsp;것이&amp;nbsp;아닌&amp;nbsp;클래스에&amp;nbsp;강력한&amp;nbsp;기능을&amp;nbsp;부여하는&amp;nbsp;도구로&amp;nbsp;봐야&amp;nbsp;합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;&lt;b&gt;자바 인터페이스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;&lt;b&gt;코틀린 인터페이스&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;주요 역할&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;강제된 규격, 구조 식별 (AOP)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;기능의 위임 및 조립&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;중복 해결&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;추상 클래스 상속 또는 별도 Provider&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;`by` 키워드를 통한 위임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;상태/로직&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;상수만 가능, default 메서드 활용&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;추상 프로퍼티, 풍부한 기본 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;설계 철학&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;추상적인 설계도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;장착, 조합&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/70</guid>
      <comments>https://dev-hui.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 9 Jan 2026 13:42:02 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in action 2/e] 코틀린 타입 시스템</title>
      <link>https://dev-hui.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin&amp;nbsp;in&amp;nbsp;Action&amp;nbsp;2/e&amp;nbsp;서적을&amp;nbsp;읽으면서&amp;nbsp;정리하고자&amp;nbsp;쓴&amp;nbsp;글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; data-alt=&quot;kotlin logo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE58XR%2FdJMcahC3jwj%2FVXHimodrEpef5IhOGtqpO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;널 가능성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린을 비롯한 최신 언어는 `null`에 대한 문제를 런타임 시점에서 컴파일 시점으로 옮겼습니다. 널이 될 수 있는지 확인하는 타입 시스템을 추가함으로써 컴파일러가 여러 가지 오류를 컴파일 시 미리 감지하여 런타임에 발생할 수 있는 예외 가능성을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;안전한 호출 연산자(Safe Call Operator): ?.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`?.`는 `null` 검사와 메서드 호출을 한 번의 연산으로 수행합니다. 왼쪽 값이 `null`이면 즉시 `null`을 반환하고, `null`이 아닐 때만 오른쪽을 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769652837564&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;s?.toUpperCase() // s 문자열 대문자 변환 (아래와 동일하게 동작한다.)
if (s != null) s.toUpperCase() else null&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769652947354&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Address(val country: String)
class Company(val address: Address?)
class Person(val company: Company?)

fun Person.countryName(): String =
    this.company?.address?.country ?: &quot;Unknown&quot;

fun main() {
    val person = Person(null)
    println(person.countryName())  // Unknown
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`company` 또는 `address`가 `null`이면 전체 표현식은 `null` 임으로 &quot;Unknown&quot;이 출력됩니다. 이처럼 `?.` 연산자를 통해 다른 추가 검사 없이 `Person`의 회사 주소의 `country` 프로퍼티를 단 한 줄로 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엘비스 연산자(Elvis Operator): ?:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 `null` 대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있는 연산자를 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769653442167&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = value ?: &quot;default&quot; // 왼쪽 값이 null이면 오른쪽 &quot;default&quot; 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 `return`, `throw`도 식이기 때문에 엘비스 연산자 우항에 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769653511666&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val address = person.company?.address
    ?: throw IllegalArgumentException(&quot;No address&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769653528818&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Address(
    val streetAddress: String,
    val zipCode: Int,
    val city: String,
    val country: String
)

class Company(val address: Address?)
class Person(val company: Company?)

fun printShippingLabel(person: Person) {
    val address = person.company?.address
        ?: throw IllegalArgumentException(&quot;No address&quot;)

    with(address) {
        println(streetAddress) // this.streetAddress (this 생략)
        println(&quot;$zipCode $city, $country&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제를 보면 `company` 또는 `address`가 `null`이면 `IllegalArgumentException` 발생되고, `null`이 아니면 정상 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;안전한 캐스트 (Safe Cast) as?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`as?`는 값을 지정한 타입으로 변환을 시도하고, 변환할 수 없으면 예외 대신 `null`을 반환하는 연산자입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769659470765&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val person = obj as? Person // 캐스트 성공 시 Person 타입, 실패 시 null&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769659516994&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(
    val firstName: String,
    val lastName: String
) {
    override fun equals(other: Any?): Boolean {
        val otherPerson = other as? Person ?: return false
        return otherPerson.firstName == firstName &amp;amp;&amp;amp;
               otherPerson.lastName == lastName
    }

    override fun hashCode(): Int =
        firstName.hashCode() * 37 + lastName.hashCode()
}

fun main() {
    println(p1 == p2) // true
    println(p1.equals(42)) // false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;널 아님 단언(Not-null Assertion): !!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`!!`는 nullable 타입을 강제로 non-null 타입으로 변환하는 연산자입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769659785295&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val sNotNull: String = s!! // 값이 not null이면 정상 동작, null이면 NPE 발생

fun ignoreNulls(s: String?) {
    val sNotNull: String = s!!
    println(sNotNull.length) // s가 null이면 NPE 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`!!`는 컴파일러의 `null` 안정성을 우회합니다. 즉, 개발자가 &quot;여기서는 절대 null이 아니다.&quot;라고 강제 단언하는 것이기 때문에 사용에 있어 주의가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 아래와 같이 여러 개의 `!!`를 한 줄에 사용하는 경우, 어디서 NPE가 발생했는지 파악하기 어렵고, 유지보수 시 위험성이 커지기 때문에 권장하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769659915577&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;person.company!!.address!!.country  // 권장하지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;let 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`let`은 객체를 람다의 인자로 전달하여 블록을 실행하는 범위 함수입니다. 특히 nullable 타입과 함께 사용할 때 많이 쓰입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660249391&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;email?.let { sendEmailTo(it) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`email`이 `not-null`이면 블록을 실행하고, `null`이면 아무것도 실행되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660310972&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sendEmailTo(email: String) {
    println(&quot;Sending email to $email&quot;)
}

fun main() {
    var email: String? = &quot;yole@example.com&quot;

    email?.let { sendEmailTo(it) } // 실행됨

    email = null
    email?.let { sendEmailTo(it) } // 실행되지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`?.` 연산자와 함께 사용하면 `null` 체크와 값 사용을 한 번에 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러 값을 동시에 `null` 검사가 필요한 경우 `let` 중첩으로 사용하면 가독성이 떨어질 수 있습니다. 이러한 경우는 일반적인 `if`문을 사용하는 것이 더 명확합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660437964&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a?.let { aa -&amp;gt;
    b?.let { bb -&amp;gt;
        // 복잡해짐
    }
}

if (a != null &amp;amp;&amp;amp; b != null) {
    // 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나중에 초기화할 프로퍼티 lateinit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 기본적으로 모든 `non-null` 프로퍼티를 생성자에서 초기화해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660620162&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Example(val name: String)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드나 DI 환경에서는 생성자가 아니라 별도의 메서드에서 초기화해야 하는 경우가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660686940&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyTest {

    private var myService: MyService? = null

    @Before
    fun setUp() {
        myService = MyService()
    }

    @Test
    fun testAction() {
        myService!!.performAction() // !! 필요
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 nullable 타입을 사용해야 하고, 매번 `!!` 또는 `?.` 연산자가 사용해야 하는 문제가 생기게 됩니다. 이를 해결하기 위한 방법으로 `lateinit`를 사용하면 초기화를 나중에 미루면서도 `non-null` 타입을 유지할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769660793525&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyTest {

    private lateinit var myService: MyService // lateinit 추가

    @Before
    fun setUp() {
        myService = MyService()
    }

    @Test
    fun testAction() {
        myService.performAction()  // null 검사 불필요
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 `null`이 될 수 없는 타입이라 해도 더 이상 생성자 안에서 초기화할 필요가 없어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코틀린의 원시 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 달리 원시 타입(primitive)과 래퍼 타입(wrapper)을 구분하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769661393580&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java: 원시/래퍼 타입으로 나뉨
int a = 10; &amp;lt;-&amp;gt; Integer a = 10;

// Kotlin: 항상 동일한 타입 사용
val a: Int = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 실행 시점에서 가능한 한 가장 효율적인 방식으로 컴파일됩니다. 즉, nullable 여부에 따라 원시 타입을 쓸지 래퍼 타입을 쓸지 결정됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nullable이 아닌 `Int` &amp;rarr; `int`&lt;/li&gt;
&lt;li&gt;nullable인 `Int?` &amp;rarr; `Integer`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 원시 타입도 메서드를 가질 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769661728439&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun showProgress(progress: Int) {
    val percent = progress.coerceIn(0, 100)
    println(&quot;We're ${percent}% done!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 `int`는 메서드를 가질 수 없지만 코틀린의 `Int`는 확장 함수 및 멤버 함수를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;숫자 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 숫자 타입을 자동으로 변환하지 않습니다. 자바는 작은 타입에서 큰 타입으로 자동 캐스팅이 가능하지만 코틀린은 명시적 변환이 필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769661981699&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
int i = 1;
long l = i; // 자동 변환

// Kotlin
val i = 1
val l: Long = i // 컴파일 오류
val l2: Long = i.toLong() // 정상&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 모든 원시 타입에 대해 명시적인 타입 변환 함수를 제공합니다. 변환 함수는 toXxx() 형태로 정의되어 있으며, toByte(), toShort(), toInt(), toLong(), ... 등과 같이 앞에 to를 붙이고 대상 타입명을 사용하는 규칙을 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 코틀린은 왜 자동 변환을 허용하지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 명확성과 타입 안정성을 우선합니다. 자동 변환이 허용되면 예상치 못한 타입 변환이 발생할 수 있으므로 모든 숫자 변환은 명시적으로 수행하도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Any, Any? 최상위 타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 `Object`는 클래스 계층의 최상위 타입이듯 코틀린은 `Any`가 모든 타입의 최상위 타입입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769662304185&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val value: Any = &quot;hello&quot;
val number: Any = 42

val value2: Any? = null
val number2 Any? = null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 `Int` 같은 숫자 타입도 `Any`의 하위 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unit 타입: 코틀린의 void&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Unit`은 자바의 `void`와 같은 역할을 수행합니다. 즉, 의미 있는 값을 반환하지 않는 함수의 반환 타입입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769662520812&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printMessage(): Unit {
    println(&quot;Hello&quot;)
}

// 반환 타입 Unit을 생략할 수도 있다.
fun printMessage() {
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 코틀린의&amp;nbsp;Unit이&amp;nbsp;자바&amp;nbsp;void와&amp;nbsp;다른&amp;nbsp;점은&amp;nbsp;무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 `void`는 상태이고, 코틀린의 `Unit`은 타입으로 사용됩니다. 즉 `Unit`은 타입 인자로 사용할 수 있으며, 제네릭을 사용할 때 의미가 분명해집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769662973444&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Processor&amp;lt;T&amp;gt; {
    fun process(): T
}

// 반환값이 없는 경우에도 Unit 타입을 인자로 넘길 수 있음
class NoResultProcessor : Processor&amp;lt;Unit&amp;gt; {
    override fun process() {
        // logic...
        // 명시적인 return Unit이 없어도 컴파일러가 자동으로 처리합니다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`process()`는 `Unit`을 반환하기 때문에 별도의 `return`이 필요하지 않습니다. 자바의 `void`로는 이런 제네릭 인터페이스를&amp;nbsp;표현할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서도 제네릭에 대응하기 위해 `void`의 래퍼 클래스인 `Void`를 사용할 수 있습니다. 하지만 코틀린의 `Unit`과 사용성 면에 차이점이 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;코틀린의 `Unit`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;자바의 `Void`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;성격&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;객체가 하나뿐인 싱글톤 타입&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;인스턴스화할 수 없는 참조 타입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;값의 존재&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`Unit` 객체가 실재&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;항상 `null`만 가짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;반환 코드&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;return 생략 가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;return null; 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nothing 타입: 이 함수는 결코 정상적으로 끝나지 않는다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 결코 성공적으로 값을 돌려주는 일이 없으므로 반환 값이라는 개념 자체가 의미 없는 함수가 존재합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769663532046&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun fail(message: String) : Nothing {
    throw IllegalStateException(message)
}

val address = company.address ?: fail(&quot;No address&quot;)
println(address.city)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Nothing`은 아무 값도 포함하지 않습니다. 따라서 컴파일러는 `Nothing`이 반환 타입인 함수가 결코 정상 종료가 되지 않음을 알고 그 함수를 호출하는 코드를 분석할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컬렉션과 배열&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬렉션과 널 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에서도 원소가 `null`이 될 수 있는지 여부는 매우 중요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769663776661&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun addValidNumbers(numbers: List&amp;lt;Int?&amp;gt;) {
    var sum = 0
    var invalid = 0

    for (number in numbers) {
        if (number != null) sum += number
        else invalid++
    }

    println(&quot;Sum: $sum&quot;)
    println(&quot;Invalid count: $invalid&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`List&amp;lt;Int?&amp;gt;`의 원소를 꺼내면 타입은 `Int?`이므로 사용 전 반드시 `null` 체크가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 표준 라이브러리는 `null` 제거를 위한 `fillterNotNull()`을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769663948517&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun addValidNumbers(numbers: List&amp;lt;Int?&amp;gt;) {
    val valid = numbers.filterNotNull() // not-null 값만 할당
    println(&quot;Sum: ${valid.sum()}&quot;)
    println(&quot;Invalid count: ${numbers.size - valid.size}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`null` 제거 후 `Int` 타입으로 안전하게 사용할 수 있게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;읽기 전용 컬렉션 vs 변경 가능 컬렉션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 읽기 전용 인터페이스와 변경 가능 인터페이스를 분리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 전용: `Collection`, `List`, `Set`, `Map`&lt;/li&gt;
&lt;li&gt;변경&amp;nbsp;가능:&amp;nbsp;`MutableCollection`,&amp;nbsp;`MutableList`,&amp;nbsp;`MutableSet`,&amp;nbsp;`MutableMap`&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769664206517&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list: List&amp;lt;String&amp;gt; = listOf(&quot;a&quot;, &quot;b&quot;)
val mutableList: MutableList&amp;lt;String&amp;gt; = mutableListOf(&quot;a&quot;, &quot;b&quot;)

fun main() {
    list.add(&quot;c&quot;) // 읽기 전용 컬렉션 컴파일 오류 발생
    mutableList.add(&quot;c&quot;) // 변경 가능 컬렉션 추가 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린 &amp;harr; 자바 상호운용 시 주의점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 모든 코틀린의 컬렉션은 자바 컬렉션입니다. 따라서 코틀린에서 읽기 전용 `List`를 선언해도 자바 코드는 수정이 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769664308406&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
public static List&amp;lt;String&amp;gt; uppercaseAll(List&amp;lt;String&amp;gt; items) {
    for (int i = 0; i &amp;lt; items.size(); i++) {
        items.set(i, items.get(i).toUpperCase());
    }
    return items;
}

// Kotlin
fun printInUppercase(list: List&amp;lt;String&amp;gt;) {
    println(CollectionUtils.uppercaseAll(list))
    println(list.first())  // 이미 변경됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 읽기 전용은 컴파일러 수준의 제약이지 진짜 불변을 의미하지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체의 배열과 원시 타입의 배열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 배열은 제네릭 클래스입니다. 또한 코틀린은 배열을 사용하기 보다 컬렉션을 사용하는 것을 권장합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769664421901&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val arr: Array&amp;lt;String&amp;gt;

val arr = arrayOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)

val arr = arrayOfNulls&amp;lt;String&amp;gt;(5) // nullable 타입일 때만 사용 가능

val letters = Array(26) { i -&amp;gt;
    ('a' + i).toString() // 람다를 통해 각 요소 초기화 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린의 널이 될 수 있는 타입을 지원해 NPE를 컴파일 시점에 감지할 수 있다.&lt;/li&gt;
&lt;li&gt;코틀린의 `?.`, `?:`, `!!`, `let` 함수 등을 사용하면 널이 될 수 있는 타입을 간결하게 다룰 수 있다.&lt;/li&gt;
&lt;li&gt;`as?`를 사용하면 널이 아닌 경우 다른 타입으로 취급할 수 있다.&lt;/li&gt;
&lt;li&gt;코틀린은 원시/래퍼 타입을 구분하지 않는다. JVM 상황에 맞게 원시/래퍼 타입을 쓸지 정해준다.&lt;/li&gt;
&lt;li&gt;`Any`는 코틀린의 모든 타입의 조상 타입이며, 자바의 `Object`에 해당한다. 또한 `Unit`은 `void`와 비슷하다.&lt;/li&gt;
&lt;li&gt;정상적으로 끝나지 않는 함수의 반환 타입을 지정할 때 `Nothing` 타입을 사용한다.&lt;/li&gt;
&lt;li&gt;코틀린의 컬렉션은 자바 컬렉션을 확장해서 사용하며, 읽기 전용, 변경 전용 컬렉션으로 구별하여 제공한다.&lt;/li&gt;
&lt;li&gt;플랫폼 타입을 확장하는 경우 널 가능성과 변경 가능성에 대해 깊이 생각해야 한다. (플랫폼 타입은 널 인지 아닌지 컴파일러가 체크하지 않기 때문)&lt;/li&gt;
&lt;li&gt;코틀린의 배열은 Array 제네릭 클래스를 사용한다. 또한 컴파일 시 자바 배열로 컴파일된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kotlin</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/69</guid>
      <comments>https://dev-hui.tistory.com/69#entry69comment</comments>
      <pubDate>Wed, 7 Jan 2026 10:13:02 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in action 2/e] 람다로 프로그래밍</title>
      <link>https://dev-hui.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin&amp;nbsp;in&amp;nbsp;Action&amp;nbsp;2/e&amp;nbsp;서적을&amp;nbsp;읽으면서&amp;nbsp;정리하고자&amp;nbsp;쓴&amp;nbsp;글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; data-alt=&quot;kotlin logo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E58XR/dJMcahC3jwj/VXHimodrEpef5IhOGtqpO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE58XR%2FdJMcahC3jwj%2FVXHimodrEpef5IhOGtqpO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;람다 식과 멤버 참조&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;람다와 컬렉션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 람다를 보다 직관적으로 사용할 수 있으며, 간결한 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767578614461&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun findTheOldest(peoples: List&amp;lt;Person&amp;gt;) {
    var maxAge = 0
    for (person in peoples) {
        if (person.age &amp;gt; maxAge) {
            maxAge = person.age
        }
    }
    return maxAge
}
fun main() {
    val peoples = listOf(Person(&quot;A&quot;, 10), Person(&quot;B&quot;, 15))
    println(findTheOlest(peoples)) // 15
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767579171469&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val peoples = listOf(Person(&quot;A&quot;, 10), Person(&quot;B&quot;, 15))
    println(peoples.maxByOrNull {it.age}) // 15
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;현재 영역에 있는 변수에 접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 익명 내부 클래스를 사용할 때, 외부 메서드의 로컬 변수가 `final`이 아니면 사용할 수 없습니다. 그러나 코틀린은 `final`이 아닌 변수에도 접근할 수 있으며, 그 값을 바꿀 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767579858271&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printProblemCouns(response: Collection&amp;lt;String&amp;gt;) {
    var clientErrors = 0 // final이 아닌 변수 var
    var serverErrors = 0
    response.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;) 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  어떻게 그런 동작이 가능한 것일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 컴파일러는 `var` 변수를 람다 안에 수정하려고 할 때 래퍼(Wrapper)라는 기법을 사용하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`val`&lt;b&gt;:&lt;/b&gt; 변수 값을 람다 코드에 함께 저장합니다. (자바와 유사)&lt;/li&gt;
&lt;li&gt;`var`&lt;b&gt;:&lt;/b&gt; 변수를 직접 저장하는 대신, 그 변수를 담고 있는 특별한 객체를 만듭니다. 람다는 이 객체를 참조하며 람다 안에서도 외부 변수를 수정할 수 있는 것처럼 보이게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 주의할 점이 있습니다. 변수를 공유할 수 있다고 해서 항상 최신 값을 얻을 수 있는 것은 아닙니다. 특히 비동기 코드나 이벤트 핸들러에서 문제가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767590675307&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun tryToCountButtonClicks(button: Button): Int {
    var clicks = 0
    button.onClick { clicks++ } // 버튼이 클릭될 때 실행될 '약속'만 하고 함수는 바로 아래로 진행됨
    return clicks // 버튼이 클릭되기도 전에 0을 반환해버림!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`onClick` 핸들러는 호출될 때마다 `clicks`의 값을 증가시키지만 그 값의 변경을 관찰할 수는 없습니다. 핸들러는 `tryToCountButtonClicks`가 `clicks`를 반환한 다음에 호출되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컬렉션 함수형 API&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필수적인 함수: filter와 map&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`filter` 함수는 컬렉션에서 조건에 맞지 않는 원소를 걸러내는 역할을 합니다. 즉, 원소의 개수를 줄일 수 있지만 각 소 자체를 변경하지 않습니다. 반면, 원소를 다른 형태로 변환해야 할 경우 `map` 함수를 사용합니다. `map` 함수는 주어진 람다를 컬렉션의 각 원소에 적용하고, 그 결과를 모아 새로운 컬렉션을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767591397115&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val people = listOf(Person(&quot;Alice&quot;, 10), Person(&quot;Bob&quot;, 31))
    
    val adults = people.filter { it.age &amp;gt; 30 })
    println(adults) // [Person(name=Bob, age=31)]
    
    val names = people.map { it.name }
    println(names) // [Alice, Bob]
    
    val numbers = mapOf(0 to &quot;zero&quot;, 1 to &quot;one&quot;)
    val upperNumbers = numbers.mapValues { it.value.uppercase() }
    println(upperNumbers) // {0=ZERO, 1=ONE}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬렉션에 술어 적용: all, any, count, find&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 대해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건에 만족하는지 판단하는 연산이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767592014399&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Person을 받아 Boolean을 반환 (27세 이하)
val canBeIn27: (Person) -&amp;gt; Boolean = { person -&amp;gt; person.age &amp;lt;= 27 }

fun main() {
    val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 31))
    
    val all = people.all(canBeIn27) // 모든 사람이 canBeIn27 조건에 만족해야 true
    println(all) // false 
    
    val any = people.any(canBeIn27) // 하나라도 만족하면 true
    println(any) // true
    
    val count = people.count(canBeIn27) // 조건을 만족하는 원소의 개수
    println(count) // 1
    
    val find = people.find(canBeIn27) // 조건을 만족하는 첫번째 원소
    println(find) // Person(name=Alice, age=27)
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;&lt;b&gt;함수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;&lt;b&gt;반환 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;`all`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;Boolean&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;모두 만족하는가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;`any`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;Boolean&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;하나라도 만족하는가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;`count`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;Int&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;몇 개가 만족하는가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;`find`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;T?&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;처음으로 만족하는 원소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중첩된 컬렉션 안의 원소 처리: flatMap과 flatten&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩된 컬렉션을 다룰 때 자주 필요한 작업은 안쪽 컬렉션의 원소를 한 번에 처리하는 것입니다. 코틀린은 이를 위해 `flatMap`과 `flatten` 함수를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`flatMap` 함수는 컬렉션의 각 원소에 주어진 람다를 적용해 &lt;b&gt;또 다른 컬렉션을 생성한 뒤&lt;/b&gt;, 그 결과로 만들어진 &lt;b&gt;여러 개의 컬렉션을 하나의 컬렉션으로 평탄화&lt;/b&gt;합니다. 즉, `flatMap`은 `map`과 `flatten`을 결합한 연산입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767592609031&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val strings = listOf(&quot;abc&quot;, &quot;def&quot;)
    
    // 각 문자열을 List&amp;lt;Char&amp;gt;로 변환한 뒤 하나의 리스트로 합침
    println(strings.flatMap { it.toList() }) // [a, b, c, d, e, f]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 문자열을&amp;nbsp; 리스트로 변환(`map`)하고, 그 결과를 생성된 중첩 리스트에 하나의 리스트로 합칩니다(`flatten`).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`flatMap`은 객체 내부에 컬렉션을 포함하는 구조에서도 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767592825631&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Book(val title: String, val authors: List&amp;lt;String&amp;gt;)
fun main() {
    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;))
    )
    
    // 각 책의 저자 목록을 하나의 컬렉션으로 합친뒤 중복 제거
    val result = books.flatMap { it.authors }.toSet()
    println(result) // [Jasper Fforde, Terry Pratchett, Neil Gaiman]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 `flatten` 함수는 이미 중첩된 컬렉션이 있을 때, 별도의 변환 없이 이를 하나의 컬렉션으로 펼치는 역할만 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지연 연산(lazy) 컬렉션 연산&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 `List`, `Set` 등에서 `map`이나 `filter`를 연결하면 즉시 계산 방식이 적용됩니다. 즉, 수평적 처리를 의미하며 각 단계마다 연산 결과를 담는 임시 컬렉션이 생성됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767593251263&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(1, 2, 3, 4)
val result = list
    .map { it * it } // 1단계: [1, 4, 9, 16] 생성
    .filter { it % 2 == 0 } // 2단계: [4, 16] 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계에서 모든 원소를 순회하며 제곱한 뒤, 새로운 리스트 `[1, 4, 9, 16]`을 생성합니다. 이후 2단계에서 만들어진 리스트를 다시 순회하며 조건에 맞는 원소만 골라 또 다른 리스트 `[4, 16]`을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시퀀스 연산 실행: 중간 연산과 최종 연산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 달리 지연 연산(lazy) 은 시퀀스를 기반으로 하며, 중간&amp;nbsp;결과&amp;nbsp;컬렉션을&amp;nbsp;생성하지&amp;nbsp;않고&amp;nbsp;각&amp;nbsp;원소를&amp;nbsp;단위로&amp;nbsp;연산&amp;nbsp;체인을&amp;nbsp;끝까지&amp;nbsp;처리하는&amp;nbsp;수직적&amp;nbsp;방식으로&amp;nbsp;동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767593485555&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(1, 2, 3, 4)
val result = list.asSequence() // 
    .map { it * it }
    .filter { it % 2 == 0 }
    .toList() // 최종 연산 (이때 모든 계산이 시작)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소 1부터 순서대로 `map` &amp;rarr; `filter` 적용하여 탈락(1은 탈락), 원소 2 `map` &amp;rarr; `filter` 적용(4는 합격),... 이러한 방식으로 원소 하나하나 끝까지 통과한 뒤 다음 원소로 넘어가는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스 연산은 크게 2가지 종류로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중간 연산:&lt;/b&gt; `map`, `filter` 등 다른 시퀀스를 반환하며, 실제 계산은 수행하지 않고 어떻게 계산할지에 대한 설계도만 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 연산:&lt;/b&gt; `toList()`, `find()`, `sum()`, `forEach()` 등 시퀀스를 다시 컬렉션으로 바꾸거나 구체적인 값을 얻는 연산으로 호출되는 순간에만 모든 계산이 시작됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  왜 시퀀스를 다시 컬렉션으로 되돌려야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스는 데이터를 직접 저장하지 않는 계산 설계도와 같으므로, 인덱스 접근이나 데이터 재사용, 그리고 기존 리스트 기반 API와의 호환성을 위해 실제 데이터 저장소인 컬렉션으로 변환하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 일반 컬렉션과 지연 연산 어떻게 사용해야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반&amp;nbsp;컬렉션은&amp;nbsp;소규모&amp;nbsp;데이터에서&amp;nbsp;빠른&amp;nbsp;속도를&amp;nbsp;보장하지만&amp;nbsp;중간&amp;nbsp;결과물로&amp;nbsp;인한&amp;nbsp;메모리&amp;nbsp;부담이&amp;nbsp;큰&amp;nbsp;반면,&amp;nbsp;시퀀스는&amp;nbsp;대규모&amp;nbsp;데이터나&amp;nbsp;복잡한&amp;nbsp;연산에서&amp;nbsp;임시&amp;nbsp;컬렉션&amp;nbsp;없이&amp;nbsp;메모리를&amp;nbsp;아끼고&amp;nbsp;최적화된&amp;nbsp;처리가&amp;nbsp;가능하므로&amp;nbsp;데이터의&amp;nbsp;양과&amp;nbsp;연산&amp;nbsp;단계에&amp;nbsp;맞춰&amp;nbsp;선택하는&amp;nbsp;것이&amp;nbsp;좋습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;일반 컬렉션 (Eager)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;시퀀스 (Lazy)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;연산 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;모든 원소에 대해 단계별 수행 (수평)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;각 원소 하나씩 전체 체인 통과 (수직)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;중간 결과물&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;매 단계마다 새로운 컬렉션 생성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;생성 안함 (메모리 효율적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;적합한 상황&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;데이터 양이 적고, 속도가 중요할 때&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;데이터 양이 아주 많거나 무한할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;최종 연산&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;필요 없음 (즉시 실행)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;반드시 필요 (최종 연산 시점에 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 자바 스트림(Java Stream)과의 비교 &lt;br /&gt;코틀린의 시퀀스는 자바 8의 스트림과 거의 유사합니다. 하지만 결정적인 차이가 있습니다. 병렬 처리: 자바 스트림은 parallelStream()을 통해 여러 CPU 코어를 활용한 병렬 연산이 가능하지만, 코틀린 시퀀스는 현재 직렬(Sequential) 처리만 지원합니다. 병렬 처리가 필요하다면 자바 스트림을 사용하거나 코루틴을 활용해야 합니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바 함수형 인터페이스 활용&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자바 메서드에 람다를 인자로 전달&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 인터페이스를 인자로 받는 자바 메서드에 코틀린 람다를 전달할 수 있습니다. 이때 객체 식을 사용하는 것과 람다를 사용하는 것에는 성능 상 차이가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체 식&lt;/b&gt; `object &lt;b&gt;:&lt;/b&gt; ..`: 메서드를 호출할 때마다 &lt;b&gt;새로운 객체가 생성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;람다:&lt;/b&gt; 정의된 함수의 변수에 접근하지 않는 람다는 메서드 호출 시마다 대응하는 &lt;b&gt;무명 객체를 반복 사용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767594886247&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체 식: 호출 시마다 매번 새로운 인스턴스 생성
postponeComputation(1000, object : Runnable {
    override fun run() { println(42) }
})

// 람다: 변수를 포획하지 않으므로 프로그램 전체에서 인스턴스가 단 하나만 생성
postponeComputation(1000) { println(42) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수 포획 시의 동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다가 주변 영역의 변수를 포획한다면 매 호출마다 같은 인스턴스를 사용할 수 없습니다. 이 경우 컴파일러는 주변 변수를 포획한 새로운 인스턴스를 매번 생성하여 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767595065396&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun handlerComputation(id: String) {
    // id를 포획하므로 호출할 때마다 새로운 Runnable 인스턴스가 생성
    postponeComputation(1000) { println(id) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  객체 식과 람다 그래서 결론은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 람다는&amp;nbsp;내부적으로&amp;nbsp;무명&amp;nbsp;클래스로&amp;nbsp;컴파일되는데,&amp;nbsp;외부&amp;nbsp;변수를&amp;nbsp;포획하지&amp;nbsp;않을&amp;nbsp;때는&amp;nbsp;단&amp;nbsp;하나의&amp;nbsp;싱글톤&amp;nbsp;인스턴스만&amp;nbsp;생성하여&amp;nbsp;재사용함으로써&amp;nbsp;효율을&amp;nbsp;높이지만,&amp;nbsp;&lt;b&gt;변수를&amp;nbsp;포획할&amp;nbsp;경우&amp;nbsp;상태&amp;nbsp;저장을&amp;nbsp;위한&amp;nbsp;필드가&amp;nbsp;추가된&amp;nbsp;객체를&amp;nbsp;매번&amp;nbsp;새로&amp;nbsp;생성하는&amp;nbsp;비용이&amp;nbsp;발생&lt;/b&gt;합니다. 다만, `inline` 함수에 전달되는 람다는 무명 클래스 생성 없이 본문 코드가 호출 지점에 직접 삽입되므로, 객체 생성 오버헤드 없이 가장 최적화된 성능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 앞에 `inline` 키워드를 붙이게 되면 컴파일러는 함수 호출 지점에 함수의 본문과 람다의 본문을 그대로 붙여 넣습니다. 실제 바이트 코드를 뜯어보면 함수 호출이 사라집니다. 즉, 객체 생성 비용이 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767596315462&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// inline 키워드 추가
inline fun flow(action: () -&amp;gt; Unit) {
    println(&quot;작업 시작&quot;)
    action()
    println(&quot;작업 끝&quot;)
}
fun main() {
    flow { println(&quot;Hello!&quot;) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767596334723&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// inline 사용 시 main 함수의 바이트 코드 추정
fun main() {
    println(&quot;작업 시작&quot;)
    println(&quot;Hello!&quot;) // 전달한 람다가 삽입되어 action 함수 호출 비용 X
    println(&quot;작업 끝&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`inline` 함수는 다음에 정리할 고차 함수에서 구체적으로 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SAM 생성자: 람다를 함수형 인터페이스 명시적으로 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SAM(Single Abstract Method) 생성자는 컴파일러가 람다를 함수형 인터페이스로 자동 변환하지 못하는 상황(반환 값으로 사용하거나 변수에 할당할 때 등)에서, 람다를 특정 인터페이스의 인스턴스로 명시적으로 변환하기 위해 컴파일러가 제공하는 특수한 함수입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767596506991&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SAM 생성자 사용 예시
fun createAllDoneRunnable(): Runnable {
    // Runnable이라는 이름을 직접 명시하여 람다를 Runnable 객체로 만듦
    return Runnable { println(&quot;All done!&quot;) } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SAM 생성자는 람다를 변수에 담아두고 여러 곳에서 재사용할 때 유용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767596543357&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 람다를 SAM 생성자로 감싸 변수에 저장
val handler = View.OnClickListener { view -&amp;gt;
    println(&quot;버튼 클릭됨: ${view.id}&quot;)
}

// 여러 버튼에 동일한 리스너 인스턴스 적용
button1.setOnClickListener(handler)
button2.setOnClickListener(handler)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수신 객체 지정 람다: with &amp;amp; apply&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 람다와 차별화되는 코틀린만의 특징 중 하나는 &lt;b&gt;수신 객체 지정 람다&lt;/b&gt;입니다. 람다 블록 내부에 특정 객체의 메서드를 마치 자신의 것처럼 호출할 수 있게 해 주어, 코드의 가독성을 높이고 DSL(도메인 특화 언어) 작성을 용이하게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;with: 객체를 인자로 전달하여 조작하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`with` 함수는 첫 번째 인자로 받은 객체를 두 번째 인자인 람다의 수신 색채로 만듭니다. 람다 내부에서는 `this`를 통해 객체에 접근할 수 있으며, `this`를 생략하고 곧바로 멤버 함수를 호출할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767746963737&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 일반적인 구조
fun alphabet(): String {
    val sb = StringBuilder()
    for (letter in 'A'..'Z') {
        sb.append(letter)
    }
    sb.append(&quot;\nNow I know the alphabet!&quot;)
    return sb.toString()
}

// ✅ with 함수를 사용하여 중복 제거한 경우
fun alphabet(): String = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter) // this.append에서 this 생략 가능
    }
    append(&quot;\nNow I know the alphabet!&quot;)
    toString() // 람다의 마지막 식의 결과를 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;apply: 수신 객체를 그대로 반환하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`with` 함수는 람다의 결괏값이 필요할 때 유용하지만, 가끔은 연산을 수행한 후 객체 자신이 다시 필요한 경우가 있습니다. 이때 `apply`를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767747161359&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ apply를 사용하여 StringBuilder 객체 자체를 반환받는 경우
fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append(&quot;\nNow I Know the alphabet!&quot;)
}.toString() // apply는 StringBuilder를 반환하므로, 마지막에 toString() 호출 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 표준 라이브러리는 `StringBuilder`를 이용한 문자열 생성을 좀 더 구체적이고 우아한 함수인 `buildString`을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767747301034&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun alphabet() = buildString {
    for (letter in 'A'..'Z') {
        append(letter) // 수신 객체인 StringBuilder의 append 호출
    }
    append(&quot;\nNow I know the alphabet!&quot;)
    // 명시적인 toString() 호출이 필요 없음
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5581%;&quot;&gt;&lt;b&gt;함수&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.3954%;&quot;&gt;&lt;b&gt;수신 객체 전달 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.3721%;&quot;&gt;&lt;b&gt;반환값&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.6744%;&quot;&gt;&lt;b&gt;주요 용도&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5581%;&quot;&gt;`with`&lt;/td&gt;
&lt;td style=&quot;width: 26.3954%;&quot;&gt;함수의 인자로 전달&lt;/td&gt;
&lt;td style=&quot;width: 23.3721%;&quot;&gt;람다의 마지막 식&lt;/td&gt;
&lt;td style=&quot;width: 32.6744%;&quot;&gt;객체를 이용해 결과를 만들고 싶을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.5581%;&quot;&gt;`apply`&lt;/td&gt;
&lt;td style=&quot;width: 26.3954%;&quot;&gt;확장 함수 형태로 호출&lt;/td&gt;
&lt;td style=&quot;width: 23.3721%;&quot;&gt;수신 객체 자신&lt;/td&gt;
&lt;td style=&quot;width: 32.6744%;&quot;&gt;객체의 속성을 설정(초기화)할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;람다를 사용하면 코드 조각을 다른 함수에게 인자로 넘길 수 있다.&lt;/li&gt;
&lt;li&gt;코틀린은 람다가 함수 인자인 경우 괄호 밖으로 빼낼 수 있고, 람다의 인자가 단 하나뿐인 경우 인자 이름을 지정하지 않고 `it`이라는 디폴트 이름을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;자바와 다르게 람다 안에 있는 코드는 그 람다가 들어있는 바깥 함수의 변수를 읽거나 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;`filter`, `map`, `all`, `any` 등의 함수를 활용하면 컬렉션에 대한 대부분의 연산을 직접 원소를 이터레이션 하지 않고 수행할 수 있다.&lt;/li&gt;
&lt;li&gt;시퀀스를 사용하면 중간 결과를 담는 컬렉션을 생성하지 않고도 컬렉션에 대한 여러 연산을 조합할 수 있다.&lt;/li&gt;
&lt;li&gt;함수형 인터페이스(추상 메서드가 단 하나뿐인 SAM 인터페이스)를 인자로 받는 자바 함수를 호출할 경우 람다를 함수형 인터페이스 인자 대신 넘길 수 있다.&lt;/li&gt;
&lt;li&gt;수신 객체 지정 람다를 사용하면 람다 안에서 미리 정해둔 수신 객체의 메서드를 직접 호출할 수 있다.&lt;/li&gt;
&lt;li&gt;표준 라이브러리 `with` 함수를 사용하면 어떤 객체에 대한 참조를 반복해서 언급하지 않고 그 객체의 메서드를 호출할 수 있다.&amp;nbsp; `apply`를 사용하면 어떤 객체라도 빌더 스타일의 API를 사용해 생성하고 초기화할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <category>apply</category>
      <category>filtter</category>
      <category>Kotlin</category>
      <category>KotlinInAction</category>
      <category>with</category>
      <category>람다</category>
      <category>람다로 프로그래밍</category>
      <category>수신 객체</category>
      <category>코틀린</category>
      <category>코틀린인액션</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/67</guid>
      <comments>https://dev-hui.tistory.com/67#entry67comment</comments>
      <pubDate>Wed, 31 Dec 2025 11:13:35 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in Action 2/e] 클래스, 객체, 인터페이스</title>
      <link>https://dev-hui.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin&amp;nbsp;in&amp;nbsp;Action&amp;nbsp;2/e&amp;nbsp;서적을&amp;nbsp;읽으면서&amp;nbsp;정리하고자&amp;nbsp;쓴&amp;nbsp;글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgZuR8/dJMcaiPr9MQ/djquCSfFVZeFhcdkZIrxf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgZuR8/dJMcaiPr9MQ/djquCSfFVZeFhcdkZIrxf1/img.png&quot; data-alt=&quot;kotlin logo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgZuR8/dJMcaiPr9MQ/djquCSfFVZeFhcdkZIrxf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgZuR8%2FdJMcaiPr9MQ%2FdjquCSfFVZeFhcdkZIrxf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 클래스와 인터페이스는 설계 단계부터 안정성과 간결함을 중시하도록 설계되었습니다. 인터페이스 내에 프로퍼티 선언이 가능해 구현체에 특정 상태를 강제할 수 있으며, 자바와 달리 모든 선언이 기본적으로 `public`이면서 동시에 상속이 제한된 `final` 상태라 의도치 않은 확장을 방지합니다. 또한 중첩 클래스는 별도의 키워드 없이 선언할 경우 외부 클래스에 대한 참조를 갖지 않아 메모리 누수 위험을 줄여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 data 즉, `data class`로 선언하면 컴파일러가 일부 표준 함수를 생성해 주며, `by` 키워드를 활용한 위임 기능을 제공함으로써 개발자가 반복적이고 번거로운 준비 코드를 직접 작성할 필요 없이 핵심 로직에만 집중할 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;클래스 계층 정의&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 코틀린 인터페이스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 인터페이스는 자 8 인터페이스와 유사하며, 추상 메서드 뿐만 아니라 구현을 포함한 메서드도 정의할 수 있습니다. 이는 자바의 `default` 메서드와 같은 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바의 `extends`와 `implements` 키워드 대신 콜론 (`:`) 하나로 클래스 확장과 인터페이스 구현을 모두 처리합니다. 자바와 동일하게 클래스는 오직 하나의 클래스만 확장할 수 있으며, 인터페이스는 개수 제한이 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767069471448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Clickable {
    fun click() // 추상 메소드
    fun showOff() = println(&quot;I'm clickable!&quot;) // 디폴트 구현
}

class Button : Clickable {
    override fun click() = println(&quot;I was clicked&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1-1)   다중 인터페이스 상속 관계에서 동일한 함수가 구현되어 있다면?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 동일한 `showOff` 함수가 있는 `Focusable` 인터페이스를 추가 생성해 봅시다. 그리고 `Button`에 상속한다면 코틀린은 중복된 상위 함수가 있음을 감지하고 반드시 하위 클래스에 구현되어야 한다는 컴파일 오류가 발생하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767069712992&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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'm focusable!&quot;)
}

class Button : Clickable, Focusable { // showOff 함수를 가진 2개의 인터페이스 상속
    override fun click() = println(&quot;I was clicked&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsAkAl/dJMcagRFY92/4KcC9K5oFPhq7hlHHv7Qf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsAkAl/dJMcagRFY92/4KcC9K5oFPhq7hlHHv7Qf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsAkAl/dJMcagRFY92/4KcC9K5oFPhq7hlHHv7Qf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsAkAl%2FdJMcagRFY92%2F4KcC9K5oFPhq7hlHHv7Qf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;232&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1-2) 자바에서 코틀린의 메서드가 있는 인터페이스 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 구버전인 자바 6도 호환되도록 설계되었습니다. 자바 6는 인터페이스의 `default` 메서드를 지원하지 않기 때문에 코틀린 컴파일러는 이를 해결하기 위해 독특한 방식을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터페이스:&lt;/b&gt; 메서드 선언부만 포함합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정적 보조 클래스:&lt;/b&gt; 인터페이스와 함께 생성되며, `default` 메서드의 실제 구현 본문을 정적(static) 메서드로 담고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;자바 클래스에서 코틀린 인터페이스를 상속받아 구현할 때 코틀린의 &lt;/b&gt;&lt;/span&gt;`default`&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; 메서드 구현에 의존할 수 없습니다.&lt;/b&gt;&lt;/span&gt; 즉, 자바에서는 코틀린에 이미 구현된 본문이 있더라도 직접 `override`하여 본문을 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) open, final, abstract 변경자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 설계 시 상속을 엄격하게 관리합니다. 이는 취약한 기반 클래스 문제를 방지하고 더 안전한 코드를 작성하기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-1)  왜 코틀린은 기본적으로 `final`인가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 상속을 명시적으로 금지하지 않는 한 모든 클래스를 상속할 수 있습니다. 하지만 이는 기반 클래스가 변경될 때 하위 클래스의 동작이 예기치 않게 깨지는 위험을 초래합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 자바에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&quot;상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라&quot;&lt;/b&gt;&lt;/span&gt;에 따라, 코틀린은 모든 클래스와 메서드를 기본적으로 `final`로 설정하였습니다. 상속이나 `override`를 허용하려면 개발자가 명시적으로 `open`을 붙여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-2) `open`과 `override` 사용법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스의 상속을 허용하려면 클래스 앞에 `open`을 붙여야 하며, 메서드나 프로퍼티 역시 `open`이 붙어 있어야만 `override`가 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767071171846&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class RichButton : Clickable { 
    fun disable() {} // final: 하위 클래스에서 오버라이드 불가
    open fun animate() {} // open: 하위 클래스에서 오버라이드 가능
    override fun click() {} // 오버라이드한 메소드는 기본적으로 open 상태
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 `override` 중인 메서드를 더 이상 하위 클래스에 재정의하지 못하게 막고 싶다면, 앞에 `final`을 명시해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767071214045&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class RichButton : Clickable {
    final override fun click() {} // 앞에 final을 붙여 오버라이드 금지
}&lt;/code&gt;&lt;/pre&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; &amp;nbsp;열린 클래스와 스마트 캐스트&lt;/b&gt;&lt;br /&gt;&lt;b&gt;클래스와 프로퍼티가 기본적으로 final이기 때문에 얻는 큰 이점 중 하나는 스마트 캐스트입니다.&lt;/b&gt; 스마트 캐스트는 타입 검사 후 변수가 변하지 않는다는 보장이 있어야만 작동합니다. 코틀린 프로퍼티는 기본적으로 final이므로, 다른 클래스가 상속받아 커스텀 접근자를 정의하거나 값을 바꿀 위험이 적어 대부분의 프로퍼티를 고민 없이 스마트 캐스트에 활용할 수 있습니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-3) 추상 클래스와 인터페이스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바처럼 코틀린에서도 `abstract` 클래스를 선언할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추상 클래스:&lt;/b&gt; 인스턴스화할 수 없으며, `abstract` 멤버는 구현이 없으므로 항상 `open` 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터페이스:&lt;/b&gt; 멤버 앞에 `final`, `open`, `abstract`를 붙이지 않습니다. 인터페이스 멤버는 항상 열려있으며, 본문이 없으면 자동으로 `abstract`가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767071703423&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class Animated {
    abstract fun animate() // 반드시 오버라이드 해야 함
    open fun stopAnimating() {} // 비추상 함수지만 오버라이드 허용
    fun animateTwice() {} // 기본적으로 final
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-4) 스프링 프레임워크와 같이 사용하면 클래스와 메서드는 `open`으로 열어두어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프록시 기반의 기능 확장 (AOP) &lt;br /&gt;&lt;/b&gt;스프링은&amp;nbsp;`@Transactional`,&amp;nbsp;`@Cacheable`&amp;nbsp;같은&amp;nbsp;기능을&amp;nbsp;제공할&amp;nbsp;때,&amp;nbsp;내가&amp;nbsp;작성한&amp;nbsp;클래스를&amp;nbsp;직접&amp;nbsp;사용하는&amp;nbsp;대신&amp;nbsp;이를&amp;nbsp;상속받은&amp;nbsp;가짜&amp;nbsp;객체(프록시)를&amp;nbsp;만들어&amp;nbsp;실행합니다.&amp;nbsp;코틀린의&amp;nbsp;기본값인&amp;nbsp;`final`&amp;nbsp;상태로는&amp;nbsp;상속이&amp;nbsp;불가능하여&amp;nbsp;이런&amp;nbsp;부가&amp;nbsp;기능을&amp;nbsp;덧붙일&amp;nbsp;수&amp;nbsp;없습니다.&lt;b&gt;&lt;br /&gt;&lt;br /&gt;빈(Bean) 관리와 싱글톤 보장 &lt;br /&gt;&lt;/b&gt;`@Configuration`이&amp;nbsp;붙은&amp;nbsp;설정&amp;nbsp;클래스&amp;nbsp;역시&amp;nbsp;스프링이&amp;nbsp;내부적으로&amp;nbsp;상속을&amp;nbsp;통해&amp;nbsp;관리합니다.&amp;nbsp;클래스가&amp;nbsp;열려&amp;nbsp;있어야만&amp;nbsp;스프링이&amp;nbsp;객체&amp;nbsp;생성&amp;nbsp;과정을&amp;nbsp;제어하고&amp;nbsp;싱글톤&amp;nbsp;패턴을&amp;nbsp;유지할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;b&gt;&lt;br /&gt;JPA 지연 로딩 (Lazy Loading) &lt;br /&gt;&lt;/b&gt;JPA(Hibernate)는&amp;nbsp;데이터가&amp;nbsp;실제로&amp;nbsp;필요할&amp;nbsp;때까지&amp;nbsp;로딩을&amp;nbsp;미루기&amp;nbsp;위해&amp;nbsp;엔티티를&amp;nbsp;상속받은&amp;nbsp;프록시&amp;nbsp;객체를&amp;nbsp;생성합니다.&amp;nbsp;엔티티&amp;nbsp;클래스가&amp;nbsp;`final`이면&amp;nbsp;이&amp;nbsp;기능을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;없어&amp;nbsp;성능&amp;nbsp;최적화가&amp;nbsp;어려워집니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; all-open 플러그인 실무에서는 매번 open을 붙이는 번거로움을 피하기 위해 kotlin-spring 플러그인을 사용합니다. 이 플러그인은 스프링 주요 애노테이션이 붙은 클래스들을 컴파일 시점에 자동으로 open으로 만들어 줍니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 176px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 22.8682%; text-align: center; height: 22px;&quot;&gt;&lt;b&gt;변경자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.031%; text-align: center; height: 22px;&quot;&gt;&lt;b&gt;해당 멤버&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.1007%; text-align: center; height: 22px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 22.8682%; height: 22px;&quot;&gt;`final`&lt;/td&gt;
&lt;td style=&quot;width: 29.031%; height: 22px;&quot;&gt;오버라이드 불가&lt;/td&gt;
&lt;td style=&quot;width: 48.1007%; height: 22px;&quot;&gt;클래스 멤버의 &lt;b&gt;기본 상태&lt;/b&gt;입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 22.8682%; height: 44px;&quot;&gt;`open`&lt;/td&gt;
&lt;td style=&quot;width: 29.031%; height: 44px;&quot;&gt;오버라이드 가능&lt;/td&gt;
&lt;td style=&quot;width: 48.1007%; height: 44px;&quot;&gt;명시적으로 선언해야만 상속 및 재정의가 가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 22.8682%; height: 44px;&quot;&gt;`abstract`&lt;/td&gt;
&lt;td style=&quot;width: 29.031%; height: 44px;&quot;&gt;반드시 오버라이드 필요&lt;/td&gt;
&lt;td style=&quot;width: 48.1007%; height: 44px;&quot;&gt;추상 클래스 내에서만 사용하며, 구현체가 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 22.8682%; height: 44px;&quot;&gt;`override`&lt;/td&gt;
&lt;td style=&quot;width: 29.031%; height: 44px;&quot;&gt;상위 멤버 재정의&lt;/td&gt;
&lt;td style=&quot;width: 48.1007%; height: 44px;&quot;&gt;오버라이드 중인 멤버는 기본적으로 `open`입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 내부 클래스와 중첩된 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바처럼 클래스 안에 클래스를 선언할 수 있습니다. 하지만 &lt;b&gt;자바와 코틀린은 이 기능을 다루는 기본 방식이 반대로 되어있습니다.&lt;/b&gt; 이 차이를 모르면 예상치 못한 메모리 누수나 직렬화 오류를 겪을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-1) 자바에서의 실수: `NotSerializableException` 발생 원인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View의 상태를 저장하는 간단한 예시를 살펴봅시다. 자바에서 `Button` 클래스 내부에 상태 정보를 담는 `ButtonState`를 선언하면 다음과 같은 형태가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767072913512&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Button implements View {
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }
    // Button 클래스 안에 ButtonState 클래스
    public class ButtonState implements State { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황에 `ButtonState`를 직렬화하려고 하면 `java.io.NotSerializableException: Button` 예외가 발생됩니다. &lt;b&gt;자바는 중첩 클래스를 그냥 선언하면 내부 클래스(Inner Class)가 되며 자신을 둘러싼 바깥쪽 클래스(`Button`)에 대한 &lt;span style=&quot;color: #ef5369;&quot;&gt;묵시적인 참조를 포함합니다.&lt;/span&gt;&lt;/b&gt; 이로 인해 `ButtonState`를 직렬화하면 `Button` 객체까지 함께 직렬화해야 하는데, `Button`은 직렬화가 불가능하여 예외가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 이를 해결하기 위해선 클래스 앞에 `static` 키워드를 붙여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-2) 코틀린은 기본 값이 반대&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 이러한 자바의 번거로움과 실수를 방지하기 위해 기본 설정을 반대로 뒤집었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 클래스 내부에 선언된 클래스는 명시적으로 요청하지 않은 한 바깥쪽 클래스에 대한 참조가 없습니다. 즉, 자바의 `static class`와 같은 상태가 기본 값으로 사용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767073284949&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Button : View {
    override fun getCurrentState(): State = ButtonState()

    // 아무 수식어가 없으면 자바의 static 중첩 클래스와 동일함
    class ButtonState : State { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 `ButtonState`는 바깥쪽 `Button`을 참조하지 않으며, 직렬화 시에도 문제가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-3) 바깥쪽 클래스의 참조가 필요하다면? `inner` 키워드 사용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;inner 클래스:&lt;/b&gt; 바깥쪽 클래스에 대한 참조를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바깥쪽 참조 방법:&lt;/b&gt; 내부 클래스 안에서 바깥쪽 클래스의 인스턴스를 가리키려면 `this@OuterClassName` 문법을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767073432249&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Outer {
    val outerValue = &quot;Outer String&quot;
    inner class Inner {
        fun getOuterReference(): Outer {
            // 바깥쪽 클래스의 멤버에 접근하거나 인스턴스 참조
            println(outerValue)
            return this@Outer 
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;클래스 안의 클래스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;자바&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;코틀린&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;바깥쪽 참조 없는 클래스&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`static class A`&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`class A` (기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;바깥쪽 참조 있는 클래스&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`class A` (기본값)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`inner class A`&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 봉인된 클래스 `sealed` 키워드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 클래스인 `Expr`에는 숫자를 표현하는 `Num`과 덧셈 연산을 표현하는 `Sum`이라는 두 하위 클래스가 있습니다. `when` 식을 통해 이 모든 하위 클래스를 처리하면 다음과 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767077047588&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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 -&amp;gt; e.value
        is Sum -&amp;gt; eval(e.right) + eval(e.left)
        else -&amp;gt; // &quot;else&quot; 분기가 꼭 있어야 한다. 
            throw IllegalArgumentException(&quot;Unknown expression&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 위와 같이 `when` 식을 사용할 경우 `Num` 또는 `Sum` 클래스가 아니라면 반드시 `else` 분기를 추가해야 합니다. 또한 새로운 하위 클래스가 추가되더라도 컴파일러는 누락된 분기를 감지해주지 못합니다. 만약 특정 하위 클래스에 대한 처리를 실수로 빠뜨릴 경우 의도하지 않게 `else` 분기가 실행되며 이는 런타임 시점에 발견되기 어려운 치명적인 버그로 이어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 해당 문제를 `sealed` 키워드를 통해 해결할 수 있습니다. 상위 클래스에 `sealed`를 붙이면 그 상위 클래스를 상속한 하위 클래스 정의를 제한할 수 있습니다. 덕분에 `sealed` 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767077447476&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Expr { // sealed 사용
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when (e) {
        is Expr.Num -&amp;gt; e.value
        is Expr.Sum -&amp;gt; eval(e.right) + eval(e.left)
        // sealed 덕분에 else 분기를 생성할 필요가 없다.
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLHYLO/dJMcagjQkh8/ERjUxN7djLMlSqAdbnw49k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLHYLO/dJMcagjQkh8/ERjUxN7djLMlSqAdbnw49k/img.jpg&quot; data-alt=&quot;https://livebook.manning.com/book/kotlin-in-action/chapter-4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLHYLO/dJMcagjQkh8/ERjUxN7djLMlSqAdbnw49k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLHYLO%2FdJMcagjQkh8%2FERjUxN7djLMlSqAdbnw49k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;112&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://livebook.manning.com/book/kotlin-in-action/chapter-4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; sealed 클래스&lt;/b&gt;&lt;br /&gt;`sealed` 클래스는 강력한 타입 안전성을 제공하지만, 초기에는 제약이 다소 많았습니다. 예를 들어 모든 하위 클래스가 반드시 중첩 클래스여야 했고, `data class`로 `sealed` 클래스를 상속하는 것도 불가능했습니다.&lt;br /&gt;&lt;br /&gt;그러나 Kotlin 1.1부터 이러한 제약이 완화되었습니다. 이제는 같은 파일 내라면 위치와 관계없이 `sealed` 클래스를 상속한 하위 클래스를 정의할 수 있으며, `data class` 또한 하위 클래스로 선언할 수 있습니다. 이로 인해 `sealed` 클래스는 여전히 컴파일 타임 안전성을 유지하면서도, 실무에서 훨씬 유연하게 활용할 수 있게 되었습니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;뻔하지 않는 생성자와 프로퍼티를 갖는 클래스 선언&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은&amp;nbsp;클래스를&amp;nbsp;선언하는&amp;nbsp;동시에&amp;nbsp;생성자를&amp;nbsp;정의하는&amp;nbsp;매우&amp;nbsp;간결한&amp;nbsp;문법을&amp;nbsp;제공합니다.&amp;nbsp;하지만&amp;nbsp;자바와의&amp;nbsp;호환성이나&amp;nbsp;다양한&amp;nbsp;초기화&amp;nbsp;상황을&amp;nbsp;대응하기&amp;nbsp;위해&amp;nbsp;주&amp;nbsp;생성자와&amp;nbsp;부&amp;nbsp;생성자를&amp;nbsp;구분하여&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 클래스 초기화: 주 생성자와 초기화 블록&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주 생성자는 생성자 파라미터를 지정하고 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 2가지 목적으로 쓰입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767078060496&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User constructor(_nickname: String) { 
    val nickName: String

    // 주 생성자는 코드를 가질 수 없으므로, 초기화 로직이 필요할 때 init 블록을 사용합니다.
    init { 
        nickName = _nickname
        println(&quot;유저가 초기화되었습니다: $nickName&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`init {}` 초기화 블록은 주 생성자와 함께 사용됩니다. 주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 부 생성자: 상위 클래스를 다른 방식으로 초기화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 달리 디폴트 파라미터를 지원하므로 생성자를 여러 개 만들 일이 적습니다. 하지만 다음과 같은 경우에는 부 생성자가 필요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자바 상호운용성:&lt;/b&gt; 여러 생성자를 가진 자바 라이브러리 클래스를 상속받아 확장할 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다양한&amp;nbsp;인자&amp;nbsp;조합:&lt;/b&gt; 인스턴스를 생성하는 방법이 여러 가지인 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767078333075&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class View {
    // 부 생성자 1
    constructor(ctx: Context) { /* ... */ }

    // 부 생성자 2
    constructor(ctx: Context, attr: AttributeSet) { /* ... */ }
}

class MyButton : View {
    // 상위 클래스의 특정 생성자를 호출해야 할 때
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 인터페이스에 선언된 프로퍼티 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 달리 인터페이스에 프로퍼티를 선언할 수 있습니다. 하지만 인터페이스는 본래 상태를 가질 수 없기 때문에 선언된 프로퍼티를 하위 클래스에서 어떻게 구현하느냐에 따라 동적 방식이 달라집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767078616899&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
    val nickName: String
}

class PrivateUser(override val nickname: String) : User // 주 생성자에 있는 프로퍼티

class SubscribingUser(val email: String) : User {
    override val nickname: String // 커스텀 게터
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId) // 프로퍼티 초기화 식
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에 `val nickname: String`이라고 선언하는 것은, 하위 클래스에 &quot;&lt;b&gt;값을&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방법을&amp;nbsp;제공하라&lt;/b&gt;&quot;고&amp;nbsp;강제하는&amp;nbsp;것과&amp;nbsp;같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주 생성자 활용 &lt;/b&gt;(`PrivateUser`)&lt;b&gt;:&lt;/b&gt; 주 생성자 안에서 직접 오버라이드하여 필드에 값을 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 게터 활용 &lt;/b&gt;(`SubscribingUser`)&lt;b&gt;:&lt;/b&gt; 별도의 필드를 만들지 않고, 프로퍼티에 접근할 때마다 로직을 실행하여 결과를 계산합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;초기화 식 활용 &lt;/b&gt;(`FacebookUser`)&lt;b&gt;:&lt;/b&gt;&amp;nbsp;객체가&amp;nbsp;생성되는&amp;nbsp;시점에&amp;nbsp;단&amp;nbsp;한&amp;nbsp;번&amp;nbsp;함수를&amp;nbsp;호출하여&amp;nbsp;그&amp;nbsp;결괏값을&amp;nbsp;필드에&amp;nbsp;저장해&amp;nbsp;둡니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 안에도 직접 게터(` get() `)를 가진 프로퍼티를 정의할 수 있습니다. 단, 인터페이스는 상태를 가질 수 없으므로 &lt;b&gt;뒷받침하는 필드(field)&lt;/b&gt;를 가질 수는 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767078799699&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
    val email: String
    val nickName: String
        get() = email.substringBefore('@') // 매번 호출 시점에 계산
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`email`: 추상 프로퍼티이므로 하위 클래스에서 반드시 구현해야 합니다.&lt;/li&gt;
&lt;li&gt;`nickName`: 인터페이스에 이미 게터 로직이 정의되어 있으므로, 하위 클래스에서 따로 구현하지 않고 그대로 상속받아 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) `get`과 `set`에서 뒷받침하는 필드에 접근&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 저장하는 동시에 로직을 실행할 수 있게 하기 위해서는 접근자 안에서 프로퍼티를 뒷받침하는 필드에 접근할 수 있어야 합니다. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶은 경우 다음과 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767079397050&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val name: String) {
    var address: String = &quot;unspecified&quot;
        set(value: String) {
            // field는 프로퍼티의 실제 저장된 값을 가리킵니다.
            println(&quot;&quot;&quot;
                Address was changed for $name:
                &quot;$field&quot; -&amp;gt; &quot;$value&quot;.&quot;&quot;&quot;.trimIndent())
            
            // 값을 업데이트합니다. 
            // 주의: 여기서 'address = value'라고 쓰면 다시 세터를 호출하여 무한 루프가 걸린다.
            field = value 
        }
}

&amp;gt;&amp;gt;&amp;gt; val u = User(&quot;alice&quot;)
&amp;gt;&amp;gt;&amp;gt; u.address = &quot;city 11-12&quot;
Address was changed for alice:
&quot;unspecified&quot; -&amp;gt; &quot;city 11-12&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 접근자의 가시성 변경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 프로퍼티의 값을 읽는 것은 자유롭게 허용하되, 수정은 클래스 내부에서만 하고 싶을 때가 있습니다. 이때 `private set`을 활용하면 아주 깔끔하게 캡슐화를 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767079452272&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LengthCounter {
    var counter: Int = 0
        private set // 외부에서는 수정 불가 (읽기 전용처럼 보임)

    fun addWord(word: String) {
        counter += word.length // 내부에서는 수정 가능
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서는 `counter`를 조회할 수만 있고, 값의 조작은 반드시 `addWord`라는 검증된 메서드를 통해서만 이루어지도록 강제할 수 있습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코틀린은 초기화 시점을 유연하게 관리할 수 있는 기능들도 제공합니다.&lt;br /&gt;&lt;br /&gt;- &lt;b&gt;lateinit:&lt;/b&gt; 널이 될 수 없는(non-null) 프로퍼티를 생성자에서 초기화하지 않고 나중에(예: Dependency Injection, Setup 메서드) 초기화하겠다고 선언할 때 사용합니다.&lt;br /&gt;&lt;br /&gt;- &lt;b&gt;지연 초기화(by lazy):&lt;/b&gt; 프로퍼티가 처음 사용되는 시점에 딱 한 번 초기화 로직을 실행합니다. 이는 더 넓은 개념인 위임 프로퍼티(Delegated Property)의 한 종류입니다.&lt;br /&gt;&lt;br /&gt;- &lt;b&gt;자바 호환성:&lt;/b&gt; @JvmField, @JvmStatic 등의 애노테이션을 사용하여 코틀린 프로퍼티가 자바에서 일반 필드처럼 보이게 에뮬레이션 할 수 있습니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컴파일러가 생성한 메서드: 데이터 클래스와 클래스 위임&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 객체의 비교나 출력을 위해 `equals`, `hashCode`, `toString` 같은 메서드를 매번 구현해야 했습니다. 코틀린은 이러한 '보일러플레이트(반복적이고 번거로운 코드)'를 컴파일러가 대신 생성해 주는 강력한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 데이터 클래스 (`Data Class`)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 클래스 앞에 `data` 변경자를 붙이면 컴파일러가 `equals`, `hashCode`, `toString` 메서드를 자동으로 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767080101169&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Client(val name: String, val postalCode: Int)

fun main() {
    val c1 = Client(&quot;alice&quot;, 100)
    val c2 = Client(&quot;bella&quot;, 100)

    println(c1.name == c2.name) // false (String 비교)
    println(c1.hashCode() == c2.hashCode()) // false (hashCode)
    println(c1.toString()) // Client(name=alice, postalCode=100)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 불변성과 `copy()` 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 데이터 클래스의 프로퍼티를 `val`로 선언하여 불변(Immutable) 객체로 만드는 것을 권장합니다. 특히 객체를 `HashMap`의 키로 쓸 때 데이터가 변하면 데이터 무결성이 깨지기 때문입니다.&lt;br /&gt;&lt;br /&gt;이때 불변 객체의 내용을 일부만 바꿔서 새로 만들고 싶다면 `copy()` 메서드를 활용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767080188016&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val bob = Client(&quot;Bob&quot;, 110)
// name은 유지하고 postalCode만 바꾼 복사본 생성
val olderBob = bob.copy(postalCode = 220)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본&amp;nbsp;객체는&amp;nbsp;그대로&amp;nbsp;유지되므로&amp;nbsp;프로그램의&amp;nbsp;다른&amp;nbsp;부분에&amp;nbsp;영향을&amp;nbsp;주지&amp;nbsp;않아&amp;nbsp;안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 클래스 위임(Class Delegation): `by` 키워드의 마법  &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을&amp;nbsp;허용하지&amp;nbsp;않는(final)&amp;nbsp;클래스에&amp;nbsp;새로운&amp;nbsp;기능을&amp;nbsp;추가하고&amp;nbsp;싶을&amp;nbsp;때&amp;nbsp;보통&amp;nbsp;데코레이터(Decorator)&amp;nbsp;패턴을&amp;nbsp;사용합니다.&amp;nbsp;하지만&amp;nbsp;이&amp;nbsp;패턴은&amp;nbsp;기존&amp;nbsp;클래스의&amp;nbsp;모든&amp;nbsp;기능을&amp;nbsp;다시&amp;nbsp;작성해야&amp;nbsp;하는&amp;nbsp;번거로운&amp;nbsp;준비&amp;nbsp;코드가&amp;nbsp;필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767080352897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 수동으로 기능을 전달하는 방식
class DelegatingCollection&amp;lt;T&amp;gt;(
    private val innerList: Collection&amp;lt;T&amp;gt; = arrayListOf&amp;lt;T&amp;gt;()
) : Collection&amp;lt;T&amp;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&amp;lt;T&amp;gt; = innerList.iterator()
    override fun containsAll(elements: Collection&amp;lt;T&amp;gt;): Boolean = innerList.containsAll(elements)
}

fun main() {
    val list = listOf(&quot;Kotlin&quot;, &quot;Java&quot;)
    val delegating = DelegatingCollection(list)
    
    println(&quot;Size: ${delegating.size}&quot;) // 결과: Size: 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;코틀린은 `by` 키워드를 통해 이 위임 로직을 단 한 줄로 해결합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767080456850&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클래스 위임(by)을 활용한 기능 확장
class CountingSet&amp;lt;T&amp;gt;(
    private val innerSet: MutableCollection&amp;lt;T&amp;gt; = HashSet&amp;lt;T&amp;gt;()
) : MutableCollection&amp;lt;T&amp;gt; by innerSet { // MutableCollection의 모든 메서드 구현을 innerSet에 맡김

    var objectsAdded = 0 // 새롭게 추가된 요소의 총 개수를 저장하는 상태

    // 요소 하나를 추가할 때 카운트 증가 로직만 재정의(Override)
    override fun add(element: T): Boolean {
        objectsAdded++
        return innerSet.add(element)
    }

    // 여러 요소를 한꺼번에 추가할 때 카운트 증가 로직만 재정의
    override fun addAll(c: Collection&amp;lt;T&amp;gt;): Boolean {
        objectsAdded += c.size
        return innerSet.addAll(c)
    }
    
    // 나머지 size, isEmpty, remove, contains 등의 메서드는 작성하지 않아도 
    // 자동으로 innerSet의 기능을 그대로 사용함
}

fun main() {
    val cset = CountingSet&amp;lt;Int&amp;gt;()
    
    cset.add(1)
    cset.addAll(listOf(2, 3, 4))
    
    println(&quot;${cset.objectsAdded} objects were added, ${cset.size} remain in the set.&quot;)
    // 출력 결과: 4 objects were added, 4 remain in the set.
    
    // 위임된 메서드(remove)를 사용해봄
    cset.remove(1)
    println(&quot;After removal - Size: ${cset.size}, Total Added: ${cset.objectsAdded}&quot;)
    // 출력 결과: After removal - Size: 3, Total Added: 4
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`MutableCollection` 인터페이스의 수많은 메서드들(`size`, `isEmpty`, `contains` 등)을 직접 구현할 필요가 없습니다. `by innerSet`이라고 명시하면, 컴파일러가 나머지 모든 메서드를 `innerSet`의 메서드를 호출하도록 자동으로 연결해 줍니다.&lt;br /&gt;&lt;br /&gt;즉, 상속의 위험성(기반 클래스 변경에 따른 취약점)은 피하면서, 필요한 기능만 깔끔하게 확장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;`object` 키워드: 클래스 선언과 인스턴스 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 `object` 키워드는 &quot;클래스를 정의하면서 동시에 단 하나의 인스턴스(객체)를 생성&quot;할 때 사용합니다. 자바의 싱글턴 패턴, 정적(static) 메서드, 익명 클래스를 코틀린만의 방식으로 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 객체 선언(Object Declaration): 완벽한 싱글톤&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 보통 클래스의 생성자를 `priavte`으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글톤 패턴을 통해 이를 구현합니다. 반면에 코틀린은 `object` 키워드를 통해 싱글톤 객체를 생성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767080984644&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object DatabaseConfig {
    val url = &quot;jdbc:mysql://localhost:3306/db&quot;
    fun connect() = println(&quot;$url 에 연결합니다.&quot;)
}

// 사용 시점: 클래스 이름처럼 접근하면 이미 생성된 단일 객체를 참조함
fun main() {
    DatabaseConfig.connect() 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 클래스 내부에만 밀접하게 연관된 싱글톤 객체가 필요하다면 클래스 안에 선언할 수도 있습니다. 이 경우에도 해당 객체의 인스턴스는 단 하나뿐입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767081124961&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Person(val name: String) {
    // Person 클래스 내부에 이름 비교를 위한 전용 컴퍼레이터 선언
    object NameComparator : Comparator&amp;lt;Person&amp;gt; {
        override fun compare(p1: Person, p2: Person): Int =
            p1.name.compareTo(p2.name)
    }
}

fun main() {
    val persons = listOf(Person(&quot;Bob&quot;), Person(&quot;Alice&quot;))
    println(persons.sortedWith(Person.NameComparator)) // '클래스명.객체명'으로 접근
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 동반 객체: 팩토리 메서드와 정적 멤버가 들어갈 장소&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린을 처음 접하면 당황스러운 점 중 하나가 클래스 내부에 `static` 키워드가 없다는 것입니다. 코틀린은 전역적인 상태를 지양하기 위해 자바의 `static`을 없앴지만, 그 역할을 더 안전하고 구조적으로 대체하기 위해 &lt;b&gt;동반 객체(Companion Object)를 도입했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-1) 동반&amp;nbsp;객체란&amp;nbsp;무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 내부에 정의된 객체 앞에 `companion`이라는 키워드를 붙이면 해당 클래스의 동반 객체가 됩니다. 이 객체의 프로퍼티나 메서드는 클래스 인스턴스 생성 없이 클래스 이름을 통해 직접 접근할 수 있어, 자바의 정적 멤버와 유사하게 동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767081258967&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class A {
    companion object {
        fun bar() {
            println(&quot;Companion object called&quot;)
        }
    }
}

fun main() {
    A.bar() // 클래스 A의 인스턴스를 만들지 않고도 호출 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-2)   왜 동반 객체를 사용하는가? (팩토리 메서드)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동반 객체의 가장 강력한 용도는 팩토리 메서드(Factory Method)를 만드는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767081313591&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User private constructor(val nickname: String) { // 주 생성자를 private으로 보호
    companion object {
        // 이메일을 통해 유저를 생성하는 팩토리 메소드
        fun newSubscribingUser(email: String) =
            User(email.substringBefore('@'))

        // 페이스북 ID를 통해 유저를 생성하는 팩토리 메소드
        fun newFacebookUser(accountId: Int) =
            User(getFacebookName(accountId))
    }
}

fun main() {
    // 생성자 대신 목적이 분명한 메소드 이름을 사용하므로 가독성이 높아짐
    val subscribingUser = User.newSubscribingUser(&quot;bob@gmail.com&quot;)
    val facebookUser = User.newFacebookUser(4)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-3) 자바 `static`과의 결정적 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 `static` 멤버는 클래스에 속한 정적인 데이터일 뿐이지만, 코틀린의 동반 객체는 실제 객체입니다. 따라서 다음과 같은 차별화된 기능을 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터페이스 구현:&lt;/b&gt; 동반 객체도 인터페이스를 상속받아 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장&amp;nbsp;함수:&lt;/b&gt;&amp;nbsp;클래스&amp;nbsp;외부에서&amp;nbsp;동반&amp;nbsp;객체에&amp;nbsp;대한&amp;nbsp;확장&amp;nbsp;함수를&amp;nbsp;정의하여&amp;nbsp;기능을&amp;nbsp;덧붙일&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 동반&amp;nbsp;객체를&amp;nbsp;일반&amp;nbsp;객체처럼&amp;nbsp;사용 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동반 객체는 클래스 내부에 정의되는 일반 객체이므로, 이름을 붙일 수 있고 인터페이스를 구현할 수 있으며, 그 안에 확장 함수와 프로퍼티를 정의할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767144938023&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(val name: String) {
    companion object Parser { // &quot;Parser&quot; 동반 객체 이름
        fun fromJSON(jsonText: String) : Person = ...
    }
}

fun main() {
    val p1 = Person.Parser.fromJSON(&quot;{name: 'bella'}&quot;)
    val p2 = Person.fromJSON(&quot;{name: 'alice'}&quot;) // 생략도 가능
    println(p1.name) // bella
    println(p2.name) // alice
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 객체 선언과 마찬가지로 동반 객체로 인터페이스를 구현할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767145105671&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface JsonFactory&amp;lt;T&amp;gt; {
    fun fromJson(jsonText: String): T
}
class Person(val name: String) {
    companion object : JsonFactory&amp;lt;Person&amp;gt; {
        // 동반 객체의 인터페이스 구현
        override fun fromJson(jsonText: String): Person { ... }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 JSON으로부터 각 원소를 다시 만들어내는 추상 팩토리가 있다면 `Person` 객체를 해당 팩토리에 넘길 수 있습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt;코틀린 동반 객체와 정적 멤버&lt;/b&gt;&lt;br /&gt;코틀린에서 클래스와 동반 객체는 컴파일 시 클래스에 포함된 정적 필드로 변환되며, 일반 객체와 유사한 방식으로 취급됩니다. 동반 객체에 이름을 붙이지 않은 경우, 자바 코드에서는 `Companion`이라는 이름을 통해 해당 객체를 접근할 수 있습니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 객체 식: 무명 내부 클래스를 다른 방식으로 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무명 객체(anonymous object)를 정의할 때도 `object` 키워드를 씁니다. 자바의&amp;nbsp;익명&amp;nbsp;내부&amp;nbsp;클래스와&amp;nbsp;유사하지만,&amp;nbsp;코틀린의&amp;nbsp;무명&amp;nbsp;객체는&amp;nbsp;훨씬&amp;nbsp;더&amp;nbsp;자유롭고&amp;nbsp;강력한&amp;nbsp;기능을&amp;nbsp;제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4-1) 무명 객체란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 없는 객체를 선언과 동시에 생성하는 방식입니다. 주로 인터페이스의 구현체나 특정 클래스를 확장한 객체가 일회성으로 필요할 때 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767145469473&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MouseAdapter를 상속하는 무명 객체를 생성하여 전달
window.addMouseListener(
    object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) { ... }
        override fun mouseEntered(e: MouseEvent) { ... }
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 생성한 무명 객체를 재사용하고 싶다면 변수에 담아 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767145788085&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val listener = object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }
    override fun mouseEntered(e: MouseEvent) { ... }
}

window.addMouseListener(listener) // 무명 객체 인자로 전달&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4-2) 코틀린 무명 객체의 차별점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 익명 클래스는 단 하나의 클래스를 상속하거나 하나의 인터페이스만 구현할 수 있습니다. 하지만 &lt;b&gt;코틀린의 무명 객체를 여러 개의 인터페이스를 동시에 구현&lt;/b&gt;하거나, &lt;b&gt;클래스 상속과 인터페이스 구현을 한 번에 처리&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767145945148&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클래스 상속과 인터페이스 구현을 동시에 하는 무명 객체
val complexObject = object : MyBaseClass(), Runnable, Serializable {
    override fun run() {
        println(&quot;다중 구현된 무명 객체가 실행 중입니다.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 익명 클래스 외부의 변수를 사용하려면 그 변수가 `final`이어야만 했습니다. 그러나 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;코틀린의 객체 식 안에서는 `final`이 아닌 변수도 자유롭게 접근하고 수정할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767146015719&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun countClicks(window: Window) {
    var clickCount = 0 // final이 아님

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++ // 외부 변수의 값을 직접 수정 가능!
        }
        override fun mouseEntered(e: MouseEvent) { ... }
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무명 객체는 람다(Lamda)와 혼동될 수 있습니다. 만약 구현해야 할 메서드가 단 하나뿐인 인터페이스(SAM)라면 무명 객체 대신 람다를 쓰는 것이 훨씬 간결해집니다. 하지만 위 예제처럼 여러 메서드를 오버라이드해야 한다면 반드시 `object` 식을 사용해야 합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.3333%; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.4961%; text-align: center;&quot;&gt;&lt;b&gt;형태&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 47.1705%; text-align: center;&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.3333%;&quot;&gt;객체 선언&lt;/td&gt;
&lt;td style=&quot;width: 29.4961%;&quot;&gt;`object Name { ... }`&lt;/td&gt;
&lt;td style=&quot;width: 47.1705%;&quot;&gt;클래스 전체에서 단 하나의 싱글톤&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.3333%;&quot;&gt;동반 객체&lt;/td&gt;
&lt;td style=&quot;width: 29.4961%;&quot;&gt;`companion object { ... }`&lt;/td&gt;
&lt;td style=&quot;width: 47.1705%;&quot;&gt;클래스 당 하나, 정적 멤버/팩토리 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.3333%;&quot;&gt;무명 객체&lt;/td&gt;
&lt;td style=&quot;width: 29.4961%;&quot;&gt;`object : Type { ... }`&lt;/td&gt;
&lt;td style=&quot;width: 47.1705%;&quot;&gt;일회성 인스턴스, 다중 구현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린의&amp;nbsp;인터페이스는&amp;nbsp;자바&amp;nbsp;인터페이스와&amp;nbsp;유사하지만,&amp;nbsp;자바와&amp;nbsp;달리&amp;nbsp;프로퍼티&amp;nbsp;선언을&amp;nbsp;포함할&amp;nbsp;수&amp;nbsp;있다.&lt;/li&gt;
&lt;li&gt;코틀린의 선언은 기본적으로 `final`이며 `public`이다.&lt;/li&gt;
&lt;li&gt;선언이 `final`이 되지 않으려면(상속과 오버라이딩이 가능) 앞에 `open`을 붙여야 한다.&lt;/li&gt;
&lt;li&gt;코틀린의 중첩 클래스는 기본적으로 내부 클래스가 아니다. 바깥쪽 클래스에 대해 참조를 중첩 클래스 안에 포함시키려면 `inner` 키워드를 중첩 클래스 선언 앞에 붙여야 내부 클래스가 된다.&lt;/li&gt;
&lt;li&gt;`sealed class`를 상속하는 클래스를 정의하려면 반드시 부모 클래스 정의 안에 중첩(또는 내부) 클래스로 정의되어야 한다. (코틀린 1.1부터 같은 파일에만 있으면 되도록 개선되었다.)&lt;/li&gt;
&lt;li&gt;초기화 블록(`init {}`)과 부 생성자를 활용해 인스턴스를 더 유연하게 초기화할 수 있다.&lt;/li&gt;
&lt;li&gt;`data class`를 사용하면 `equals`, `hashCode`, `toString`, `copy` 등 메서드를 자동으로 생성해 준다.&lt;/li&gt;
&lt;li&gt;클래스 위임(`by`)을 사용하면 객체 위임 기반 설계를 구현할 때 불필요한 준비 코드를 크게 줄일 수 있다.&lt;/li&gt;
&lt;li&gt;객체 선언(`object`)을 사용하면 싱글톤 클래스를 쉽게 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;동반 객체(`comanion object`)는 자바의 정적 메서드와 필드 정의를 대신한다.&lt;/li&gt;
&lt;li&gt;동반 객체도 다른 객체와 마찬가지로 인터페이스를 구현할 수 있다. 외부에서 동반 객체에 대한 확장 함수와 프로퍼티를 정의할 수 있다.&lt;/li&gt;
&lt;li&gt;코틀린의 객체 식은 자바의 무명 내부 클래스를 대신한다. 또한 여러 인스턴스를 구현하거나 객체가 포함된 영역(scope)에 있는 변수의 값을 변경할 수 있는 등 자바 무명 클래스보다 더 많은 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <category>by</category>
      <category>Kotlin</category>
      <category>kotlin in action</category>
      <category>Sealed</category>
      <category>객체</category>
      <category>동반 객체</category>
      <category>위임</category>
      <category>인터페이스</category>
      <category>코틀린</category>
      <category>클래스</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/66</guid>
      <comments>https://dev-hui.tistory.com/66#entry66comment</comments>
      <pubDate>Tue, 30 Dec 2025 13:49:29 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in Action 2/e] 함수 정의와 호출</title>
      <link>https://dev-hui.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin&amp;nbsp;in&amp;nbsp;Action&amp;nbsp;2/e&amp;nbsp;서적을&amp;nbsp;읽으면서&amp;nbsp;정리하고자&amp;nbsp;쓴&amp;nbsp;글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAMFC1/dJMcai2Yz8V/pH5eX83jgCl1Z8fDBZ4TcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAMFC1/dJMcai2Yz8V/pH5eX83jgCl1Z8fDBZ4TcK/img.png&quot; data-alt=&quot;kotlin logo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAMFC1/dJMcai2Yz8V/pH5eX83jgCl1Z8fDBZ4TcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAMFC1%2FdJMcai2Yz8V%2FpH5eX83jgCl1Z8fDBZ4TcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코틀린에서 컬렉션 만들기&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 다음과 같이 컬렉션을 생성하는 함수를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 코틀린은 표준 자바 컬렉션 클래스를 사용하기 때문에 자바 코드와 상호작용하기가 훨씬 더 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자바 &amp;rarr; 코틀린 또는 코틀린 &amp;rarr; 자바 함수를 호출할 때 컬렉션을 서로 변환할 필요가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767022078525&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val set = setOf(1, 7, 10)
    val list = listOf(1, 10, 100)
    val map = mapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 10 to &quot;ten&quot;) // ex) key=1, value=&quot;one&quot;
    
    println(set.javaClass) // class java.util.LinkedHashSet
    println(list.javaClass) // class java.util.Arrays$ArrayList
    println(map.javaClass) // class java.util.LinkedHashMap
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 이름 붙인 인자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 함수를 보면 인자로 전달하는 값이 어떤 역할을 하는지 구분하기 어렵습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767022626861&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;joinToString(collection, &quot; &quot;, &quot; &quot;, =&quot;.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 이를 해결하기 위해 인자의 이름을 명시할 수 있는 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 인자 순서를 변경할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767022774936&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;joinToString(
    prefix = &quot; &quot;,
    postfic = &quot;.&quot;,
    separator = &quot; &quot;, 
    collection = collection
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 디폴트 파라미터 값&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의&amp;nbsp;경우&amp;nbsp;여러&amp;nbsp;파라미터를&amp;nbsp;받기&amp;nbsp;위해서는&amp;nbsp;`overloaing`한&amp;nbsp;메서드가&amp;nbsp;많아진다는&amp;nbsp;문제를&amp;nbsp;가지고&amp;nbsp;있습니다. &lt;br /&gt;그러나 코틀린은 함수 선언 시 파라미터의 디폴트 값을 지정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767059775877&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; joinToString(
    collection: Collection&amp;lt;T&amp;gt;,
    separator: String = &quot;,&quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String { ... }

fun main() {
    val list = listOf(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;)
    joinToString(list) // 이외 다른 파라미터를 받지 않아도 디폴트 파라미터 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 특정 클래스에 속하지 않는 공통 로직을 처리하기 위해 자바처럼 무의미한 클래스를 만들 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 수준에 함수와 프로퍼티를 직접 선언합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 88px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px; text-align: center;&quot;&gt;&lt;b&gt;비고&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;최상위 함수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;클래스 밖에 최상위에 선언된 함수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;자바의 `static` 메서드로 컴파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;최상위 프로퍼티&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;클래스 밖 최상위에 선언된 변수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;자바의 `static` 필드로 컴파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;`const` 상수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;`const val`로 선언하며, 컴파일 타임 상수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 22px;&quot;&gt;원시 타입과 `String`만 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일의 최상위 수준에서 선언한다면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767060566022&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.utils

// 최상위 프로퍼티: 상수 선언
const val UNIX_LINE_SEPARATOR = &quot;\n&quot;

// 최상위 프로퍼티: 일반 변수 (게터가 생성됨)
var opCount = 0

// 최상위 함수: 유틸리티 함수
fun joinToString(items: List&amp;lt;String&amp;gt;): String {
    opCount++
    return items.joinToString(separator = UNIX_LINE_SEPARATOR)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 코틀린 및 자바에서 사용 예시입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767060705223&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Kotlin
fun main() {
    val list = listOf(&quot;Kotlin&quot;, &quot;Java&quot;)
    println(joinToString(list)) // 클래스 이름 없이 직접 호출합니다.
}

// Java
class Main {
    public static void main(String[] args) {
    	List&amp;lt;String&amp;gt; list = List.of(&quot;Kotlin&quot;, &quot;Java&quot;);
    	String result = StringUtilsKt.joinToString(list);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 다음과 같은 특징을 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가독성 향상:&lt;/b&gt; 불필요한 클래스 선언(ex: `class StringUtils { private ...() }`)가 사라져 코드가 훨씬 간결해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일 구조:&lt;/b&gt; 코틀린 파일은 내부적으로 자바 클래스로 변환되므로 자바와의 상호 운용성이 완벽하게 유지됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상수 선언의 적절성:&lt;/b&gt; 실행 시점에 결정되는 값은 `val`을 사용하고, 컴파일 시점 완전히 고정되는 값은 `const val`을 사용하여 인라인 최적화를 유도해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메서드를 다른 클래스에 추가: 확장 함수와 프로퍼티  &lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;코틀린의 확장은 기존 크래스의 소스코드를 수정하지 않고도 새로운 메서드나 프로퍼티를 추가할 수 있게 해주는 매우 강력한 기능입니다.&lt;/b&gt;&lt;/span&gt; 이는 자바의 외부 라이브러리나 표준 API를 코틀린 스타일로 확장할 때 핵심적인 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 확장 함수 (Extension Functions)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 클래스 밖에 선언된 함수를 말합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767061443544&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// String: 수신 객체 타입 (Receiver Type)
// this: 수신 객체 (Receiver Object)
fun String.lastChar(): Char = this.get(this.length - 1)

println(&quot;Kotlin&quot;.lastChar()) // n (String 클래스를 건들지 않고 확장할 수 있다.)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;수신 객체 타입:&lt;/b&gt; 확장이 정의될 클래스의 이름입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수신 객체:&lt;/b&gt; 확장 함수가 호출되는 실제 객체 인스턴스입니다. 함수 내부에서 `this` 키워드를 통해 접근할 수 있으며, `this`를 생략하고 멤버에 접근하는 것도 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수는 클래스 내부에서 선언된 것이 아니므로, 클래스의 `private`이나 `protected` 멤버에는 접근할 수 없습니다. 또한 확장 함수를 사용하기 위해 반드시 `import`를 해야합니다. 이름이 충돌할 경우 as 키워드를 사용하여 이름을 변경할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767061685345&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import strings.lastChar as last // last로 이름 변경
val c = &quot;Kotlin&quot;.last()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 자바에서 확장 함수 호출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 확장 함수는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;수신 객체를 첫 번째 인자로 받는 정적 메서드&lt;/b&gt;&lt;/span&gt;로 컴파일됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비용 없음:&lt;/b&gt; 실행 시점에 부가적인 객체 생성이나 오버헤드가 발생하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자바에서 호출:&lt;/b&gt; 자바에서는 해당 확장 함수가 포함된 파일 이름 뒤 `Kt`가 붙은 클래스를 통해 정적 메서드로 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767061796694&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java 코드
char c = StringUtilsKt.lastChar(&quot;Java&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 확장 함수로 유틸리티 함수 정의&lt;/h3&gt;
&lt;pre id=&quot;code_1767061945539&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; Collection&amp;lt;T&amp;gt;.joinToString( // Collection&amp;lt;T&amp;gt;에 대한 확장 함수 선언
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
	val result = StringBuilder(prefix)

    // this는 수신 객체. 여기서는 T 타입의 원소로 이루어진 Collection
    for ((index, element) in this.withIndex()) {
        if (index &amp;gt; 0) result.append(separator)
        result.append(element)
    }

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

fun main(args: Array&amp;lt;String&amp;gt;) {
    val list = arrayListOf(1, 2, 3)
    println(list.joinToString(&quot; &quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소로&amp;nbsp;이뤄진&amp;nbsp;컬렉션에&amp;nbsp;대한&amp;nbsp;확장&amp;nbsp;함수를&amp;nbsp;만들고,&amp;nbsp;모든&amp;nbsp;인자에&amp;nbsp;대한&amp;nbsp;디폴트&amp;nbsp;값을&amp;nbsp;지정하였습니다.&amp;nbsp;`joinToString`을&amp;nbsp;마치&amp;nbsp;클래스의&amp;nbsp;멤버인&amp;nbsp;것처럼&amp;nbsp;호출할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 확장 함수는 오버라이드할 수 없습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수는 클래스의 멤버가 아니며, &lt;b&gt;정적(Static) 메서드&lt;/b&gt;와 동일한 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적인 객체지향의 다형성이 적용되지 않습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;멤버 메서드 (`Override` 가능)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;확장 함수 (`Override` 불가)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;결정 시점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;실행 시점 (Runtime)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;컴파일 시점 (Compile-time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;호출 기준&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;객체의 실제 인스턴스 타입&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;변수에 선언된 타입&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수는 변수의 정적 타입(컴파일 시점의 타입)에 의해 호출될 함수가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 실제 동적 타입(런타임 타입)에 의해 결정되지 않음을 유의해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 확장 프로퍼티&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 클래스에 프로퍼티 문법(점 표기법)으로 접근할 수 있는 기능을 추가할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767062380094&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val String.lastChar: Char
    get() = get(length - 1) // backing field가 없으므로 게터 정의 필수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 프로퍼티는 실제로 필드를 가질 수 없으므로, 상태를 저장할 방법이 없습니다. 즉, 초기화 코드(`init { ... }`)를 쓸 수 없으며 오직 `getter`나 `setter`만 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컬렉션 처리: 가변 인자, 중위 함수 호출, 구조 분해 선언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바의 컬렉션 API를 그대로 사용하면서도, 확장 함수를 통해 훨씬 편리한 사용성을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 가변 인자 함수: `varage` 키워드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드를 호출할 때 인자의 개수를 자유롭게 조절할 수 있다면 `vararg` 변경자를 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선언 방식:&lt;/b&gt; 자바의 타입 뒤 `...` 대신, 코틀린은 파라미터 앞에 `vararg`를 붙입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프레드 연산자 (`*`):&lt;/b&gt; 이미 존재하는 배열을 가변 인자로 넘길 때는 배열 앞에 별표(`*`)를 붙여야 합니다. 이를 통해 배열의 각 원소가 인자로 하나씩 펼쳐져서 전달됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767066840149&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// args = {&quot;two&quot;, &quot;eight&quot;}
fun main(args: Array&amp;lt;String&amp;gt;) {
    val list = listOf(&quot;one&quot;, &quot;two&quot;, &quot;eight&quot;) // 가변 인자를 받는 listOf 함수
    val combinedList = listOf(&quot;one&quot;, *args) // 배열 앞에 *를 붙여 스프레드(펼침) 처리
    
    println(list) // [&quot;one&quot;, &quot;two&quot;, &quot;eight&quot;]
    println(combinedList) // [&quot;one&quot;, &quot;two&quot;, &quot;eight&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 중위 호출(Infix call) 및 구조 분해 선언&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자가 하나뿐인 메서드는 `infix` 변경자를 붙여 중위 호출 방식으로 사용할 수 있습니다. 수신 객체와 인자 사이에 메서드 이름을 넣어 가독성을 높입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767067237056&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(val name: String) {
    // infix 키워드를 붙여 중위 함수로 정의
    infix fun likes(other: Person) {
        println(&quot;${this.name}은(는) ${other.name}을(를) 좋아합니다.&quot;)
    }
}

fun main() {
    val alice = Person(&quot;Alice&quot;)
    val bob = Person(&quot;Bob&quot;)

    // 일반적인 호출 방식
    alice.likes(bob)

    // 중위 호출 방식 (마치 문장처럼 읽힘)
    alice likes bob 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`to`&amp;nbsp;함수는&amp;nbsp;두&amp;nbsp;원소로&amp;nbsp;이뤄어진&amp;nbsp;순서쌍&amp;nbsp;`Pair`&amp;nbsp;객체를&amp;nbsp;반환합니다.&amp;nbsp;코틀린은&amp;nbsp;`Pair`와&amp;nbsp;같은&amp;nbsp;객체의&amp;nbsp;내용을&amp;nbsp;여러&amp;nbsp;변수에&amp;nbsp;나누어&amp;nbsp;담는&amp;nbsp;&lt;b&gt;구조&amp;nbsp;분해&amp;nbsp;선언&lt;/b&gt;을&amp;nbsp;지원합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767066951034&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1.to(&quot;one&quot;) // 일반적인 호출 방식
1 to &quot;one&quot; // 중위 호출 방식 (객체 이름 인자 순서로 공백을 두어 작성)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767067066952&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pair의 내용을 즉시 두 변수에 나누어 담습니다.
val (number, name) = 1 to &quot;one&quot;

println(number) // 1
println(name) // &quot;one&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`to` 함수는 확장 함수로 `to`를 사용하면 타입과 관계없이 임의의 순서쌍을 만들 수 있습니다. 이는 `to`의 수신 객체가 제네릭하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 다듬기: 로컬 함수와 확장&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자들은 좋은 코드의 중요한 특징 중 하나로 &lt;b&gt;중복이 없는 코드&lt;/b&gt;를 꼽으며, 이를 &lt;b&gt;DRY(Don't Repeat Yourself) 원칙&lt;/b&gt;이라 부릅니다. 하지만 자바에서 이 원칙을 지키기란 쉽지 않습니다. 보통 긴 메서드를 분리하기 위해 메서드 추출 리팩토링을 적용하지만, 그 결과 클래스 안에 작은 메서드가 늘어나면서 코드 흐름을 파악하기 어려워지는 문제가 생길 수 있습니다. 리팩토링을 진행하여 추출한 메서드를 별도 내부 클래스에 넣으면 코드를 깔금하게 조작할 수 있지만, 그에 따른 불필요한 코드가 늘어나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코틀린은 함수에 추출한 함수를 원 함수 내부에 중첩시킬 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제는 사용자를 저장하기 전 검증 로직입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767068239652&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            &quot;Can't save user ${user.id}: empty Name&quot;)
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            &quot;Can't save user ${user.id}: empty Address&quot;)
    }
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 중복되는 검증 로직을 하나의 함수로 개선한다면 다음과 같이 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767068371916&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                &quot;Can't save user ${user.id}: empty ${fieldName}&quot;)
        }
    }

    validate(user.name, &quot;Name&quot;)
    validate(user.address, &quot;Address&quot;)
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 확장 함수 기능이 있습니다. 이를 통해 검증 로직을 확장 함수로 만들어 좀 더 개선할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767068460762&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
               &quot;Can't save user $id: empty $fieldName&quot;)
        }
    }
    validate(name, &quot;Name&quot;)
    validate(address, &quot;Address&quot;)
}

fun saveUser(user: User) {
    user.validateBeforeSave()
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린은 자체 컬렉션 클래스를 정의하지 않고 자바 클래스를 확장하여 더 풍부한 API를 제공한다.&lt;/li&gt;
&lt;li&gt;함수 파라미터의 디폴트 값을 정의하면 `overloading` 함수의 필요성이 줄어든다.&lt;/li&gt;
&lt;li&gt;코틀린은 파일에서 클래스 멤버가 아닌 최상위 함수와 프로퍼티를 직접 선언할 수 있다.&lt;/li&gt;
&lt;li&gt;확장 함수와 프로퍼티를 사용하면 외부 라이브러리에 정의된 클래스를 포함해 모든 클래스의 API를 해당 클래스의 코드를 바꿀 필요없이 유연하게 확장할 수 있다. 확장 함수를 사용해도 실행 시점에 부가 비용이 들지 않는다.&lt;/li&gt;
&lt;li&gt;중위 호출을 통해 인자가 하나 밖에 없는 경우 더 깔끔한 구문을 호출할 수 있다.&lt;/li&gt;
&lt;li&gt;로컬 함수를 써서 코드를 더 깔끔하게 유지하면서 중복을 제거할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kotlin</category>
      <category>Kotlin</category>
      <category>kotlin in action</category>
      <category>TO</category>
      <category>구조 분해 선언</category>
      <category>디폴트 파라미터</category>
      <category>문법</category>
      <category>코틀린</category>
      <category>함수 정의와 호출</category>
      <category>확장 함수</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/65</guid>
      <comments>https://dev-hui.tistory.com/65#entry65comment</comments>
      <pubDate>Tue, 30 Dec 2025 00:40:25 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in Action 2/e] 코틀린의 기초</title>
      <link>https://dev-hui.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin&amp;nbsp;in&amp;nbsp;Action&amp;nbsp;2/e&amp;nbsp;서적을&amp;nbsp;읽으면서&amp;nbsp;정리하고자&amp;nbsp;쓴&amp;nbsp;글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NHLNS/dJMcagqu9c6/yXKf2LK6wiQ2TKVkx1n2qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NHLNS/dJMcagqu9c6/yXKf2LK6wiQ2TKVkx1n2qk/img.png&quot; data-alt=&quot;kotlin logo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NHLNS/dJMcagqu9c6/yXKf2LK6wiQ2TKVkx1n2qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNHLNS%2FdJMcagqu9c6%2FyXKf2LK6wiQ2TKVkx1n2qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수와 변수(Variables)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java, Kotlin 각각 `Hello, world` 출력 함수를 출력하면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765519182015&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
public class Main {
    public static void main(String[] args) {
    	System.out.println(&quot;Hello, world&quot;);
    }
}

// Kotlin
fun main(args: Array&amp;lt;String&amp;gt;) {
    println(&quot;Hello, world&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 선언을 할 때 `fun` (파라미터 이름: 파라미터 타입) 키워드를 사용합니다.&lt;/li&gt;
&lt;li&gt;Java의 경우 메서드를 호출하기 위해 클래스를 선언 후 작성해야 하지만 &lt;b&gt;Kotlin의 함수는 클래스 생성 없이 파일 내 최상위 수준에 위치할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Java와 달리 배열 처리를 위한 문법이 따로 존재하지 않습니다. &lt;b&gt;Kotlin에서 배열은 일반적인 클래스와 동일하게 사용됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;`System.out.println ` 대신 `println`을 사용합니다. Java 표준 라이브러리 함수를 간결하게 사용할 수 있도록 ` wrapper `를 제공합니다.&lt;/li&gt;
&lt;li&gt;세미콜론(`;`)을 붙이지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;Kotlin은 Java와 다르게 `변수: 타입`순으로 선언합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin의 함수는 `fun` 키워드를 사용하며, 기본적인 형태는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765520312947&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun max(a: Int, b: Int): Int {
    return if (a &amp;gt; b) a else b
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 형태는 `fun` &lt;b&gt;(파라미터 이름: 파라미터 타입): 반환 타입 { 함수 본문 }&lt;/b&gt;과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 주목해야 할 점은 `if`가 Statement(문)가 아니고 Expression(식)으로 사용되었다는 점입니다. 즉, 예제의 `if` 식은 Java의 삼항 연산자 ` (a &amp;lt; b) ? a : b `와 유사하게 동작하여 결과 값을 즉시 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`if` 식처럼 함수 본문 전체가 하나의 식으로만 구성되어 값을 반환하는 경우, Kotlin에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;식이 본문인 함수&lt;/b&gt; &lt;/span&gt;형태로 더욱 간결하게 표현할 수 있습니다. (Kotlin에서 자주 사용되는 형태)&lt;/p&gt;
&lt;pre id=&quot;code_1765524512213&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun max(a: Int, b: Int): Int = if (a &amp;gt; b) a else b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가 Kotlin은 타입 추론 기능이 있어 반환 타입을 생략할 수 있습니다. 단, &lt;b&gt;본문인 함수&lt;/b&gt;에서는 컴파일러가 타입을 추론할 수 없기 때문에 반드시 반환 타입을 명시하고 `return` 문을 이용해 반환 값을 명시해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765524596241&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// expression body - 반환 타입 추론 가능 (생략 OK)
fun max(a: Int, b: Int) = if (a &amp;gt; b) a else b

// block body - 반환 타입 명시 필요
fun max(a: Int, b: Int): Int {
    return if (a &amp;gt; b) a else b
}&lt;/code&gt;&lt;/pre&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Statement(문): &lt;b&gt;값을 만들어내지 않습니다.&lt;/b&gt; 자신을 둘러싼 블록의 최상위 요소만 존재합니다. (ex. `var`, `fun`, `return`, `while`)&lt;br /&gt;Expression(식): &lt;b&gt;값을 만들어 냅니다.&lt;/b&gt; 다른 식의 하위 요소로 계산에 참여할 수 있습니다. (ex. `if`, `when`, `a+b`, 10)&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 변수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서는 변수 이름 뒤에 타입을 명시하는 방식을 사용하며, 종종 타입 지정을 생략할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765525070345&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val age = 10 // 타입이 생략되었지만 값 10을 보고 Int 타입을 추론한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 초기화 식을 사용하지 않고 변수를 선언하려면 컴파일러가 타입을 추론할 수 없기 때문에 변수 타입을 반드시 명시해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765525140517&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val age: Int // 값 초기화 X
age = 10 // 이후 값 초기화 O&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 변경 가능한 `var`과 불변성을 가진 `val` 2가지 키워드를 제공합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 16.7442%; height: 22px;&quot;&gt;키워드&lt;/td&gt;
&lt;td style=&quot;width: 17.4418%; height: 22px;&quot;&gt;유래&lt;/td&gt;
&lt;td style=&quot;width: 46.628%; height: 22px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 19.186%; height: 22px;&quot;&gt;Java 대응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 16.7442%; height: 44px;&quot;&gt;`val`&lt;/td&gt;
&lt;td style=&quot;width: 17.4418%; height: 44px;&quot;&gt;Value (값)&lt;/td&gt;
&lt;td style=&quot;width: 46.628%; height: 44px;&quot;&gt;&lt;b&gt;변경 불가능한 참조&lt;/b&gt;(Immutable&amp;nbsp;Reference)&lt;b&gt;&lt;br /&gt;&lt;/b&gt;초기화 후 재대입이 불가능합니다.&lt;/td&gt;
&lt;td style=&quot;width: 19.186%; height: 44px;&quot;&gt;&amp;nbsp;`final` 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 16.7442%; height: 44px;&quot;&gt;`var`&lt;/td&gt;
&lt;td style=&quot;width: 17.4418%; height: 44px;&quot;&gt;Variable (변수)&lt;/td&gt;
&lt;td style=&quot;width: 46.628%; height: 44px;&quot;&gt;&lt;b&gt;변경 가능한 참조&lt;/b&gt;(Mutable Reference)&lt;br /&gt;초기화 후 재대입이 가능합니다.&lt;/td&gt;
&lt;td style=&quot;width: 19.186%; height: 44px;&quot;&gt;일반 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; &amp;nbsp;불변성&amp;nbsp;사용&amp;nbsp;권장&amp;nbsp;원칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 모든 변수를 `val` 키워드를 사용하여 불변 변수로 선언하고, &lt;b&gt;꼭 필요할 때에만&lt;/b&gt; `var`&lt;b&gt;로 변경&lt;/b&gt;하는 것을 권장합니다. 이는 불변성 기반 설계의 핵심이며 코드의 안정성을 높입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  `val` 변수의 초기화 규칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`val`은 참조 자체는 불변이지만, 반드시 &lt;b&gt;블록이 실행될 때 정확히 한 번만 초기화&lt;/b&gt; 되어야 합니다. 컴파일러가 특정 실행 경로에서 오직 하나의 초기화 문장만 실행됨을 확인할 수 있다면, 조건에 따라 `val` 값을 여러 다른 값으로 초기화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765525678356&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val message: String
if (canPerformOp()) { // 특정 조건 별 분기
    message = &quot;success&quot;
} else {
    message = &quot;failed&quot;
}
// 컴파일러는 이 시점에서 message가 반드시 한 번 초기화되었음을 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  참조의 불변성과 객체의 가변성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`val` 참조 자체는 불변이지만, 그 참조가 가리키는 객체의 내부 값은 변경될 수 없음을 유의해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765525817483&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = mutableListOf(1, 2, 3) // numbers 참조 자체는 불변
numbers.add(4) // numbers가 가리키는 객체의 내부 상태는 변경 가능하다.
// numbers = mutableListOf(5) 재대입 시도 시 컴파일 오류 발생!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스와 프로퍼티&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 데이터를 저장하는 클래스, 특히 값 객체(Value Object)를 간결하게 정의할 수 있도록 지원합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765526148765&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
public class Person {
    private final String name;
    public Person(String name) { this.name = name; }
    public getName() { return name; }
}

// Kotlin
class Person(val name: String)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 데이터만을 포함하는 클래스는 Kotlin의 간결성을 극대화하는 대표적인 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 프로퍼티&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 주된 목적은 &lt;b&gt;데이터를 캡슐화&lt;/b&gt;하고, 데이터를 다루는 코드를 하나의 주체 아래에 통합하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java 방식:&lt;/b&gt; 데이터를 필드(Field)에 저장하고, 이 필드에 접근할 수 있는 통로로 접근 메서드(Accessor Method)인 getter/setter를 제공합니다. Java에서는 필드와 해당 접근자 메서드를 묶어 &lt;b&gt;프로퍼티&lt;/b&gt;라고 관례적으로 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kotlin 방식:&lt;/b&gt; 프로퍼티 개념을 언어의 &lt;b&gt;기본 기능&lt;/b&gt;으로 제공하며, Java의 &lt;b&gt;필드와 접근자 메서드를 완전히 대신&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언: Kotlin에서 프로퍼티를 선언할 때는 변수와 마찬가지로 `val`과 `var` 키워드를 선언합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.4728%;&quot;&gt;키워드&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;프로퍼티 유형&lt;/td&gt;
&lt;td style=&quot;width: 39.1473%;&quot;&gt;자동 생성 접근자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.4728%;&quot;&gt;`val`&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;읽기 전용 프로퍼티&lt;/td&gt;
&lt;td style=&quot;width: 39.1473%;&quot;&gt;비공개 필드와 getter만 선언합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.4728%;&quot;&gt;`var`&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;변경 가능한 프로퍼티&lt;/td&gt;
&lt;td style=&quot;width: 39.1473%;&quot;&gt;비공개 필드와 getter/setter 모두 선언합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 커스텀 접근자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티는 단순히 값을 저장하는 필드 역할뿐만이 아닌 접근할 때도 특정 로직을 실행하도록 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 `getter` 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직사각형을 표현하는 `Rectangle` 클래스에 해당 사각형이 정사각형인지 여부를 판단하는 기능을 커스텀 `getter`로 구현할 수 있습니다. 이 경우 별도의 필드를 만들어 상태를 저장할 필요가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766987850396&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Rectangle(
    val height: Int,
    val width: Int
) {
    val isSquare: Boolean
        get() = height == width
}

fun run() {
    val r = Rectangle(10, 10)
    println(&quot;${r.isSquare}&quot;) // 출력: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 `getter`를 호출할 때마다 값을 새로 계산합니다. 즉, `Rectangle`의 `height`나 `width`가 변경된다면, `isSquare` 프로퍼티를 호출할 때마다 변경된 값을 반영한 최신 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;enum &amp;amp; when&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) `enum` 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서 `enum`은 &lt;b&gt;Soft keyword&lt;/b&gt;입니다. 즉, 예약어로 동작하는 것이 아니라 `enum class` 형태로 `class` 앞에 사용될 때만 특별한 의미를 가지게 됩니다. 단순히 상수를 열거하는 것 이상으로, 프로퍼티와 메서드를 가질 수 있는 완전한 클래스입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766993727590&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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); // 상수 목록 끝에 세미콜론 필수

    fun rgb() = (r * 256 + g) * 256 + b // 메서드 정의
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) `when` 식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 Java의 `switch`문을 대체하면서 더 강력한 기능을 제공하는 `when`이 있습니다. `when`은 문(Statement)이 아닌 식(Expression)이므로 결괏값을 반환할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766993962660&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getWarmth(color: Color) = when(color) {
    Color.RED, Color.ORANGE, Color.YELLOW -&amp;gt; &quot;warm&quot;
    Color.GREEN -&amp;gt; &quot;neutral&quot;
    Color.BLUE, Color.INDIGO, Color.VIOLET -&amp;gt; &quot;cold&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java와 달리 각 분기 끝에 `break`를 넣지 않아도 되며, 한 분기 안에서 여러 값을 매칭하려면 콤마(`,`)로 연결합니다. 또한 Java와 달리 `when`의 분기 조건에 상수 뿐만이 아닌 &lt;b&gt;임의의 객체&lt;/b&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766994091523&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun mix(c1: Color, c2: Color) = when (setOf(c1, c2)) {
    setOf(Color.RED, Color.YELLOW) -&amp;gt; Color.ORANGE
    setOf(Color.YELLOW, Color.BLUE) -&amp;gt; Color.GREEN
    else -&amp;gt; throw Exception(&quot;Dirty color&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 `setOf`를 통해 두 색상의 순서와 상관없이 조합을 검사할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만&amp;nbsp;위&amp;nbsp;방식은&amp;nbsp;setOf를&amp;nbsp;호출할&amp;nbsp;때마다&amp;nbsp;새로운&amp;nbsp;Set&amp;nbsp;객체가&amp;nbsp;생성되므로,&amp;nbsp;함수가&amp;nbsp;자주&amp;nbsp;호출되는&amp;nbsp;경우&amp;nbsp;불필요한&amp;nbsp;임시&amp;nbsp;객체와&amp;nbsp;가비지&amp;nbsp;컬렉션&amp;nbsp;부담이&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 이러한 상황을 위해 &lt;b&gt;인자 없는 `when`&lt;/b&gt;을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766994481897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun mixOptimized(c1: Color, c2: Color) = when {
    (c1 == RED &amp;amp;&amp;amp; c2 == YELLOW) ||
    (c1 == YELLOW &amp;amp;&amp;amp; c2 == RED) -&amp;gt; ORANGE

    (c1 == YELLOW &amp;amp;&amp;amp; c2 == BLUE) ||
    (c1 == BLUE &amp;amp;&amp;amp; c2 == YELLOW) -&amp;gt; GREEN

    (c1 == BLUE &amp;amp;&amp;amp; c2 == VIOLET) ||
    (c1 == VIOLET &amp;amp;&amp;amp; c2 == BLUE) -&amp;gt; INDIGO

    else -&amp;gt; throw Exception(&quot;Dirty color&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 스마트 캐스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 컴파일러는 형 변환을 자동으로 수행해 주는 스마트 캐스트 기능을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`is` 연산자: Java의 `instanceof`와 비슷하게 타입을 검사합니다.&lt;/li&gt;
&lt;li&gt;스마트 캐스팅: `is`로 타입을 검사하고 나면, 컴파일러가 해당 블록 내에 변수를 해당 타입으로 간주하므로 명시적인 캐스팅(`as`) 없이 프로퍼티를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1766994868516&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun eval(e: Expr): Int = when (e) {
    is Num -&amp;gt; e.value // 스마트 캐스트 적용
    is Sum -&amp;gt; eval(e.left) + eval(e.right) // 스마트 캐스트 적용
    else -&amp;gt; throw IllegalArgumentException(&quot;Unknown expression&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) `if`와 `when`의 리팩토링 및 블록 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 `if` 역시 식(Exprssion)이므로 값으로 반환할 수 있습니다. 따라서 Java와 같은 별도의 3항 연산자가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1766995059151&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = when (e) {
    is Num -&amp;gt; {
        println(&quot;value: ${e.value}&quot;)
        e.value // 이 블록의 결과값
    }
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;while과 for loop&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin의 `while` &amp;amp; `do-while`의 경우 Java와 완전히 동일한 구조를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`for loop`의 경우 Java의 고전적인 `int i = 0; i &amp;lt; 10; i++` 방식이 존재하지 않으며, Kotlin은 &lt;b&gt;범위 연산자&lt;/b&gt;를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 수에 대한 이터레이션: 범위와 순열&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;표현식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;Java 대응 코드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;1..10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1부터 10까지 (10포함)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i = 0; i &amp;lt;= 10; i++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;1 until 10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1부터 9까지&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i = 0; i &amp;lt; 10; i++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;10 downTo 1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;10부터 1까지 역순&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i = 10; i &amp;gt;= 1; i--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: left;&quot;&gt;step 2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2씩 증가 (간격 설정)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i += 2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1767018910891&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 기본 범위 반복 (10 포함)
for (i in 1..10) { // a..z 로 사용해도 a b c d e f g... 출력
    print(&quot;$i &quot;) // 1 2 3 ... 10
}

// 2. 마지막 숫자 제외 (until) - 배열 인덱스 순회 시 유용
val list = listOf(&quot;Java&quot;, &quot;Kotlin&quot;, &quot;Spring&quot;)
for (i in 0 until list.size) {
    println(&quot;Index $i: ${list[i]}&quot;)
}

// 3. 역순 및 간격 설정 (downTo, step)
for (i in 10 downTo 1 step 2) {
    print(&quot;$i &quot;) // 10 8 6 4 2
}

// 4. 컬렉션 직접 순회 (for-each 스타일)
for (lang in list) {
    println(&quot;Learning $lang&quot;)
}

// 5. 인덱스와 값을 동시에 추출 (withIndex)
for ((index, value) in list.withIndex()) {
    println(&quot;[$index]: $value&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) `in`으로 컬렉션이나 범위의 원소 검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`in` 연산자를 통해 어떤 값이 범위에 속하는지, 반대로 `!in`을 사용한다면 범위에 속하지 않는지 검사할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767019946679&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun main() {
    println(isLetter('h')) // true
    println(isLetter('11')) // false
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767020041520&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun recognize(c: Char) = when (c) {
    in '0'..'9' -&amp;gt; &quot;It's a digit!&quot;
    in 'a'..'z', in 'A'..'Z' -&amp;gt; &quot;It's a letter!&quot;
    else -&amp;gt; &quot;I don't know&amp;hellip;&quot;
}
fun main() {
    println(recognize('8')) // It's a digit!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin의 예외처리&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin의 예외처리는 Java나 다른 언어의 예외처리와 비슷합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) try, catch, finally를 사용한 예외처리와 오류 복구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 코드와 가장 큰 차이는 `throws` 절이 Kotlin에 없다는 점입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767020192538&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun readNumber (reader: BufferedReader): Int {
    try {
    	val line = reader.readLine()
    	return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
    	return 0
    } finally {
    	reader.close()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 모든 예외를 언체크 예외(Unchecked Exception)로 취급합니다. 따라서 Java와 달리 `throws`를 명시하거나 예외 처리를 강제할 필요가 없어 코드가 간결해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 `try-with-resources` 대신 표준 라이브러리의 `.use` 확장 함수를 사용해 리소스를 안전하게 해제할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767020724986&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Kotlin: .use() 확장 함수 사용
val line = BufferedReader(FileReader(path)).use { br -&amp;gt;
    br.readLine()
}
// 블록을 나가는 순간 자동으로 br.close() 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) try를 식으로 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`try`문 또한 식(Expression)으로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767020833580&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun readNumber (reader: BufferedReader) { 
     val number = try {
        Integer.parseInt(reader.readLine()) // 이 식의 값이 try 식의 값이 된다.
     } catch (e: NumberFormatException) { 
        return 
    }
    println(number)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`if`와 달리 `try`의 본문을 중괄호 {}로 둘러싸야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 정의할 때 `fun` 키워드를 사용하며, val는 읽기 전용 변수, var는 변경 가능한 변수를 선언할 때 쓰인다.&lt;/li&gt;
&lt;li&gt;Kotlin의 `if`는 식(Expression)이며, 값을 만들 수 있다.&lt;/li&gt;
&lt;li&gt;Java의 `switch`를 대체하며, 더 강력한 기능을 제공하는 `when`이 있다.&lt;/li&gt;
&lt;li&gt;스마트 캐스팅: 어떤 변수의 타입을 검사하면 직접 캐스팅하지 않아도 형 변환을 한 변수처럼 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;`1..5`와 같은 식으로 범위를 나타낼 수 있고, 어떤 값이 범위 안에 들어있거나 들어있지 않은지 검사하기 위해 `in`이나 `!in`을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;Kotlin의 예외처리는 Java와 비슷하다. 다만 함수가 던질 수 있는 예외를 선언하지 않아도 된다. (언체크 예외)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kotlin</category>
      <category>2/e</category>
      <category>Kotlin</category>
      <category>kotlin in action</category>
      <category>When</category>
      <category>스마트 캐스트</category>
      <category>코틀린의 기초</category>
      <category>클래스와 프로퍼티</category>
      <category>함수와 변수</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/64</guid>
      <comments>https://dev-hui.tistory.com/64#entry64comment</comments>
      <pubDate>Fri, 12 Dec 2025 14:57:07 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin in Action 2/e] 코틀린이란 무엇이며, 왜 필요한가?</title>
      <link>https://dev-hui.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin in Action 2/e 서적을 읽으면서 정리하고자 쓴 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u1Y0X/dJMcadHg0bZ/jgK7j9lTW3mXnlHqfJyAi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u1Y0X/dJMcadHg0bZ/jgK7j9lTW3mXnlHqfJyAi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u1Y0X/dJMcadHg0bZ/jgK7j9lTW3mXnlHqfJyAi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu1Y0X%2FdJMcadHg0bZ%2FjgK7j9lTW3mXnlHqfJyAi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;왜 JetBrains는 Kotlin을 만들었는가&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JetBrains는 IntelliJ IDEA를 만든 회사로 내부 서비스와 툴 대부분 Java로 이루어져 있으며 다음과 같이 불편이 누적되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java의 장황한 문법 (Boilerplate)&lt;/li&gt;
&lt;li&gt;안전하지 않는 Null 처리&lt;/li&gt;
&lt;li&gt;더 생산적이고 간결한 코드에 대한 실용적인 요구 증가 (모던한 방식)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#처럼 모던한 방식을 원했고, 기존 Java로 이뤄진 코드를 호환할 수 있는 언어를 찾아보았으나 그러한 언어가 없었습니다. 그렇게 JetBrains는 Java와 호환성이 좋고, 모던함을 갖추고자 실용적인 언어 &lt;b&gt;Kotlin&lt;/b&gt;을 만들기로 결정합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Kotlin은 코틀린 개발 팀이 대부분 살고 있는 러시아의 상트페테르부르크 근처에 있는 섬이름을 따서 만든 이름입니다. Java나 Ceylon 같이 언어 기원의 전통을 따른 것인데, 고향에 가까운 섬의 이름을 따왔습니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kotlin이 무엇이고, 왜 써야하는가&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java보다 더 모던한 방식이며서 Java로 만든 라이브러리, 프레임워크, SDK를 이용할 수 있는 언어가 필요하였고, 위 조건을 충족할 수 있는 목표로 만들어진 언어가 Kotlin입니다. 아래는 Java 코드를 Kotlin으로 변환하면 어떻게 되는지 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 코드&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) {
        List&amp;lt;Person&amp;gt; persons = List.of(
                new Person(&quot;owen&quot;),
                new Person(&quot;bella&quot;, 29)
        );

        Person oldPerson = persons.stream()
                .max(Comparator.comparingInt(p -&amp;gt; p.getAge() != null ? p.getAge() : 0))
                .orElse(null);
        System.out.println(&quot;나이가 가장 많은 사람: &quot; + oldPerson);
    }
}

class Person {
    private String name;
    private Integer age; // nullable

    public Person(String name) {
        this(name, null);
    }

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

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public String toString() {
        return &quot;Person(name=&quot; + name + &quot;, age=&quot; + age + &quot;)&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kotlin 코드&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val persons = listOf(
        Person(&quot;owen&quot;),
        Person(&quot;bella&quot;, 29)
    )

    val oldPerson = persons.maxBy { it.age ?: 0 }
    println(&quot;나이가 가장 많은 사람: $oldPerson&quot;)
}

data class Person(val name: String, val age: Int? = null)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;40줄이 넘는 코드가 10줄 정도로 줄어드는 마술을 보면 왜 Kotlin을 써야 하는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kotlin은 다음과 같은 특성들이 있습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 활용 플랫폼&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 JVM 위에 동작하는 언어지만 여러 플랫폼에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JVM 기반 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot, Android 등 Java가 동작하는 곳이라면 그대로 Koltin을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;멀티 플랫폼 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Koltin/JS &amp;rarr; React, Node.js 환경에서 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin/Native &amp;rarr; IOS, Windows, macOS, Linux에서 실행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin Multiplatform &amp;rarr; 비즈니스 로직 공유가 가능하여 mobile/web/back-end 공통 로직을 하나로 유지 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 정적 타입 언어 (+ 타입 추론)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 정적 언어이기 때문에 안정성을 확보하면서도 타입 추론을 지원해 코드가 간결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;타입 추론&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입을 직접 쓰지 않아도 Kotlin은 타입을 추론합니다. 하지만 추론이 과도하면 상위 타입이 강제로 할당되거나 의도한 타입이 모호해지는 경우가 있을 수 있어 주의가 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;fsharp&quot;&gt;&lt;code&gt;val age = 20     // Int 추론
val name = &quot;Kim&quot; // String 추론&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Unit 타입 (Java의 void와 차이)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서는 반환값을 명시하지 않은 함수도 내부적으로는 항상 `Unit`타입을 반환합니다. 이 `Unit`은 Java의 `void`처럼 &amp;ldquo;아무것도 없다&amp;rdquo;는 의미가 아니라, 하나의 값만 존재하는 정상적인 타입입니다. 개념적으로는 Java의 `void`보다는 `Void` 클래스에 가깝기 때문에 값처럼&amp;nbsp;전달하거나&amp;nbsp;파라미터로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765512352696&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Kotlin에서는 반환값이 없는 함수도 &quot;Unit&quot; 타입을 반환합니다.
// 즉, 이 함수의 실제 시그니처는 fun test1(): Unit 입니다.
fun test1() {
    println(&quot;Hello&quot;)
}

// Unit을 파라미터 타입으로 받는 함수 정의가 가능합니다.
fun test2(value: Unit) {
    value // Unit은 실제로 객체처럼 전달이 가능하다.
}

// test1()을 호출하면 println은 수행되지만, 반환값으로 Unit 인스턴스가 반환됩니다.
// 이 Unit 값이 test2의 파라미터로 정상 전달됩니다.
fun main() {
    test2(test1())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 객체 지향(OOP) + 함수형 프로그래밍(FP)을 모두 갖춘 멀티 패러다임&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 객체지향과 함수형 두 접근 방식을 상황에 따라 혼합할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일급 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 다른 일반적인 값(변수, 객체 등)과 &lt;b&gt;동일한 자격&lt;/b&gt;을 갖는 것을 의미합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765512555026&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 함수 정의 (A: 일반 함수)
val sum: (Int, Int) -&amp;gt; Int = { a, b -&amp;gt; a + b }

// 2. 함수 타입 정의 및 변수에 함수 할당 (Function Reference 사용)
// ::sum 은 sum 함수 자체를 참조(Reference)하는 것을 의미합니다.
// (Int, Int) -&amp;gt; Int 는 이 함수의 타입(signature)을 의미합니다.
val myOperation: (Int, Int) -&amp;gt; Int = ::sum 

// 3. 변수에 할당된 함수 실행
val result = myOperation(10, 5) 
println(&quot;결과: $result&quot;) // 결과: 15

// 4. 함수를 리스트에 저장
val functionList = listOf(::sum)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;고차 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 함수를 인자로 받거나 함수를 결과로 반환하는 함수를 말합니다. 일급 함수 개념이 있기 때문에 고차 함수를 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765512647069&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun operate(a: Int, b: Int, op: (Int, Int) -&amp;gt; Int): Int = op(a, b)

val result = operate(3, 5) { x, y -&amp;gt; x + y } // Kotlin은 람다 함수를 뒤로 뺄 수 있다.
println(&quot;결과: $finalResult&quot;) // 결과: 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;선언형 프로그래밍&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java가 `stream()`을 통해 컬렉션 파이프라인을 구축하는 반면, Kotlin은 컬렉션 API의 확장 함수들을 바로 연결하여 데이터의 변환 및 필터링 흐름을 명령형 코드보다 더욱 직관적인 파이프라인 구조로 설계할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765513829383&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java21
var names = List.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;)
var result = names.stream()
	.map(String::toLowerCase)
    .filter(s -&amp;gt; s.startWith(&quot;a&quot;))
    .toList();
System.out.println(result); // [a]

// Kotlin
val names = listOf(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;)
val result = names
	.map { it.lowerCase() }
	.filter { it.startsWith(&quot;a&quot;) }
println(result) // [a]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4) 안드로이드 프로그래밍&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 Android 공식 언어로 선정되었으며, 많은 최적화가 적용되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Jetpack Compose&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google의 최신 UI 프레임워크인 Jetpack Compose는 Kotlin API를 활용하여 선언 UI를 구축하는 대표 사례입니다. 이는 Kotlin의 DSL(Domain-Spectofoc Language)적인 특성을 살린 모던한 개발 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765514518073&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Greeting(name: String) {
    Text(text = &quot;Hello $name&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Kotlin과 Java와 비교했을 때 성능 차이가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin으로 작성된 애플리케이션은 최종적으로 JVM 바이트코드로 컴파일되므로, 기존 Java와 성능 차이가 거의 없습니다. 또한 런타임 시스템의 용량이 작기 때문에 패키지(APK/AAB) 크기에 미치는 영향이 미미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin의 고차 함수는 인자로 받은 람다를 `inline`처리합니다. (함수에 함수를 호출하지만 마치 하나의 함수로 호출되는 마술) 이를 통해 람다를 호출할 때 새로운 객체가 생성되는 것을 방지합니다. 이는 GC 활동을 줄여 성능의 효율을 높일 수 이습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5) Kotlin의 철학: 실용성 &amp;middot; 간결성 &amp;middot; 안정성 &amp;middot; 상호 운용성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실용성 (Practicality)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학술적 언어가 아닌 실전 문제를 해결하기 위한 언어&lt;/li&gt;
&lt;li&gt;IntelliJ 기반이라 IDE 지원이 매우 뛰어남 (자동 변환, 리팩토링, 빠른 인스펙션)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;간결성 (Conciseness)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter/setter, 생성자, 데이터 객체 등 모두 간결하게 작성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765516774374&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
    public String setName(String name) { this.name = name; }
}

// Kotlin: data class로 선언하면 기본적으로 getter/setter를 생성해준다.
data class Person(val name: String)

fun main() {
    val person = Person(&quot;hun&quot;)
    println(&quot;이름은 ${person.name}입니다.&quot;) // 이름은 hun입니다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;안정성 (Safety)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 문법적 안전장치(Null 안정성, 스마트 캐스트 등)를 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765517314546&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Non-nullable Type: 변수를 선언할 때 기본적으로 `null`값을 허용하지 않습니다.
var name: String = &quot;hun&quot;
name = null // 컴파일 오류 발생!!

// 2. Nullable Type: 변수에 `null`을 저장하려면 타입 뒤에 물음표(?)를 명시적으로 붙여야 합니다.
var age: Int? = 20
name = null // 허용

// 3. 안전 호출 연산자(?.): `null` 아닌 경우 메서드나 프로퍼티 호출을 보장합니다.
val length = name?.toString()?.length // age가 null이면 전체 결과는 null

// 4. elvis 연산자(?:): 안전 호출 결과가 `null`인 경우 대신 사용할 값을 지정합니다.
val safeLength = age?.toString()?.length ?: 0 // age가 null이면 0 사용

// 5. 스마트 캐스트 (Smart Casts): 타입 검사 후 명시적 형 변환 없이 바로 해당 타입을 사용할 수 있습니다.
fun printLength(x: Any) { // Any: Java의 Object 클래스처럼 Kotlin의 최상위 타입이다.
	if (x is String) { // x instanceOf String
    	print(x.length) // 타입 검사가 유효하다면 x는 Any -&amp;gt; String으로 캐스팅된다.
    }
}

// 6. 예외 안전 처리 runCatching 함수: try-catch를 함수형 스타일로 사용할 수 있는 표준 라이브러리 함수
// 성공하면 결과를, 예외가 발생하면 &quot;fallback value&quot;를 반환
val result = runCatching {
	riskOp(); // 예외가 발생할 수 있는 함수
}.getOrDefault(&quot;fallback value&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상호 운용성 (Interoperability)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내 Java와 Kotlin으로 작성된 클래스가 각각 있을 때 서로 의존해도 아무런 호환 문제가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Java에서 가져온 클래스를 Kotlin에서 사용할 때, Java는 기본적으로 `null` 가능성을 명시적으로 처리하지 않기 때문에 해당 타입은 코틀린에서 &lt;b&gt;플랫폼 타입&lt;/b&gt;으로 인식됩니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;플랫폼 타입&lt;/b&gt;은 `null` 허용 여부 정보가 없어 코틀린의 `null` 안정성 시스템을 보장받지 못하기 때문에 이를 주의해서 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin Build 과정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zOPCa/dJMcagYkXc2/f9fZTX10n1kfNaPF8z1f50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zOPCa/dJMcagYkXc2/f9fZTX10n1kfNaPF8z1f50/img.png&quot; data-alt=&quot;Kotlin Build&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zOPCa/dJMcagYkXc2/f9fZTX10n1kfNaPF8z1f50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzOPCa%2FdJMcagYkXc2%2Ff9fZTX10n1kfNaPF8z1f50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;238&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Kotlin Build&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;소스&amp;nbsp;코드&amp;nbsp;작성&amp;nbsp;(.kt&amp;nbsp;파일)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력&lt;/b&gt;: 개발자가 작성한 Kotlin 소스 파일 (.kt)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Kotlin Compiler (kotlinc)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: 코틀린 컴파일러(kotlinc)는 `.kt` 파일을 JVM이 이해할 수 있는 형태로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력&lt;/b&gt;: 표준 자바 클래스 파일 포맷인 `.class` 파일(바이트코드)이 생성됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상호 운용성 핵심&lt;/b&gt;: 이 바이트코드는 자바 컴파일러가 생성한 바이트코드와 완벽히 호환됩니다. 따라서 코틀린과 자바 코드가 서로를 호출할 수 있으며, 기존 자바 라이브러리를 그대로 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 패키징 및 최종 아티팩트 생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JVM (back-end/desktop)&lt;/b&gt;: `.class` 파일들을 모아 `META-INF/services` 등의 메타 데이터와 함께 압축 &amp;rarr; `.jar` (Java Archive)&lt;span data-path-to-node=&quot;14,2,1,0&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 애플리케이션 실행 환경 및 런타임 의존성&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;최종 아티팩트가 실행될 때, 해당 애플리케이션은 &lt;b&gt;코틀린 런타임(Kotlin Runtime)&lt;/b&gt; 라이브러리에 의존합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Kotlin Runtime:&lt;/b&gt; 코틀린의 표준 라이브러리(kotlin-stdlib.jar)를 의미합니다. 이 라이브러리는 &lt;b&gt;확장 함수, 기본 함수, 코틀린만의 컬렉션 인터페이스&lt;/b&gt; 등 코틀린 언어의 고유한 기능을 지원하는 데 필요한 클래스들을 포함하고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성:&lt;/b&gt; 이 런타임 라이브러리는 최종 `.jar`에 포함되어 배포되어야 애플리케이션이 실행될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM 바이트코드 사용:&lt;/b&gt; 코틀린은 자바와 동일한 바이트코드를 사용하므로, 코틀린 파일과 자바 파일은 한 프로젝트 내에서 함께 컴파일되고 실행될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kotlin Runtime 포함:&lt;/b&gt; 코틀린 고유의 문법을 지원하기 위해 소형의 표준 라이브러리(런타임)가 최종 애플리케이션에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <category>2/e</category>
      <category>Kotlin</category>
      <category>KotlinInAction</category>
      <category>자바</category>
      <category>코를린기초</category>
      <category>코틀린</category>
      <category>코틀린인액션</category>
      <category>코틀린정리</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/63</guid>
      <comments>https://dev-hui.tistory.com/63#entry63comment</comments>
      <pubDate>Fri, 12 Dec 2025 11:28:42 +0900</pubDate>
    </item>
    <item>
      <title>백준 15990번: 1, 2, 3 더하기 5</title>
      <link>https://dev-hui.tistory.com/62</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15990&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/15990&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백준 15990번:&amp;nbsp;1,&amp;nbsp;2,&amp;nbsp;3&amp;nbsp;더하기&amp;nbsp;5&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xj4Gh/btsLK0U4kZb/gLe39Wqp2s9FwcK8M4yUK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xj4Gh/btsLK0U4kZb/gLe39Wqp2s9FwcK8M4yUK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xj4Gh/btsLK0U4kZb/gLe39Wqp2s9FwcK8M4yUK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxj4Gh%2FbtsLK0U4kZb%2FgLe39Wqp2s9FwcK8M4yUK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;782&quot; height=&quot;544&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제는 1, 2, 3의 합으로 표현하는 모든 경우를 구하여야 합니다. 또한 같은 숫자를 연속해서 2번 이상 사용할 수 없습니다. 즉&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;마지막으로 사용한 숫자가 다시 연속으로 등장하면 안됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건을 만족하기 위해 각 경우의 마지막으로 사용된 숫자를 명시적으로 관리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;b&gt;2차원 배열&lt;/b&gt;을 사용해 dp[i][j]를 다음과 같이 정의합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;dp[i][j]: &lt;span style=&quot;color: #ee2323;&quot;&gt;i를 1, 2, 3의 합으로 나타날 때 마지막 숫자가 j인 경우의 수&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;b&gt;  왜 2차원 배열을 써야할까?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;마지막으로 사용된 숫자를 추척함으로써, 같은 숫자가 연속해서 등장하지 않도록 관리할 수 있으며&lt;/b&gt;, 이를 통해 결과를 계산한다면 dp[i][1], dp[i][2], dp[i][3]의 값을 합하면 i를 만드는 모든 경우의 수를 구할 수 있기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;만약 dp[i]를 단순히 i를 만드는 모든 경우의 수로 정의한다면 마지막 숫자가 무엇인지 알 수 없습니다. &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;또한 점화식을 세울 때 중복된 계산이 발생하거나, 조건을 만족하는 않는 경우를 포함할 위험이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;점화식 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 배열 dp[i][j]는 위에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;i를 1, 2, 3의 합으로 나타날 때 마지막 숫자가 j인 경우의 수&quot;&lt;/b&gt;&lt;/span&gt;라고 정의했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식의 마지막이 + 3 이라면 j는 1 or 2이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식의 마지막이 + 2 이라면 j는 1 or 3이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수식의 마지막이 + 1 이라면 j는 2 or 3이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;i를 7로 보고 1, 2, 3의 합을 나타낸다면 다음과 같이 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4 에서 3을 더해주고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5 에서 2를 더해주고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6 에서 1을 더해주면 각각 7이 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7을 만들 수 있는 수식 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;으로 끝나는 수식(마지막에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;+ 3&lt;/span&gt;인 경우)은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4를 만드는 수식에서 1(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;을 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4를 만드는 수식에서 2(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;을 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, dp[7][&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;] =&lt;span&gt;&amp;nbsp;&lt;/span&gt;dp[4][1] + dp[4][2] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이를 점화식으로 표현하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;dp[n][&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;] = dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;][1] + dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&gt;][2] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7을 만들 수 있는 수식 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;로 끝나는 수식(마지막에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;+ 2&lt;/span&gt;인 경우)은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5를 만드는 수식에서 1(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;를 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5를 만드는 수식에서 3(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;를 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, dp[7][&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;] =&lt;span&gt;&amp;nbsp;&lt;/span&gt;dp[5][1] + dp[5][3] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이를 점화식으로 표현하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;dp[n][&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;] = dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;][1] + dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;2&lt;/span&gt;][3] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7을 만들 수 있는 수식 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;로 끝나는 수식(마지막에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;+ 1&lt;/span&gt;인 경우)은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6을 만드는 수식에서 2(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;을 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6을 만드는 수식에서 3(으)로 끝나는 것에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;을 더 하는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, dp[7][&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;] = dp[6][2] + dp[6][3] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 점화식으로 표현하면 dp[n][&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;] = dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;][2] + dp[n -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;1&lt;/span&gt;][3] 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 그림으로 표현한다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DXKEB/btsLNumqade/gfZNbqLq7krafHWhLQYxX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DXKEB/btsLNumqade/gfZNbqLq7krafHWhLQYxX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DXKEB/btsLNumqade/gfZNbqLq7krafHWhLQYxX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDXKEB%2FbtsLNumqade%2FgfZNbqLq7krafHWhLQYxX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;322&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7을 i로 즉, 점화식으로 설명한다면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;마지막이 3으로 끝나는 경우 (dp[i][3])&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;i - 3 에서 1로 끝나는 경우 3을 더함&lt;/li&gt;
&lt;li&gt;i - 3 에서 2으로 끝나는 경우 3을 더함&lt;/li&gt;
&lt;li&gt;dp[i][3]&amp;nbsp;=&amp;nbsp;(dp[i&amp;nbsp;&amp;minus;&amp;nbsp;3][1]+dp[i&amp;nbsp;&amp;minus;&amp;nbsp;3][2])&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;마지막이 2로 끝나는 경우 (dp[i][2])&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;i - 2 에서 1로 끝나는 경우 2를 더함&lt;/li&gt;
&lt;li&gt;i - 2 에서 3으로 끝나는 경우 2를 더함&lt;/li&gt;
&lt;li&gt;dp[i][2]&amp;nbsp;=&amp;nbsp;(dp[i&amp;nbsp;&amp;minus;&amp;nbsp;2][1]+dp[i&amp;nbsp;&amp;minus;&amp;nbsp;2][3])&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;마지막이 1로 끝나는 경우 (dp[i][1])&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;i - 1 에서 2로 끝나는 경우 1을 더함&lt;/li&gt;
&lt;li&gt;i - 1 에서 3으로 끝나는 경우 1을 더함&lt;/li&gt;
&lt;li&gt;dp[i][1] = (dp[i &amp;minus; 1][2]+dp[i &amp;minus; 1][3])&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와&amp;nbsp;같은&amp;nbsp;과정을&amp;nbsp;통해 n &amp;gt;= 4 일 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i][1] = dp[i - 1][2] + dp[i - 1][3]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i][2] = dp[i - 2][1] + dp[i - 2][3]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i][3] = dp[i - 3][1] + dp[i - 3][2] 라는 점화식이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 n에 대해 마지막으로 사용한 숫자가 다시 연속으로 등장하지 않도록 각자 마지막에 끝나는 숫자 1, 2, 3을 별도로 관리하여 합한 후, 마지막으로 dp[n][1], dp[n][2], dp[n][3]을 모두 더한 값이 정답이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[n][1] + dp[n][2] + dp[n][3]&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;1,000,000,009로 나눈 나머지를 출력하라고 되어 있기 때문에 코드는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  &lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;1,000,000,009&lt;/span&gt; 으로 나눈 이유는?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n이 커질수록 가능한 경우의 수가 매우 커지게 됩니다. 이를&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;% 연산으로 적용된 dp[n]을 저장하지 않는다면 overflow가 발생할 수 있습니다. 따라서, &lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;1,000,000,009로 나눈 나머지를 저장함으로써 결과의 안정성을 확보할 수 있습니다.&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1736776582041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    private static final int MAX = 100_000;
    private static final int DIVISOR = 1_000_000_009;

    public static void main(String[] args) throws Exception {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))
        ) {
            int t = Integer.parseInt(br.readLine());
            int[] arr = new int[t];
            for (int i = 0; i &amp;lt; t; i++) {
                arr[i] = Integer.parseInt(br.readLine());
            }
            for (long a : solution(t, arr)) {
                bw.write(a + &quot;\n&quot;);
            }
            bw.flush();
        }
    }

    private static List&amp;lt;Long&amp;gt; solution(int n, int[] arr) {
        List&amp;lt;Long&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

        // dp[i][j]일 때 i가 4인 경우, j는 수식 마지막에 더하는 값
        // ex) i = 4, j = 1 or 2 or 3
        // dp[4][1] = (1+2+1, 3+1) = 경우의 수 2개
        // dp[4][2] = (x) = 경우의 수 0개
        // dp[4][3] = (1+3) = 경우의 수 1개
        // 총 경우의 수는 dp[4][1] + dp[4][2] + dp[4][3] = 3개
        long[][] dp = new long[MAX + 1][4];
        dp[1][1] = 1; // 1
        dp[2][2] = 1; // 2
        dp[3][1] = 1; // 1+2
        dp[3][2] = 1; // 2+1
        dp[3][3] = 1; // 3

        for (int i = 4; i &amp;lt;= MAX; i++) {
            dp[i][1] = (dp[i - 1][2] + dp[i - 1][3]) % DIVISOR;
            dp[i][2] = (dp[i - 2][1] + dp[i - 2][3]) % DIVISOR;
            dp[i][3] = (dp[i - 3][1] + dp[i - 3][2]) % DIVISOR;
        }

        for (int i : arr) {
            long answer = (dp[i][1] + dp[i][2] + dp[i][3]) % DIVISOR;
            list.add(answer);
        }

        return list;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1736945916322&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    static long [][] dp = new long[100_001][4];
    static long mod = 1_000_000_009;

    public static void main(String[] args) throws Exception {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))
        ) {
            int t = Integer.parseInt(br.readLine());
            int[] arr = new int[t];

            for (int i = 0; i &amp;lt; t; i++) {
                arr[i] = Integer.parseInt(br.readLine());
            }

            StringBuilder sb = new StringBuilder();
            dp[1][1] = dp[2][2] = dp[3][1] = dp[3][2] = dp[3][3] = 1;
            int i = 4;

            for (int n : arr) {
                while (i &amp;lt;= n) {
                    dp[i][1] = (dp[i - 1][2] + dp[i - 1][3]) % mod;
                    dp[i][2] = (dp[i - 2][1] + dp[i - 2][3]) % mod;
                    dp[i][3] = (dp[i - 3][1] + dp[i - 3][2]) % mod;
                    i++;
                }
                long rs = (dp[n][1] + dp[n][2] + dp[n][3]) % mod;
                sb.append(rs).append(&quot;\n&quot;);
            }

            bw.write(sb.toString());
            bw.flush();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/문제풀이</category>
      <category>15990</category>
      <category>2차원 배열</category>
      <category>dp</category>
      <category>dynamic programming</category>
      <category>java</category>
      <category>동적 프로그래밍</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>자바</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/62</guid>
      <comments>https://dev-hui.tistory.com/62#entry62comment</comments>
      <pubDate>Mon, 13 Jan 2025 22:56:38 +0900</pubDate>
    </item>
    <item>
      <title>백준 2563번: 색종이</title>
      <link>https://dev-hui.tistory.com/61</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2563&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2563&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백준 2563번: 색종이&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;1520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SHTTN/btsJiSXCil3/ZTOny6LcbkZ7dpNfhQR1Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SHTTN/btsJiSXCil3/ZTOny6LcbkZ7dpNfhQR1Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SHTTN/btsJiSXCil3/ZTOny6LcbkZ7dpNfhQR1Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSHTTN%2FbtsJiSXCil3%2FZTOny6LcbkZ7dpNfhQR1Bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;724&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;1520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로와&amp;nbsp;세로&amp;nbsp;길이가&amp;nbsp;각각&amp;nbsp;100인&amp;nbsp;도화지가&amp;nbsp;있습니다.&amp;nbsp;이&amp;nbsp;도화지&amp;nbsp;위에&amp;nbsp;가로와&amp;nbsp;세로&amp;nbsp;크기가&amp;nbsp;각각&amp;nbsp;10인&amp;nbsp;색종이&amp;nbsp;N장을&amp;nbsp;붙입니다.&amp;nbsp;색종이는&amp;nbsp;도화지의&amp;nbsp;변과&amp;nbsp;평행하게&amp;nbsp;붙이며,&amp;nbsp;색종이가&amp;nbsp;겹칠&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;이&amp;nbsp;문제는&amp;nbsp;겹치는&amp;nbsp;부분을&amp;nbsp;고려하여&amp;nbsp;총&amp;nbsp;검은색&amp;nbsp;영역의&amp;nbsp;넓이를&amp;nbsp;계산하는&amp;nbsp;문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적으로 푼다면 넓이를 계산하고 겹친 부분을 뺀다면 쉽게 구할 수 있으나, 해당 문제는 알고리즘으로 풀어야 합니다. 우선 도화지를 나타낼 배열을 [100][100] 크기로 생성합니다. 이때 타입은 boolean으로 하여 ture는 포함, false는 미포함으로 구분합니다.&lt;br /&gt;&lt;br /&gt;즉, 어떤 수 x, y에 대해 배열[x][y]가 true이면 그 부분은 색종이가 붙어져 있다는 것을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 받은 색종이를 N개를 입력 받고, 색종이의 x, y이 주어지면 x, y ~ x + 9, x + 9까지 순회하면서 색종이를 붙입니다. 이때 이미 붙여진 색종이가 있다면 제외하고 붙였다면 넓이 값을 +1 해줍니다. 순회가 끝나면 총 넓이가 구해집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1724813727264&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class Main {

    private static final int SIZE = 100;

    public static void main(String[] args) {
        boolean[][] arr = new boolean[SIZE][SIZE];
        int answer = 0;

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        for (int i = 0; i &amp;lt; n; i++) {
            int x = sc.nextInt();
            int y = sc.nextInt();

            for (int j = x; j &amp;lt; x + 10; j++) {
                for (int k = y; k &amp;lt; y + 10; k++) {
                    if (!arr[j][k]) {
                        arr[j][k] = true;
                        answer++;
                    }
                }
            }
        }

        System.out.print(answer);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/문제풀이</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/61</guid>
      <comments>https://dev-hui.tistory.com/61#entry61comment</comments>
      <pubDate>Wed, 28 Aug 2024 11:37:53 +0900</pubDate>
    </item>
    <item>
      <title>Invalid bean definition with name 'jpaAuditingHandler'...</title>
      <link>https://dev-hui.tistory.com/60</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 진행하면서 전체 테스트 코드를 실행시켰더니 다음과 같은 예외가 발생하였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'jpaAuditingHandler' defined in null: Cannot register bean definition...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀쩡하게 돌아가던 테스트가 안되니 식은 땀이 났고, 문제를 찾아보니 테스트 코드에 &lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;@EnableJpaAuditing&lt;/b&gt;&lt;/span&gt;를 붙여 발생한 해프닝이였습니다... BeanDefinitionOverrideException은 동일한 이름의 Bean이 여러 번 등록되려고 할 때 발생됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drPUYJ/btsI7UCq3UV/4fV3n2QmZ1pbI2T4p2lpYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drPUYJ/btsI7UCq3UV/4fV3n2QmZ1pbI2T4p2lpYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drPUYJ/btsI7UCq3UV/4fV3n2QmZ1pbI2T4p2lpYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrPUYJ%2FbtsI7UCq3UV%2F4fV3n2QmZ1pbI2T4p2lpYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;311&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 문제 원인에 대해 구체적으로 알고 싶어 포스트를 정리하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제의 원인&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableJpaAuditing은 JPA Auditing 기능을 활성화하기 위해 사용됩니다. 이 기능을 활성화하면 엔티티가 생성되거나 수정될 때 자동으로 시간과 사용자를 기록하는 등의 작업을 할 수 있습니다.&lt;br /&gt;&lt;br /&gt;문제는 @EnableJpaAuditing이 애플리케이션의 main 클래스나 별도의 설정 클래스뿐만 아니라 테스트 컨텍스트에서도 중복으로 등록될 때 발생합니다. Spring Boot는 애플리케이션 실행 시와 테스트 실행 시 각각의 애플리케이션 컨텍스트를 생성합니다. 이 과정에서 두 개의 @EnableJpaAuditing이 발견되면, Spring은 2번 JpaAuditingHandler를 생성하려고 시도하며, 이로 인해 Bean 중복 등록으로 충돌이 발생합니다.&lt;br /&gt;&lt;br /&gt;이러한&amp;nbsp;문제는&amp;nbsp;특히&amp;nbsp;@SpringBootTest를&amp;nbsp;사용할&amp;nbsp;때&amp;nbsp;발생할&amp;nbsp;가능성이&amp;nbsp;큽니다.&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@SpringBootTest는&amp;nbsp;기본적으로&amp;nbsp;전체&amp;nbsp;애플리케이션&amp;nbsp;컨텍스트를&amp;nbsp;로드하기&amp;nbsp;때문에&lt;/b&gt;&lt;/span&gt;,&amp;nbsp;@EnableJpaAuditing이&amp;nbsp;설정된&amp;nbsp;클래스가&amp;nbsp;이미&amp;nbsp;로드된&amp;nbsp;상태에서&amp;nbsp;또&amp;nbsp;다른&amp;nbsp;@EnableJpaAuditing이&amp;nbsp;포함된&amp;nbsp;설정이&amp;nbsp;테스트&amp;nbsp;컨텍스트에&amp;nbsp;포함되면서&amp;nbsp;충돌이&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 테스트 코드 내 문제 부분의 코드(@EnableJpaAuditing)를 지우면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 테스트 클래스에 조건부로 설정하고 싶다면 어떻게 해야할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 필요한 설정을 별도로 적용하고 싶다면, @TestConfiguration을 사용하여 필요한 설정을 조건적으로 추가하는 방법이 있습니다. 그러나, &lt;b&gt;@EnableJpaAuditing을 테스트에 직접 사용하는 것을 권장하지는 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724047410567&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TestConfiguration
static class TestConfig {
    @Bean
    @ConditionalOnMissingBean
    public AuditingHandler jpaAuditingHandler() {
        return new AuditingHandler();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 클래스에서 @EnableJpaAuditing을 사용하는 경우, JpaAuditingHandler Bean이 중복 등록되어 BeanDefinitionOverrideException이 발생할 수 있습니다.&lt;/b&gt; 해결 방법은 테스트 클래스에서 @EnableJpaAuditing을 제거하면 됩니다. 또한 애플리케이션 컨텍스트에서 자동으로 설정된 JPA Auditing을 사용하는 것을 권장하며, 테스트 컨텍스트에 별도 설정은 권장되지 않습니다.&lt;/p&gt;</description>
      <category>Project/Trouble Shooting</category>
      <category>@enablejpaauditing</category>
      <category>auditinghandler</category>
      <category>bean</category>
      <category>beandefinitionoverrideexception</category>
      <category>invalid bean definition with name</category>
      <category>JPA Auditing</category>
      <category>jpaauditinghandler</category>
      <category>중복</category>
      <author>Hui._.</author>
      <guid isPermaLink="true">https://dev-hui.tistory.com/60</guid>
      <comments>https://dev-hui.tistory.com/60#entry60comment</comments>
      <pubDate>Mon, 19 Aug 2024 15:05:52 +0900</pubDate>
    </item>
  </channel>
</rss>