<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>slowsnail.log</title>
        <link>https://velog.io/</link>
        <description>한걸음이라도 제대로... 쓰임있는 앱을 만들자</description>
        <lastBuildDate>Mon, 08 Dec 2025 09:06:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>slowsnail.log</title>
            <url>https://velog.velcdn.com/images/subin_k/profile/92c4a019-fe48-403d-af3d-f0cd18914eb7/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. slowsnail.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/subin_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[코틀린 코루틴에서 suspend, Coroutine, Flow 이해하기 - 핵심 소비/변환 패턴: collect, collectLatest, stateIn, collectAsStateWithLifecycle]]></title>
            <link>https://velog.io/@subin_k/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%97%90%EC%84%9C-suspend-Coroutine-Flow-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%ED%95%B5%EC%8B%AC-%EC%86%8C%EB%B9%84%EB%B3%80%ED%99%98-%ED%8C%A8%ED%84%B4-collect-collectLatest-stateIn-collectAsStateWithLifecycle</link>
            <guid>https://velog.io/@subin_k/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%97%90%EC%84%9C-suspend-Coroutine-Flow-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%ED%95%B5%EC%8B%AC-%EC%86%8C%EB%B9%84%EB%B3%80%ED%99%98-%ED%8C%A8%ED%84%B4-collect-collectLatest-stateIn-collectAsStateWithLifecycle</guid>
            <pubDate>Mon, 08 Dec 2025 09:06:11 GMT</pubDate>
            <description><![CDATA[<p>Kotlin 코루틴은 비동기 처리를 구조적으로 표현하기 위한 언어/라이브러리 조합이다.
thread blocking을 피하면서도 코드 흐름을 직관적으로 유지하는 것을 목표로 한다.
이 글에서는 suspend, 코루틴, Flow의 관계와 함께, 실제 Android 환경에서 자주 사용하는 Flow 소비 및 상태 변환 패턴을 정리한다.</p>
<hr>
<h2 id="1-코루틴의-기반-suspend-fun의-마법">1. 코루틴의 기반: <code>suspend fun</code>의 마법</h2>
<p>코루틴은 스레드보다 훨씬 가벼운 비동기 작업 단위입니다. 이 코루틴의 능력을 가능하게 하는 것이 바로 <strong><code>suspend</code></strong> 키워드입니다.</p>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">일반 함수</th>
<th align="left"><code>suspend fun</code></th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>비동기 처리</strong></td>
<td align="left">콜백(Callback)을 사용해야 함.</td>
<td align="left">코루틴이 <strong>일시 중지/재개</strong>를 관리.</td>
</tr>
<tr>
<td align="left"><strong>스레드</strong></td>
<td align="left">작업이 끝날 때까지 스레드를 <strong>막음(Blocking)</strong>.</td>
<td align="left">작업 대기 시 스레드를 <strong>놓아주고(Non-Blocking)</strong> 다른 작업을 수행.</td>
</tr>
</tbody></table>
<h3 id="suspend의-핵심-원리"><code>suspend</code>의 핵심 원리</h3>
<ol>
<li><strong>표식(Marker):</strong> <code>suspend</code>는 이 함수가 실행을 <strong>잠시 멈췄다가(Suspend)</strong> 나중에 다시 시작할 수 있다(Resume)는 것을 컴파일러에게 알리는 표식입니다.</li>
<li><strong>컴파일러 변환:</strong> <code>suspend fun</code>이 호출되면, Kotlin 컴파일러는 이를 Continuation-Passing Style (CPS)이라는 복잡한 콜백 로직으로 변환합니다. 이 변환 덕분에 우리는 비동기 코드를 마치 <strong>순차적인 동기 코드처럼</strong> 작성할 수 있습니다.</li>
<li><strong>코루틴 스코프:</strong> <code>suspend</code> 함수는 코루틴(Scope)이라는 특별한 환경 내에서만 호출될 수 있습니다. 코루틴이 일시 중지된 지점의 상태를 저장하고 있다가 비동기 작업이 완료되면 재개(Resume)를 명령합니다.</li>
</ol>
<!-- end list -->

<pre><code class="language-kotlin">// 예시: suspend를 사용하여 비동기 코드를 동기처럼 작성
suspend fun fetchData() {
    // 1. 네트워크 호출: 코루틴은 여기서 멈춥니다(Suspend). 메인 스레드는 막히지 않습니다.
    val user = apiService.getUserData() 

    // 2. 데이터가 도착하면 코루틴이 재개(Resume)되어 다음 줄이 실행됩니다.
    println(&quot;사용자 이름: ${user.name}&quot;)
}</code></pre>
<hr>
<h2 id="2--flow-비동기-데이터-스트림">2.  Flow: 비동기 데이터 스트림</h2>
<p><strong>Flow</strong>는 코루틴 환경에서 시간이 지남에 따라 <strong>여러 개의 값</strong>을 순차적으로 내보내는 <strong>비동기 데이터 스트림</strong>입니다. API 응답처럼 단일 값을 한 번만 받는 <code>suspend fun</code>과 달리, Flow는 실시간 업데이트나 지속적인 데이터 흐름(예: 센서 데이터, DB 변경 사항)에 사용됩니다.</p>
<h3 id="flow의-3단계-구조">Flow의 3단계 구조</h3>
<table>
<thead>
<tr>
<th align="left">단계</th>
<th align="left">역할</th>
<th align="left">예시 함수</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>생산자 (Producer)</strong></td>
<td align="left">데이터를 생성하고 스트림으로 내보냅니다.</td>
<td align="left"><code>flow { emit(value) }</code></td>
</tr>
<tr>
<td align="left"><strong>중개자 (Intermediate)</strong></td>
<td align="left">데이터를 변형하거나 필터링합니다.</td>
<td align="left"><code>.map { ... }</code>, <code>.filter { ... }</code></td>
</tr>
<tr>
<td align="left"><strong>소비자 (Consumer)</strong></td>
<td align="left">스트림의 값을 받아서 처리합니다.</td>
<td align="left"><code>.collect { ... }</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="3-🎯-flow-소비-및-상태-변환-전략-4가지">3. 🎯 Flow 소비 및 상태 변환 전략 4가지</h2>
<p>Flow에서 데이터를 소비하는 방식은 앱의 성능과 안정성에 직접적인 영향을 미칩니다.</p>
<h3 id="a-collect-순차적serial-소비">A. <code>collect</code>: 순차적(Serial) 소비</h3>
<ul>
<li><strong>동작:</strong> Flow에서 값을 내보낼 때마다, 소비 블록 내부의 <strong>모든 로직이 완료될 때까지 다음 값의 처리를 기다립니다.</strong> 처리 순서가 엄격하게 보장됩니다.</li>
<li><strong>사용처:</strong> 모든 데이터가 빠짐없이 순서대로 처리되어야 할 때 (예: 트랜잭션 기록).</li>
</ul>
<h3 id="b-collectlatest-최신-값만-처리-및-이전-작업-취소">B. <code>collectLatest</code>: 최신 값만 처리 및 이전 작업 취소</h3>
<ul>
<li><strong>동작:</strong> 값을 처리하는 <strong>도중에</strong> 새로운 값이 들어오면, <strong>진행 중이던 이전 값의 처리(suspend 블록)를 즉시 취소</strong>하고 가장 최신 값의 처리를 시작합니다.</li>
<li><strong>사용처:</strong> 중간 과정은 중요하지 않고 <strong>최종 결과</strong>만 중요할 때 (예: 검색어 입력 시 이전 검색 요청 취소, 빠른 스크롤 시 이전 이미지 로드 취소).</li>
</ul>
<!-- end list -->

<pre><code class="language-kotlin">// collectLatest 예시
someFlow.collectLatest { query -&gt;
    // 1. 새로운 query가 들어오면 delay와 searchUsers는 즉시 취소됨
    delay(300) // 디바운스 효과
    val results = searchUsers(query) 
    updateUI(results)
}</code></pre>
<h3 id="c-statein-flow를-stateflow로-변환-viewmodel-상태화">C. <code>stateIn</code>: Flow를 <code>StateFlow</code>로 변환 (ViewModel 상태화)</h3>
<ul>
<li><strong>동작:</strong> 일반 <code>Flow</code>(Cold Flow)를 <strong><code>StateFlow</code></strong> (Hot Flow)로 변환하여 <strong>UI 상태 관리</strong>에 적합하게 만듭니다. <code>StateFlow</code>는 항상 초기 값과 최신 단일 값을 유지합니다.</li>
<li><strong>필수 인자:</strong><ul>
<li><code>scope</code>: Flow가 실행될 코루틴 스코프 (보통 <code>viewModelScope</code>).</li>
<li><code>started</code>: Flow 데이터 생산 시작 시점을 결정하는 정책 (대부분 <code>SharingStarted.WhileSubscribed(5000)</code> 사용).</li>
</ul>
</li>
<li><strong>사용처:</strong> <code>ViewModel</code>에서 Repository의 Flow 데이터를 받아 <strong>UI 상태</strong>로 노출할 때 사용됩니다.</li>
</ul>
<!-- end list -->

<pre><code class="language-kotlin">// stateIn 예시 (ViewModel에서 사용)
class MyViewModel(repository: Repository) : ViewModel() {
    val uiState: StateFlow&lt;UiState&gt; = repository.getDataFlow()
        .map { data -&gt; UiState.Success(data) } // 데이터를 UI 상태로 변환
        .stateIn(
            scope = viewModelScope,
            // 구독자가 있을 때만 데이터 생산 시작 (UI 최적화)
            started = SharingStarted.WhileSubscribed(5000), 
            initialValue = UiState.Loading // 초기 값 필수
        )
}</code></pre>
<h3 id="d-collectasstatewithlifecycle-compose-ui-소비">D. <code>collectAsStateWithLifecycle</code> (Compose UI 소비)</h3>
<ul>
<li><strong>동작:</strong> <code>StateFlow</code> 또는 <code>Flow</code>를 Jetpack Compose의 <strong><code>State&lt;T&gt;</code></strong> 객체로 변환하여 UI 컴포저블 내에서 쉽게 읽고 사용할 수 있게 합니다. 값이 변경되면 **자동으로 재구성(Recomposition)**이 발생합니다.</li>
<li><strong>라이프사이클 통합:</strong> <strong>Android <code>Lifecycle</code></strong> 상태에 따라 Flow 수집을 <strong>자동으로 시작/중지</strong>합니다.<ul>
<li>화면 활성화 시 (예: <code>STARTED</code> 상태): 수집 시작</li>
<li>화면 비활성화 시 (예: 백그라운드): 수집 중지</li>
</ul>
</li>
<li><strong>장점:</strong> 메모리 누수를 방지하고 앱 성능을 최적화하는 Compose UI의 <strong>모범 사례</strong>입니다.</li>
</ul>
<!-- end list -->

<pre><code class="language-kotlin">// collectAsStateWithLifecycle 예시 (Compose Screen에서 사용)
@Composable
fun DataScreen(viewModel: MyViewModel = viewModel()) {
    // UI 상태 소비. lifecycle에 맞춰 collect가 자동 관리됨.
    val state by viewModel.uiState.collectAsStateWithLifecycle() 

    when (state) {
        is UiState.Loading -&gt; LoadingIndicator()
        is UiState.Success -&gt; Text((state as UiState.Success).data.content)
        // ...
    }
}</code></pre>
<h3 id="요약">요약</h3>
<p>suspend - 작업의 일시 중지/재개를 표현하는 함수 표식</p>
<p>Coroutine - suspend 실행과 상태 보존을 관리하는 실행 환경</p>
<p>Flow - 시간에 따라 변하는 비동기 값의 스트림 모델</p>
<p>Flow 소비/상태 변환 패턴:</p>
<pre><code>collect: 모든 값을 순차 처리

collectLatest: 최신 값 중심 처리

stateIn: Flow를 UI 상태(StateFlow)로 변환/공유

collectAsStateWithLifecycle: Compose에서 라이프사이클 기반 안전 수집</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose (Runtime / Internals) -  Runtime의 Snapshot 구조 정리]]></title>
            <link>https://velog.io/@subin_k/Jetpack-Compose-Runtime%EC%9D%98-Snapshot-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@subin_k/Jetpack-Compose-Runtime%EC%9D%98-Snapshot-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 09 Nov 2025 07:09:15 GMT</pubDate>
            <description><![CDATA[<h2 id="1️⃣-snapshot이란">1️⃣ Snapshot이란?</h2>
<blockquote>
<p>Compose Runtime이 상태(State) 의 변경을 감지하고</p>
<p>UI 재구성을 효율적으로 수행하기 위해 사용하는 <strong>데이터 일관성(Consistency) 관리 시스템</strong>입니다.</p>
</blockquote>
<p>간단히 말해,</p>
<ul>
<li><p>“현재 상태가 어떤 값인지”</p>
</li>
<li><p>“어떤 컴포저블이 그 값을 참조 중인지”</p>
</li>
<li><p>“값이 바뀌었을 때 어디를 다시 그려야 하는지”</p>
<p>  이 모든 것을 <strong>Snapshot 시스템이 내부에서 추적</strong>합니다.</p>
</li>
</ul>
<hr>
<h2 id="2️⃣-snapshot이-필요한-이유">2️⃣ Snapshot이 필요한 이유</h2>
<p>Compose는 멀티스레드에서 상태를 바꿀 수도 있고,</p>
<p>Recomposition은 비동기적으로 발생합니다.</p>
<p>그래서 다음 두 가지 문제가 생길 수 있죠:</p>
<table>
<thead>
<tr>
<th>문제</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터 불일치</strong></td>
<td>한 스레드가 상태를 읽는 도중 다른 스레드가 값을 바꾸면 UI가 꼬임</td>
</tr>
<tr>
<td><strong>불필요한 Recomposition</strong></td>
<td>바뀌지 않은 상태까지 다시 그림</td>
</tr>
</tbody></table>
<p>💡 <strong>Snapshot 시스템의 역할:</strong></p>
<blockquote>
<p>상태를 읽고 쓰는 시점을 “스냅샷(사진)”처럼 캡처해서</p>
<p>변경과 참조를 정교하게 추적하고, 필요한 UI만 다시 그리게 한다.</p>
</blockquote>
<hr>
<h2 id="3️⃣-snapshot의-내부-핵심-개념">3️⃣ Snapshot의 내부 핵심 개념</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Snapshot</strong></td>
<td>상태 값의 일관된 시점(View)을 나타내는 객체</td>
</tr>
<tr>
<td><strong>SnapshotMutableState</strong></td>
<td>Compose의 <code>mutableStateOf()</code>가 내부적으로 사용하는 상태 래퍼</td>
</tr>
<tr>
<td><strong>SnapshotRecord</strong></td>
<td>상태 변경을 추적하기 위한 레코드(버전 정보 포함)</td>
</tr>
<tr>
<td><strong>SnapshotObserver</strong></td>
<td>어떤 상태를 읽었는지 관찰하고, 변경 시 재구성을 트리거</td>
</tr>
<tr>
<td><strong>ApplyScope</strong></td>
<td>상태 변경을 실제로 커밋하는 영역 (atomic commit)</td>
</tr>
</tbody></table>
<hr>
<h2 id="4️⃣-내부-구조-흐름">4️⃣ 내부 구조 흐름</h2>
<pre><code>[mutableStateOf(0)]
      │
      ▼
 SnapshotMutableState
      │
  ┌───┴───────────────────────────────────┐
  │  SnapshotRecord(version, value)       │
  │  SnapshotObserver: who read this?     │
  └───────────────────────────────────────┘
      │
      ▼
 Compose Runtime registers observer
 (so it knows which composable depends on this state)
</code></pre><hr>
<h2 id="5️⃣-상태-읽기read와-쓰기write-과정">5️⃣ 상태 읽기(Read)와 쓰기(Write) 과정</h2>
<h3 id="🧩-1-read-phase">🧩 (1) Read Phase</h3>
<ul>
<li><p>컴포저블이 <code>state.value</code>를 읽을 때,</p>
<p>  Snapshot Observer가 “이 컴포저블이 이 상태를 읽었다”고 기록합니다.</p>
</li>
</ul>
<pre><code class="language-kotlin">val count by remember { mutableStateOf(0) }
Text(&quot;Count: $count&quot;)
</code></pre>
<p>➡ 내부 동작:</p>
<pre><code>SnapshotObserver:
  Text() 컴포저블이 count 상태를 참조함
</code></pre><hr>
<h3 id="🧩-2-write-phase">🧩 (2) Write Phase</h3>
<ul>
<li><p><code>count++</code> 처럼 상태가 바뀌면</p>
<p>  Snapshot 시스템이 “해당 상태를 읽은 모든 구독자(컴포저블)”를 탐색합니다.</p>
</li>
<li><p>Compose Runtime이 그 구독자에 대해 <strong>Recomposition 요청</strong>을 보냅니다.</p>
</li>
</ul>
<p>➡ 즉, Snapshot이 변경을 감지하고</p>
<p>“누가 이 데이터를 보고 있었는지”를 알고 있기 때문에</p>
<p>필요한 부분만 정확히 다시 그려줍니다.</p>
<hr>
<h2 id="6️⃣-snapshot의-commit-cycle">6️⃣ Snapshot의 Commit Cycle</h2>
<p>Compose는 상태 변경을 트랜잭션처럼 처리합니다.</p>
<pre><code>┌───────────────────────────┐
│  1️⃣ Snapshot Open        │ ← 상태 변경 시작
│  2️⃣ Modify Record        │ ← 새 값 기록 (임시)
│  3️⃣ Apply (Commit)       │ ← 모든 변경을 일괄 반영
│  4️⃣ Notify Observers     │ ← 관련 컴포저블 재구성 요청
└───────────────────────────┘
</code></pre><p>이 덕분에 여러 상태가 동시에 바뀌어도</p>
<p>UI는 <strong>일관된 한 시점의 값만 본다.</strong></p>
<hr>
<h2 id="7️⃣-coroutine·runtime과의-관계">7️⃣ Coroutine·Runtime과의 관계</h2>
<p><code>rememberCoroutineScope()</code> 등에서 비동기로 상태를 바꾸는 경우도,</p>
<p>Snapshot은 <strong>코루틴 내부의 상태 변경까지 감지</strong>합니다.</p>
<pre><code class="language-kotlin">val scope = rememberCoroutineScope()
val state = remember { mutableStateOf(0) }

Button(onClick = { scope.launch { state.value++ } }) {
    Text(&quot;Click ${state.value}&quot;)
}
</code></pre>
<ul>
<li>코루틴이 백그라운드에서 <code>state.value++</code> 수행</li>
<li>Snapshot이 write operation을 감지</li>
<li>Compose Runtime이 관련 컴포저블을 재구성</li>
</ul>
<p>💡 즉, <strong>CoroutineScope는 실행의 단위</strong>,</p>
<p><strong>Snapshot은 상태 일관성 관리의 단위</strong>,</p>
<p>그리고 <strong>Runtime이 그 둘을 통합적으로 제어</strong>합니다.</p>
<hr>
<h2 id="8️⃣-전체-내부-흐름-요약">8️⃣ 전체 내부 흐름 요약</h2>
<pre><code>CoroutineScope.launch { state.value++ }   // suspend context에서 상태 변경
        │
        ▼
Snapshot Write detected by Runtime
        │
        ▼
Snapshot Commit → Notify Observers
        │
        ▼
Runtime marks affected Composables as invalid
        │
        ▼
Recomposition Scheduler executes → affected UI redraw
</code></pre><hr>
<h2 id="9️⃣-snapshot의-장점">9️⃣ Snapshot의 장점</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>일관성(Consistency)</strong></td>
<td>여러 상태를 동시에 변경해도 UI는 한 시점만 본다</td>
</tr>
<tr>
<td><strong>정확한 변경 감지</strong></td>
<td>어떤 UI가 어떤 상태를 읽었는지 추적</td>
</tr>
<tr>
<td><strong>부분적 재구성</strong></td>
<td>전체 UI 대신 필요한 컴포저블만 다시 그림</td>
</tr>
<tr>
<td><strong>스레드 안전성</strong></td>
<td>스냅샷 단위로 변경을 관리하여 병행 접근 안정화</td>
</tr>
<tr>
<td><strong>비동기 안정성</strong></td>
<td>Coroutine 등 비동기 변경도 안전히 반영</td>
</tr>
</tbody></table>
<hr>
<h2 id="비유로-이해하기">비유로 이해하기</h2>
<blockquote>
<p>Snapshot은 런타임의 타임머신입니다.</p>
<p>런타임이 매 순간 상태의 “사진(Snapshot)”을 찍어두고,</p>
<p>새로운 변경이 생기면</p>
<p>“이 사진을 참조하던 컴포넌트만 다시 갱신”하는 구조입니다.</p>
</blockquote>
<hr>
<h2 id="한-줄-요약">한 줄 요약</h2>
<blockquote>
<p>Snapshot은 Compose Runtime 내부의 상태 관리 핵심 엔진으로,</p>
<p>모든 <code>mutableStateOf</code> 값을 트랜잭션처럼 추적·커밋하며</p>
<p>필요한 컴포저블만 재구성되도록 보장한다.</p>
<p>즉, <strong>Compose의 반응형·일관된 UI를 가능하게 하는 런타임 레벨의 뇌(Brain)</strong> 이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose - 런타임, 코루틴 스코프, 바텀모달시트 관계]]></title>
            <link>https://velog.io/@subin_k/Jetpack-Compose-%EB%9F%B0%ED%83%80%EC%9E%84-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84-%EB%B0%94%ED%85%80%EB%AA%A8%EB%8B%AC%EC%8B%9C%ED%8A%B8-%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@subin_k/Jetpack-Compose-%EB%9F%B0%ED%83%80%EC%9E%84-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84-%EB%B0%94%ED%85%80%EB%AA%A8%EB%8B%AC%EC%8B%9C%ED%8A%B8-%EA%B4%80%EA%B3%84</guid>
            <pubDate>Sun, 09 Nov 2025 07:07:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1️⃣-compose-runtime이란">1️⃣ Compose Runtime이란?</h2>
<blockquote>
<p>Jetpack Compose의 UI 생명주기와 상태 추적을 관리하는 시스템입니다.</p>
</blockquote>
<ul>
<li>Compose는 <strong>선언형 UI</strong> → “UI = 상태(state)의 함수”</li>
<li>Compose Runtime은 내부적으로 <strong>상태 변경을 감지하고</strong>, 필요한 부분만 <strong>재구성(Recomposition)</strong> 합니다.</li>
<li><code>remember</code>나 <code>mutableStateOf()</code> 같은 것은 <strong>Compose Runtime이 상태를 추적하기 위한 장치</strong>예요.</li>
</ul>
<p>즉, Runtime은 “UI를 그려주고, 상태를 기억하고, 상태 변화에 맞춰 UI를 다시 그리는 관리자”입니다.</p>
<hr>
<h2 id="2️⃣-coroutinescope란">2️⃣ CoroutineScope란?</h2>
<blockquote>
<p>비동기 코드의 실행 범위를 정의하는 “작업 영역”입니다.</p>
</blockquote>
<pre><code class="language-kotlin">val scope = rememberCoroutineScope()
scope.launch {
    // 이 블록 안은 별도의 코루틴으로 실행
}
</code></pre>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Coroutine</strong></td>
<td>가벼운 스레드처럼 동작하는 비동기 작업 단위</td>
</tr>
<tr>
<td><strong>CoroutineScope</strong></td>
<td>여러 코루틴을 담고 관리하는 “컨테이너”</td>
</tr>
<tr>
<td><strong>launch</strong></td>
<td>새 코루틴을 실행하고, Scope가 살아있는 동안 유지됨</td>
</tr>
<tr>
<td><strong>cancel()</strong></td>
<td>Scope를 종료하면 내부 코루틴도 함께 종료됨</td>
</tr>
</tbody></table>
<hr>
<h2 id="3️⃣-remembercoroutinescope란">3️⃣ <code>rememberCoroutineScope()</code>란?</h2>
<p>Compose에 특화된 CoroutineScope로,</p>
<blockquote>
<p>현재 컴포저블이 런타임에 활성 상태일 동안만 살아 있는 코루틴 스코프를 제공합니다.</p>
</blockquote>
<p>즉:</p>
<ul>
<li>해당 스코프는 <strong>컴포저블이 화면에 있을 때만 유효</strong>합니다.</li>
<li>컴포저블이 사라지거나, 재구성 시 상태가 제거되면 → Scope도 자동으로 <strong>cancel()</strong> 됩니다.</li>
</ul>
<pre><code class="language-kotlin">val bottomSheetActionScope = rememberCoroutineScope()
</code></pre>
<p>✅ 이 스코프는 <strong>Compose Runtime이 자동으로 관리</strong>합니다.</p>
<p>UI가 사라지면 코루틴도 정리돼서 <strong>메모리 누수나 비정상 실행이 발생하지 않아요.</strong></p>
<hr>
<h2 id="4️⃣-bottomsheet에서의-동작-원리">4️⃣ BottomSheet에서의 동작 원리</h2>
<pre><code class="language-kotlin">val bottomSheetState = rememberModalBottomSheetState()
val bottomSheetActionScope = rememberCoroutineScope()

if (bottomSheetState.isVisible) {
    BottomSheetScreen(bottomSheetState) {
        bottomSheetActionScope.launch {
            bottomSheetState.hide()
        }
    }
}
</code></pre>
<p>이 구조를 세분화해서 보면 👇</p>
<h3 id="🧩-1-remembermodalbottomsheetstate">🧩 (1) <code>rememberModalBottomSheetState()</code></h3>
<ul>
<li>Compose Runtime이 <strong>BottomSheet의 상태(열림/닫힘)</strong> 을 추적함.</li>
<li>이 상태는 remember로 “화면 내에서 유지되는 상태”가 됨.</li>
</ul>
<h3 id="🧩-2-remembercoroutinescope">🧩 (2) <code>rememberCoroutineScope()</code></h3>
<ul>
<li><strong>현재 컴포저블의 수명에 종속된 CoroutineScope</strong> 생성.</li>
<li>BottomSheet가 <strong>화면에 있는 동안만</strong> 유지됨.</li>
</ul>
<h3 id="🧩-3-launch--bottomsheetstatehide-">🧩 (3) <code>launch { bottomSheetState.hide() }</code></h3>
<ul>
<li>BottomSheetState의 <code>hide()</code> 함수는 <strong>suspend function</strong> (일시 중단 가능 함수)이므로,</li>
<li>반드시 <strong>코루틴 안에서 호출</strong>해야 함. (그래서 <code>launch</code> 필요)</li>
<li>이 코루틴은 <strong>bottomSheetActionScope</strong>에서 실행됨 → 즉, Compose 런타임이 관리하는 Scope 내부에서 실행됨.</li>
</ul>
<hr>
<h2 id="5️⃣-모달시트가-안보일-때-코루틴을-닫는다는-말의-의미">5️⃣ “모달시트가 안보일 때 코루틴을 닫는다”는 말의 의미</h2>
<p>이건 매우 정확한 표현이에요.</p>
<p>Compose의 런타임은 다음과 같은 원리로 동작합니다:</p>
<table>
<thead>
<tr>
<th>상태 변화</th>
<th>Compose 런타임 동작</th>
<th>CoroutineScope 동작</th>
</tr>
</thead>
<tbody><tr>
<td>모달시트가 보임 (<code>isVisible = true</code>)</td>
<td>해당 UI 트리(컴포저블)가 활성화됨</td>
<td><code>rememberCoroutineScope()</code>가 활성 상태</td>
</tr>
<tr>
<td>모달시트가 닫힘 (<code>isVisible = false</code>)</td>
<td>BottomSheetScreen 컴포저블이 <strong>Composition Tree에서 제거</strong>됨</td>
<td><strong>Scope 자동 cancel()</strong> → 코루틴도 함께 종료됨</td>
</tr>
</tbody></table>
<p>즉,</p>
<ul>
<li>모달시트가 사라지는 순간 → 해당 Compose UI는 더 이상 렌더링 대상이 아님</li>
<li>이때 Compose 런타임이 자동으로 <strong>remember로 생성된 코루틴 스코프를 dispose</strong></li>
<li>내부에서 실행 중이던 코루틴도 함께 <strong>cancel</strong>되어 종료됩니다.</li>
</ul>
<blockquote>
<p>💡 즉, “모달시트가 닫히면 코루틴을 끊는다”는 건</p>
<p>Compose Runtime이 해당 코루틴 스코프를 자동으로 정리(cancellation)한다는 뜻이에요.</p>
</blockquote>
<hr>
<h2 id="6️⃣-compose-runtime-↔-coroutine-관계-요약">6️⃣ Compose Runtime ↔ Coroutine 관계 요약</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
<th>관리 주체</th>
</tr>
</thead>
<tbody><tr>
<td><code>remember</code></td>
<td>상태를 런타임에 저장</td>
<td>Compose Runtime</td>
</tr>
<tr>
<td><code>mutableStateOf</code></td>
<td>상태 변화를 감지해 재구성 트리거</td>
<td>Compose Runtime</td>
</tr>
<tr>
<td><code>rememberCoroutineScope()</code></td>
<td>코루틴 스코프를 Compose 수명주기에 맞게 관리</td>
<td>Compose Runtime</td>
</tr>
<tr>
<td><code>launch { ... }</code></td>
<td>비동기 실행 블록</td>
<td>CoroutineScope</td>
</tr>
<tr>
<td><code>hide()</code> / <code>show()</code></td>
<td>suspend 함수 → 코루틴 내에서만 실행 가능</td>
<td>Coroutine</td>
</tr>
</tbody></table>
<hr>
<h2 id="7️⃣-전체-흐름-다이어그램">7️⃣ 전체 흐름 다이어그램</h2>
<pre><code>[Compose Runtime]
    │
    ├─ rememberModalBottomSheetState()
    │     ↓ (상태 저장 및 추적)
    │
    ├─ rememberCoroutineScope()
    │     ↓ (UI 수명에 맞는 코루틴 스코프 생성)
    │
    └─ launch { bottomSheetState.hide() }
          ↓
     suspend fun hide() → BottomSheetState 애니메이션 비동기 처리
          ↓
     BottomSheet 사라짐 → Composition Tree에서 제거
          ↓
     rememberCoroutineScope() 자동 cancel()
</code></pre><hr>
<h2 id="8️⃣-정리-compose에서-코루틴이-작용하는-방식">8️⃣ 정리: Compose에서 코루틴이 작용하는 방식</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>코루틴은 Compose 런타임이 제어하는 생명주기 내에서 작동한다.</strong></td>
<td>UI가 사라지면 Scope도 함께 cancel됨</td>
</tr>
<tr>
<td><strong><code>rememberCoroutineScope()</code>는 UI 생명주기 의존형 스코프이다.</strong></td>
<td>UI와 함께 생성되고 파괴됨</td>
</tr>
<tr>
<td><strong>Suspend 함수는 반드시 코루틴 내에서 호출해야 한다.</strong></td>
<td>예: <code>bottomSheetState.show()</code>, <code>hide()</code></td>
</tr>
<tr>
<td><strong>런타임이 코루틴을 직접 관리한다.</strong></td>
<td>불필요한 코루틴이 남지 않도록 자동 정리</td>
</tr>
</tbody></table>
<hr>
<h2 id="9️⃣-예시로-정리하면">9️⃣ 예시로 정리하면</h2>
<blockquote>
<p>“BottomSheet가 떠 있는 동안만 CoroutineScope가 유지되고,</p>
<p>닫히면 Compose Runtime이 자동으로 Scope를 정리한다.”</p>
</blockquote>
<pre><code class="language-kotlin">val sheetState = rememberModalBottomSheetState()
val sheetScope = rememberCoroutineScope()

BottomSheetScreen(sheetState) {
    sheetScope.launch {
        sheetState.hide() // suspend 함수 → 코루틴 내에서 실행
    }
}
</code></pre>
<p>✔️ 코루틴은 <strong>런타임이 UI 생명주기에 맞춰 관리</strong></p>
<p>✔️ <code>hide()</code> 수행 도중 UI가 사라지면 → 런타임이 Scope를 cancel → 코루틴 즉시 종료</p>
<hr>
<h2 id="한줄-요약">한줄 요약</h2>
<blockquote>
<p>Compose Runtime은 상태와 UI 생명주기를 관리하고,</p>
<p><strong>rememberCoroutineScope()</strong>는 그 생명주기에 맞춰 자동으로 생성·소멸되는 <strong>코루틴 실행 컨테이너</strong>이다.</p>
<p><strong>즉, BottomSheet가 닫히면 Scope가 cancel되어 코루틴도 함께 종료된다.</strong></p>
</blockquote>
<h2 id="비유로-이해하기">비유로 이해하기</h2>
<blockquote>
<p>Compose Runtime은 무대 감독, CoroutineScope는 조명 팀과 같아요.</p>
<p>무대(컴포저블)가 열리면 조명이 켜지고 (코루틴 실행),</p>
<p>무대가 닫히면 감독(Runtime)이 조명 팀에게 “끄자(cancel)” 하고 조명을 종료시킵니다.</p>
<p>즉, <strong>무대가 사라지면(모달시트 닫힘)</strong> 코루틴도 함께 끝납니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose - hoisting, 재구성]]></title>
            <link>https://velog.io/@subin_k/jetpack-compose-hoisting-%EC%9E%AC%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@subin_k/jetpack-compose-hoisting-%EC%9E%AC%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sun, 09 Nov 2025 07:03:47 GMT</pubDate>
            <description><![CDATA[<h1 id="jetpack-compose-상태-관리-정리">Jetpack Compose 상태 관리 정리</h1>
<h3 id="remember-·-state-hoisting-·-recomposition-관계"><code>remember</code> · State Hoisting · Recomposition 관계</h3>
<hr>
<h2 id="1️⃣-remember란">1️⃣ <code>remember</code>란?</h2>
<blockquote>
<p>재구성(Recomposition) 중에도 값을 유지하기 위한 “상태 저장 장치”</p>
</blockquote>
<pre><code class="language-kotlin">var count by remember { mutableStateOf(0) }
</code></pre>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>remember</code></td>
<td>컴포저블 함수가 재호출되어도 값을 잊지 않게 함</td>
</tr>
<tr>
<td><code>mutableStateOf</code></td>
<td>값이 변하면 해당 상태를 참조하는 UI만 자동으로 다시 그림</td>
</tr>
<tr>
<td>효과</td>
<td>재구성 시에도 상태가 초기화되지 않고 유지됨</td>
</tr>
</tbody></table>
<hr>
<h2 id="2️⃣-recomposition이란">2️⃣ Recomposition이란?</h2>
<blockquote>
<p>상태(State)가 변할 때, 해당 상태를 참조하는 UI만 다시 그리는 과정</p>
</blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>발생 시점</td>
<td><code>mutableStateOf()</code>로 관리되는 값이 변경될 때</td>
</tr>
<tr>
<td>동작 방식</td>
<td>Compose Runtime이 자동으로 UI 부분만 업데이트</td>
</tr>
<tr>
<td>예시</td>
<td><code>count++</code> 시 → <code>Text(&quot;Clicked $count&quot;)</code> 부분만 새로 그림</td>
</tr>
</tbody></table>
<hr>
<h2 id="3️⃣-부모에-remember를-선언한다는-뜻은">3️⃣ “부모에 remember를 선언한다”는 뜻은?</h2>
<blockquote>
<p>“상태를 자식이 아니라 부모 쪽에서 기억하고 관리한다”는 의미입니다.</p>
<p>이렇게 하면 부모가 상태를 제어하고, 자식은 단순히 “UI 표현 + 이벤트 전달”만 담당합니다.</p>
</blockquote>
<hr>
<h3 id="비교-예시-①-❌-비권장-자식-내부에-remember-선언">비교 예시 ① ❌ 비권장: 자식 내부에 remember 선언</h3>
<pre><code class="language-kotlin">@Composable
fun Child() {
    var text by remember { mutableStateOf(&quot;&quot;) } // 자식이 직접 상태 관리
    TextField(
        value = text,
        onValueChange = { text = it }
    )
}
</code></pre>
<p>📍 문제점:</p>
<ul>
<li>자식이 상태를 직접 들고 있어 부모가 제어 불가</li>
<li>다른 자식과 상태 공유 불가</li>
<li>재사용성과 테스트성이 떨어짐</li>
</ul>
<hr>
<h3 id="비교-예시-②-✅-권장-부모에-remember-선언-state-hoisting">비교 예시 ② ✅ 권장: 부모에 remember 선언 (State Hoisting)</h3>
<pre><code class="language-kotlin">@Composable
fun Parent() {
    var text by remember { mutableStateOf(&quot;&quot;) } // 부모가 상태를 기억하고 관리
    Child(
        value = text,
        onValueChange = { text = it }
    )
}

@Composable
fun Child(value: String, onValueChange: (String) -&gt; Unit) {
    TextField(
        value = value,
        onValueChange = onValueChange
    )
}
</code></pre>
<p>📍 설명:</p>
<ul>
<li><code>remember</code>는 부모에 선언 → 부모가 상태를 기억</li>
<li>자식은 단순히 <strong>값을 표시하고 이벤트를 부모로 전달</strong></li>
<li>사용자가 입력 시 → <code>onValueChange</code> 콜백 → 부모의 상태(<code>text</code>) 변경 → UI 자동 업데이트</li>
</ul>
<hr>
<h2 id="4️⃣-state-hoisting-상태-끌어올리기">4️⃣ State Hoisting (상태 끌어올리기)</h2>
<blockquote>
<p>자식이 직접 상태를 들고 있지 않고,</p>
<p>부모가 상태를 관리하며 자식에게 <strong>값과 이벤트 콜백</strong>을 전달하는 패턴</p>
</blockquote>
<table>
<thead>
<tr>
<th>비교</th>
<th>자식 내부 상태</th>
<th>부모 상태 Hoisting</th>
</tr>
</thead>
<tbody><tr>
<td>선언 위치</td>
<td>자식 내부에서 <code>remember</code> 사용</td>
<td>부모에서 <code>remember</code> 사용</td>
</tr>
<tr>
<td>제어 권한</td>
<td>자식만 제어 가능</td>
<td>부모가 제어 가능</td>
</tr>
<tr>
<td>재사용성</td>
<td>낮음</td>
<td>높음</td>
</tr>
<tr>
<td>데이터 흐름</td>
<td>자식 내부 순환</td>
<td>부모 → 자식 → 부모 (단방향)</td>
</tr>
</tbody></table>
<hr>
<h2 id="5️⃣-코드-예시-counter-구조">5️⃣ 코드 예시 (Counter 구조)</h2>
<pre><code class="language-kotlin">@Composable
fun ParentCounter() {
    var count by remember { mutableStateOf(0) }
    ChildCounter(
        count = count,
        onCountChange = { count = it }
    )
}

@Composable
fun ChildCounter(count: Int, onCountChange: (Int) -&gt; Unit) {
    Button(onClick = { onCountChange(count + 1) }) {
        Text(&quot;Clicked $count times&quot;)
    }
}
</code></pre>
<p>💡 <strong>핵심 요약:</strong></p>
<p>부모는 상태를 “기억(remember)”하고</p>
<p>자식은 “표현(UI)”과 “이벤트 전달”만 담당한다.</p>
<hr>
<h2 id="6️⃣-데이터-흐름-구조">6️⃣ 데이터 흐름 구조</h2>
<pre><code>┌──────────────────────┐
│      Parent()        │
│  var count by remember│
│  mutableStateOf(0)   │
│          │            │
│          ▼            │
│  ChildCounter(count,  │
│     onCountChange)    │
└──────────┬────────────┘
           │
           ▼
  Button → onClick() → 부모의 상태 업데이트
</code></pre><blockquote>
<p>🔁 부모가 상태를 기억하고, 자식은 상태를 표시 및 이벤트만 전달</p>
<p>→ “단방향 데이터 흐름 (Unidirectional Data Flow)” 완성</p>
</blockquote>
<hr>
<h2 id="7️⃣-왜-부모에-remember를-선언해야-할까">7️⃣ 왜 부모에 remember를 선언해야 할까?</h2>
<table>
<thead>
<tr>
<th>이유</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>① 상태의 단일 출처 (Single Source of Truth)</strong></td>
<td>한 상태를 여러 자식이 공유할 수 있음</td>
</tr>
<tr>
<td><strong>② 재구성 시 상태 유지</strong></td>
<td>부모가 상태를 기억하므로 자식이 새로 생성되어도 값 유지</td>
</tr>
<tr>
<td><strong>③ 예측 가능한 흐름 (UDF)</strong></td>
<td>데이터가 항상 부모 → 자식으로 흐름</td>
</tr>
</tbody></table>
<hr>
<h2 id="8️⃣-확장-예시-부모-remember--자식-분리">8️⃣ 확장 예시 (부모 remember + 자식 분리)</h2>
<pre><code class="language-kotlin">@Composable
fun Parent() {
    var count by remember { mutableStateOf(0) }   // 부모 remember 선언

    Column {
        ChildDisplay(count = count)
        ChildButton(onIncrease = { count++ })
    }
}

@Composable
fun ChildDisplay(count: Int) {
    Text(&quot;Count: $count&quot;)
}

@Composable
fun ChildButton(onIncrease: () -&gt; Unit) {
    Button(onClick = onIncrease) {
        Text(&quot;Increase&quot;)
    }
}
</code></pre>
<p>📍 이 구조의 장점</p>
<ul>
<li><strong>상태는 부모가 기억</strong></li>
<li><strong>자식은 Dumb UI (표시 + 콜백만 처리)</strong></li>
<li><strong>Recomposition 최소화</strong> → 성능 효율적</li>
</ul>
<hr>
<h2 id="9️⃣-요약-부모-remember-선언의-의미">9️⃣ 요약: “부모 remember 선언”의 의미</h2>
<table>
<thead>
<tr>
<th>관점</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>기능적 관점</strong></td>
<td><code>remember</code>로 선언된 변수는 재구성 중에도 유지되는 상태 저장소</td>
</tr>
<tr>
<td><strong>구조적 관점</strong></td>
<td>부모가 이 상태를 기억하면, 자식은 “상태를 사용하는 UI”가 됨</td>
</tr>
<tr>
<td><strong>데이터 흐름 관점</strong></td>
<td>부모 → 자식(값 전달), 자식 → 부모(이벤트 콜백)</td>
</tr>
<tr>
<td><strong>Compose 철학</strong></td>
<td>“UI는 상태(State)의 함수이다.” → <code>UI = f(state)</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="🔟-정리-요약표">🔟 정리 요약표</h2>
<table>
<thead>
<tr>
<th>키워드</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>remember</code></td>
<td>상태를 기억 (재구성 중에도 유지)</td>
<td><code>var text by remember { mutableStateOf(&quot;&quot;) }</code></td>
</tr>
<tr>
<td><code>mutableStateOf</code></td>
<td>값이 변하면 자동으로 UI 업데이트</td>
<td><code>count++</code> 시 UI 자동 갱신</td>
</tr>
<tr>
<td><strong>State Hoisting</strong></td>
<td>상태를 부모로 끌어올려 제어 일원화</td>
<td><code>Parent()</code>가 상태 기억, <code>Child()</code>는 UI 표현</td>
</tr>
<tr>
<td><strong>UDF (단방향 데이터 흐름)</strong></td>
<td>상태 → UI → 이벤트 → 상태 순환</td>
<td>부모→자식→부모 구조</td>
</tr>
</tbody></table>
<hr>
<h2 id="비유로-이해하기">비유로 이해하기</h2>
<blockquote>
<p>부모가 상태를 “기억”하는 건,</p>
<p>“노트북에서 문서 파일을 열고, 여러 탭(자식들)에서 같은 문서를 공유하는 것”과 같아요.</p>
<p>파일(상태)은 하나지만, 탭(UI)은 여러 개일 수 있고,</p>
<p>부모(노트북)는 항상 최신 내용을 기억하고 있습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - Coroutine Flow]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-Coroutine-Flow</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-Coroutine-Flow</guid>
            <pubDate>Wed, 22 Oct 2025 04:56:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>비동기 데이터를 다루는 가장 코틀린스러운 방법.</p>
</blockquote>
<hr>
<h2 id="1️⃣-flow란">1️⃣ Flow란?</h2>
<p><code>Flow</code>는 <strong>비동기적으로 여러 값을 순차적으로 내보내는(emit) 데이터 스트림</strong>이에요.</p>
<p>데이터를 한 번에 다 가져오지 않고, <strong>필요할 때마다 값을 흘려보내는 구조</strong>라고 보면 됩니다.</p>
<hr>
<h2 id="2️⃣-cold-flow-vs-hot-flow">2️⃣ Cold Flow vs Hot Flow</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Cold Flow</strong></td>
<td><code>collect</code>가 호출될 때 비로소 실행됨</td>
<td><code>flow { emit() }</code>, <code>asFlow()</code></td>
</tr>
<tr>
<td><strong>Hot Flow</strong></td>
<td>구독 여부와 상관없이 계속 동작</td>
<td><code>StateFlow</code>, <code>SharedFlow</code></td>
</tr>
</tbody></table>
<p>→ 대부분의 경우 Cold Flow로 충분하며,</p>
<p>Hot Flow는 <strong>UI 상태 관리(ViewModel)</strong> 나 <strong>이벤트 전달</strong>용으로 사용됩니다.</p>
<hr>
<h2 id="3️⃣-flow의-기본-구조-생산자중개자소비자">3️⃣ Flow의 기본 구조 (생산자–중개자–소비자)</h2>
<p>Flow는 데이터의 “흐름(stream)”을 구성하는 3단계로 이루어져 있어요.</p>
<table>
<thead>
<tr>
<th>역할</th>
<th>키워드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>생산자 (Producer)</strong></td>
<td><code>flow { emit(...) }</code></td>
<td>데이터를 생성하고 내보냄</td>
</tr>
<tr>
<td><strong>중개자 (Intermediate Operator)</strong></td>
<td><code>map</code>, <code>filter</code>, <code>onEach</code></td>
<td>데이터를 가공하거나 필터링</td>
</tr>
<tr>
<td><strong>소비자 (Consumer)</strong></td>
<td><code>collect { ... }</code></td>
<td>최종 데이터를 받아 처리</td>
</tr>
</tbody></table>
<pre><code class="language-kotlin">flow {
    emit(1) // 생산자: 데이터 방출
    emit(2)
    emit(3)
}.filter { it % 2 == 1 } // 중개자: 홀수만 통과
 .map { &quot;값: $it&quot; }     // 중개자: 형식 변경
 .collect { println(it) } // 소비자: 최종 처리
</code></pre>
<p>출력:</p>
<pre><code>값: 1
값: 3</code></pre><hr>
<h2 id="4️⃣-emit이란">4️⃣ emit이란?</h2>
<p><code>emit()</code>은 Flow 내부에서 <strong>데이터를 방출(내보내는)</strong> 함수예요.</p>
<p>생산자가 <code>emit()</code>으로 값을 보내고, 소비자는 <code>collect()</code>로 받습니다.</p>
<pre><code class="language-kotlin">val flow = flow {
    emit(&quot;첫 번째 데이터&quot;)
    emit(&quot;두 번째 데이터&quot;)
}
</code></pre>
<blockquote>
<p>즉, emit = 생산자 → 소비자 방향으로 “데이터 신호를 보냄”</p>
</blockquote>
<hr>
<h2 id="5️⃣-flow와-스레드-flowon">5️⃣ Flow와 스레드 (flowOn)</h2>
<p>기본적으로 Flow는 <strong>collect가 실행되는 코루틴 컨텍스트</strong>에서 동작합니다.</p>
<p>데이터 생성은 백그라운드에서, 수집은 UI 스레드에서 하고 싶다면 <code>flowOn()</code>을 사용합니다.</p>
<pre><code class="language-kotlin">flow {
    emit(loadData()) // I/O
}.flowOn(Dispatchers.IO)
 .collect { updateUI(it) } // Main
</code></pre>
<hr>
<h2 id="6️⃣-hot-flow-상태-관리용-flow">6️⃣ Hot Flow (상태 관리용 Flow)</h2>
<h3 id="🟢-stateflow">🟢 StateFlow</h3>
<ul>
<li>항상 <strong>하나의 최신 상태</strong>를 유지</li>
<li>새로운 구독자도 <strong>즉시 현재 값 수신</strong></li>
<li><code>LiveData</code> 대체로 <strong>UI 상태 관리</strong>에 자주 사용</li>
</ul>
<pre><code class="language-kotlin">private val _uiState = MutableStateFlow(&quot;Loading&quot;)
val uiState: StateFlow&lt;String&gt; = _uiState

_uiState.value = &quot;Success&quot;
</code></pre>
<hr>
<h3 id="🟣-sharedflow">🟣 SharedFlow</h3>
<ul>
<li>여러 구독자가 <strong>같은 이벤트를 공유</strong></li>
<li><code>Snackbar</code>, <code>Toast</code>, <code>Navigation 이벤트</code> 등 단발성 알림에 적합</li>
</ul>
<pre><code class="language-kotlin">private val _event = MutableSharedFlow&lt;String&gt;()
val event = _event.asSharedFlow()

viewModelScope.launch {
    _event.emit(&quot;데이터 저장 완료!&quot;)
}
</code></pre>
<hr>
<h2 id="7️⃣-mutablelistflow-패턴-리스트-상태-관리">7️⃣ MutableListFlow 패턴 (리스트 상태 관리)</h2>
<p><code>MutableListFlow</code>는 관용적으로 부르는 이름으로,</p>
<p>정식 타입은 <code>MutableStateFlow&lt;List&lt;T&gt;&gt;</code>입니다.</p>
<p>리스트를 상태로 관리할 때 자주 쓰입니다.</p>
<pre><code class="language-kotlin">private val _items = MutableStateFlow&lt;List&lt;String&gt;&gt;(emptyList())
val items: StateFlow&lt;List&lt;String&gt;&gt; = _items

fun addItem(newItem: String) {
    _items.value = _items.value + newItem
}
</code></pre>
<blockquote>
<p>기존 리스트를 수정하는 대신 새 리스트로 교체해야 UI가 갱신됩니다.</p>
</blockquote>
<hr>
<h2 id="✨-요약">✨ 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>Flow</code></td>
<td>비동기 데이터 스트림</td>
</tr>
<tr>
<td><code>emit()</code></td>
<td>Flow 내부에서 데이터 방출</td>
</tr>
<tr>
<td><code>collect()</code></td>
<td>Flow의 데이터를 수집</td>
</tr>
<tr>
<td><code>flowOn()</code></td>
<td>생산자 스레드 변경</td>
</tr>
<tr>
<td><code>StateFlow</code></td>
<td>UI 상태 관리용 Hot Flow</td>
</tr>
<tr>
<td><code>SharedFlow</code></td>
<td>이벤트 전달용 Hot Flow</td>
</tr>
<tr>
<td><code>MutableListFlow</code></td>
<td>리스트 형태의 상태 관리 패턴</td>
</tr>
</tbody></table>
<h2 id="안드로이드-개발에서-flow를-쓰는-이유">안드로이드 개발에서 Flow를 쓰는 이유</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>권장 Flow 타입</th>
</tr>
</thead>
<tbody><tr>
<td>네트워크/DB 데이터 스트림</td>
<td><code>flow {}</code> / <code>asFlow()</code></td>
</tr>
<tr>
<td>UI 상태(State 관리)</td>
<td><code>StateFlow</code></td>
</tr>
<tr>
<td>이벤트(단발성 알림, SnackBar 등)</td>
<td><code>SharedFlow</code></td>
</tr>
<tr>
<td>Room 데이터베이스</td>
<td><code>Flow&lt;List&lt;T&gt;&gt;</code> (자동 업데이트)</td>
</tr>
</tbody></table>
<h2 id="핫-플로우를-꼭-알아야-하는-이유">핫 플로우를 꼭 알아야 하는 이유</h2>
<ul>
<li><p><strong>안드로이드 ViewModel</strong>에서 UI 상태를 노출할 때 거의 표준처럼 사용됨</p>
<p>  (<code>LiveData</code>보다 더 강력하고 코루틴 친화적)</p>
</li>
<li><p>Jetpack Compose의 <strong><code>collectAsState()</code></strong> 와 완벽하게 호환됨</p>
<p>  → Compose 전환 시 필수 개념</p>
</li>
<li><p>이벤트 처리(SharedFlow)는 UI 피드백 구현 시 자주 등장</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 컬렉션 기본 문법( List, Set, Map)]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%BB%AC%EB%A0%89%EC%85%98-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-List-Set-Map</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%BB%AC%EB%A0%89%EC%85%98-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-List-Set-Map</guid>
            <pubDate>Thu, 16 Oct 2025 10:15:31 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-문법---컬렉션-기본-문법-list-set-map">Kotlin 문법 - 컬렉션 기본 문법( List, Set, Map)</h1>
<p>Kotlin 컬렉션은 <strong>List / Set / Map</strong>으로 나뉘며, 각각 <strong>읽기 전용</strong>과 <strong>가변</strong> 형태가 있다.</p>
<ul>
<li>읽기 전용: <code>List&lt;T&gt;</code>, <code>Set&lt;T&gt;</code>, <code>Map&lt;K, V&gt;</code></li>
<li>가변: <code>MutableList&lt;T&gt;</code>, <code>MutableSet&lt;T&gt;</code>, <code>MutableMap&lt;K, V&gt;</code></li>
</ul>
<blockquote>
<p>읽기 전용은 인터페이스 제약일 뿐, 객체가 절대 변하지 않는다는 의미의 “진짜 불변”을 보장하지 않는다.</p>
</blockquote>
<hr>
<h2 id="1-list--mutablelist">1) List / MutableList</h2>
<ul>
<li><code>List</code>: 순서가 있는 목록, 추가·삭제 불가</li>
<li><code>MutableList</code>: 순서가 있는 목록, 추가·삭제 가능</li>
</ul>
<pre><code class="language-kotlin">val l: List&lt;Int&gt; = listOf(1, 2, 3)
val m: MutableList&lt;Int&gt; = mutableListOf(1, 2, 3)

println(l[0])   // 인덱스 접근
println(m.size) // 크기 조회

m.add(4)
m.remove(2)
println(m)      // [1, 3, 4]
</code></pre>
<h3 id="arraylist-참고">ArrayList 참고</h3>
<ul>
<li><code>ArrayList&lt;T&gt;</code>는 구현체 이름이다.</li>
<li>JVM에서 <code>mutableListOf()</code>는 보통 <code>ArrayList</code> 기반으로 생성된다.</li>
</ul>
<pre><code class="language-kotlin">val a: ArrayList&lt;String&gt; = arrayListOf(&quot;A&quot;, &quot;B&quot;)
val b: MutableList&lt;String&gt; = mutableListOf(&quot;A&quot;, &quot;B&quot;)
</code></pre>
<hr>
<h2 id="2-linkedlist">2) LinkedList</h2>
<ul>
<li>노드들이 포인터로 연결된 연결 리스트 개념의 컬렉션</li>
<li>Kotlin 표준 팩토리는 없으며 JVM에선 <code>java.util.LinkedList</code>를 직접 사용</li>
</ul>
<pre><code class="language-kotlin">val ll = java.util.LinkedList&lt;Int&gt;()
ll.addFirst(10)
ll.addLast(20)
println(ll) // [10, 20]
</code></pre>
<hr>
<h2 id="3-set--mutableset">3) Set / MutableSet</h2>
<ul>
<li>중복 없는 집합</li>
</ul>
<pre><code class="language-kotlin">val s: Set&lt;Int&gt; = setOf(1, 2, 2, 3) // {1, 2, 3}
val ms: MutableSet&lt;Int&gt; = mutableSetOf(1, 2)
ms.add(2)  // 변화 없음
ms.add(3)  // {1, 2, 3}
</code></pre>
<p>자주 쓰는 구현체</p>
<ul>
<li><code>HashSet</code> : 일반 집합</li>
<li><code>LinkedHashSet</code> : 삽입 순서 유지</li>
<li><code>TreeSet</code> : 항상 정렬 상태 유지</li>
</ul>
<pre><code class="language-kotlin">val sorted = sortedSetOf(3, 1, 2) // TreeSet 기반
println(sorted) // [1, 2, 3]
</code></pre>
<hr>
<h2 id="4-map--mutablemap">4) Map / MutableMap</h2>
<ul>
<li>Key → Value 쌍을 보관, Key 중복 시 덮어씀</li>
</ul>
<pre><code class="language-kotlin">val m1: Map&lt;String, Int&gt; = mapOf(&quot;A&quot; to 1, &quot;B&quot; to 2)
val m2: MutableMap&lt;String, Int&gt; = mutableMapOf(&quot;A&quot; to 1)

m2[&quot;B&quot;] = 2         // 추가/수정
println(m2[&quot;A&quot;])    // 조회
m2.remove(&quot;A&quot;)      // 삭제
</code></pre>
<p>자주 쓰는 구현체</p>
<ul>
<li><code>HashMap</code> : 일반 맵</li>
<li><code>LinkedHashMap</code> : 삽입 순서 유지</li>
<li><code>TreeMap</code> : Key 기준 정렬 유지</li>
</ul>
<pre><code class="language-kotlin">val sortedMap = sortedMapOf(2 to &quot;two&quot;, 1 to &quot;one&quot;)
println(sortedMap) // {1=one, 2=two}
</code></pre>
<hr>
<h2 id="5-정렬-유지-컬렉션">5) 정렬 유지 컬렉션</h2>
<p>항상 정렬 규칙을 유지해야 할 때 <code>TreeSet</code>과 <code>TreeMap</code>을 사용한다. 요소의 자연 순서나 <code>Comparator</code>로 기준을 지정한다.</p>
<pre><code class="language-kotlin">data class Item(val name: String, val price: Int)

val byPrice = java.util.TreeSet&lt;Item&gt;(
    compareBy(Item::price).thenBy(Item::name)
)

byPrice.add(Item(&quot;A&quot;, 1200))
byPrice.add(Item(&quot;B&quot;, 800))
println(byPrice) // 가격→이름 순으로 정렬된 상태로 보관
</code></pre>
<hr>
<h2 id="6-선택-가이드">6) 선택 가이드</h2>
<ul>
<li>읽기 전용 목록이 필요하면 <code>listOf</code></li>
<li>추가·삭제되는 목록이면 <code>mutableListOf</code></li>
<li>중복 없이 모으려면 <code>mutableSetOf</code> 또는 <code>HashSet</code></li>
<li>키로 값을 관리하려면 <code>mutableMapOf</code> 또는 <code>HashMap</code></li>
<li>항상 정렬 상태가 필요하면 <code>sortedSetOf</code> 또는 <code>sortedMapOf</code></li>
<li>삽입 순서를 유지하며 보고 싶으면 <code>LinkedHashSet</code>, <code>LinkedHashMap</code></li>
</ul>
<hr>
<h2 id="7-컬렉션-확장-함수">7) 컬렉션 확장 함수</h2>
<pre><code class="language-kotlin">val nums = listOf(1, 2, 3, 4, 5)

val even = nums.filter { it % 2 == 0 } // [2, 4]
val doubled = nums.map { it * 2 }      // [2, 4, 6, 8, 10]

nums.forEach { println(it) }           // 요소 순회
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - inline, crossinline, noinlin]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-inline-crossinline-noinlin</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-inline-crossinline-noinlin</guid>
            <pubDate>Wed, 15 Oct 2025 04:37:23 GMT</pubDate>
            <description><![CDATA[<h3 id="inline-noinline-crossinline-세-가지-키워드의-역할과-차이"><code>inline</code>, <code>noinline</code>, <code>crossinline</code> 세 가지 키워드의 역할과 차이</h3>
<hr>
<h2 id="1️⃣-inline-이란">1️⃣ <code>inline</code> 이란?</h2>
<blockquote>
<p>“함수 호출을 인라인(복붙) 하여 호출 오버헤드를 줄이는 키워드”</p>
</blockquote>
<h3 id="기본-개념">기본 개념</h3>
<ul>
<li><p>일반 함수는 <strong>호출 시 스택에 함수를 넣고 실행</strong>하지만,</p>
<p>  <code>inline</code> 함수는 <strong>컴파일 시 함수 본문을 호출 위치에 그대로 삽입</strong>합니다.</p>
</li>
<li><p>즉, <strong>함수 호출 비용(call overhead)</strong> 을 제거해줍니다.</p>
</li>
</ul>
<h3 id="✅-예시">✅ 예시</h3>
<pre><code class="language-kotlin">inline fun repeatTwice(action: () -&gt; Unit) {
    action()
    action()
}

fun main() {
    repeatTwice { println(&quot;Hi&quot;) }
}
</code></pre>
<p>컴파일 시 내부적으로 이렇게 바뀜 👇</p>
<p>(즉, 함수 호출 자체가 사라집니다.)</p>
<pre><code class="language-kotlin">fun main() {
    println(&quot;Hi&quot;)
    println(&quot;Hi&quot;)
}
</code></pre>
<p><strong>효과</strong></p>
<ul>
<li>성능 최적화 (람다 호출 오버헤드 제거)</li>
<li>비지역 반환(non-local return) 가능 (람다 안에서 <code>return</code>하면 바깥 함수 종료)</li>
</ul>
<hr>
<h2 id="2️⃣-noinline-이란">2️⃣ <code>noinline</code> 이란?</h2>
<blockquote>
<p>“인라인 함수 안에서 특정 람다 파라미터만 인라인하지 않도록 예외 처리”</p>
</blockquote>
<h3 id="사용-배경">사용 배경</h3>
<ul>
<li><p><code>inline</code> 함수 안에서는 기본적으로 <strong>모든 람다 파라미터가 인라인됨</strong>.</p>
</li>
<li><p>하지만 람다를 <strong>나중에 저장하거나 다른 함수에 전달</strong>해야 한다면,</p>
<p>  인라인되면 안 되므로 <code>noinline</code>을 붙여야 함.</p>
</li>
<li><p>객체 생성시 <code>noinline</code>이 붙는다면 선언할때마다 계속 생성함</p>
</li>
</ul>
<h3 id="✅-예시-1">✅ 예시</h3>
<pre><code class="language-kotlin">inline fun doSomething(
    inlineBlock: () -&gt; Unit,
    noinline normalBlock: () -&gt; Unit
) {
    inlineBlock()           // 인라인됨
    val ref = normalBlock   // ❌ 일반 람다는 저장 불가 → noinline 덕분에 가능
    ref()                   // 나중에 호출 가능
}
</code></pre>
<p><strong>정리</strong></p>
<ul>
<li><code>inline</code> 함수 내부에서 람다를 <strong>저장/전달</strong>해야 한다면 → <code>noinline</code></li>
<li>즉, “인라인은 하지 말고 일반 변수처럼 써라.”</li>
</ul>
<hr>
<h2 id="3️⃣-crossinline--비지역-반환-금지--안전한-컨텍스트-호출-허용">3️⃣ <code>crossinline</code> — 비지역 반환 금지 + 안전한 컨텍스트 호출 허용</h2>
<blockquote>
<p>“인라인 함수 안의 람다를 다른 컨텍스트(비동기, 콜백 등) 에서 호출할 수 있도록 허용하되,</p>
<p>비지역 반환은 <strong>컴파일 단계에서 금지</strong>시킴”</p>
</blockquote>
<h3 id="✅-예시-2">✅ 예시</h3>
<pre><code class="language-kotlin">inline fun runAsync(crossinline block: () -&gt; Unit) {
    Thread {
        block() // 다른 스레드(비동기) 안에서 실행
    }.start()
}

fun main() {
    runAsync {
        println(&quot;Running in background&quot;)
        // return ❌ 불가능 (비지역 반환 금지)
        return@runAsync ✅ 가능 (지역 반환)
    }
}
</code></pre>
<p><strong>핵심 의미 두 가지</strong></p>
<ol>
<li><p>컴파일 타임에 <strong>비지역 반환을 차단</strong></p>
<p> → 다른 컨텍스트에서 <code>return</code>이 호출돼 프로그램 흐름이 꼬이는 걸 방지</p>
</li>
<li><p><strong>비동기 컨텍스트 허용</strong></p>
<p> → 람다가 스레드, 콜백, 리스너 안에서도 안전하게 실행 가능</p>
</li>
</ol>
<hr>
<h2 id="4️⃣-한눈에-보는-비교표">4️⃣ 한눈에 보는 비교표</h2>
<table>
<thead>
<tr>
<th>키워드</th>
<th>인라인 여부</th>
<th>비지역 반환 가능</th>
<th>다른 컨텍스트 호출 가능</th>
<th>람다 저장 가능</th>
<th>주요 사용 목적</th>
</tr>
</thead>
<tbody><tr>
<td><code>inline</code></td>
<td>✅</td>
<td>✅ 가능</td>
<td>⚠️ 위험할 수 있음</td>
<td>❌ 불가능</td>
<td>성능 최적화, 호출 오버헤드 제거</td>
</tr>
<tr>
<td><code>noinline</code></td>
<td>❌</td>
<td>❌ 불가능</td>
<td>✅ 가능</td>
<td>✅ 가능</td>
<td>람다를 저장하거나 전달해야 할 때</td>
</tr>
<tr>
<td><code>crossinline</code></td>
<td>✅</td>
<td>❌ 금지</td>
<td>✅ 가능</td>
<td>❌ 불가능</td>
<td>비동기/리스너 컨텍스트에서 안전하게 호출</td>
</tr>
</tbody></table>
<hr>
<h2 id="5️⃣-함께-이해하면-좋은-개념-비지역-반환-non-local-return">5️⃣ 함께 이해하면 좋은 개념: <strong>비지역 반환 (Non-local return)</strong></h2>
<pre><code class="language-kotlin">inline fun call(block: () -&gt; Unit) {
    block()
    println(&quot;After block&quot;)
}

fun main() {
    call {
        println(&quot;Inside&quot;)
        return  // ✅ main()까지 빠져나감
    }
    println(&quot;This line never runs&quot;)
}
</code></pre>
<p>→ <code>inline</code> 함수이기 때문에 <code>block()</code> 내부의 <code>return</code>이</p>
<p><strong>바깥 함수(main)</strong> 까지 영향을 미침.</p>
<p>즉, 이것이 “비지역 반환(non-local return)”입니다.</p>
<p><code>crossinline</code>을 붙이면 이런 식의 제어 흐름을 <strong>컴파일러가 막습니다.</strong></p>
<hr>
<h2 id="6️⃣-안드로이드-예시--searchview-검색어-리스너">6️⃣ 안드로이드 예시 — <code>SearchView</code> 검색어 리스너</h2>
<p><code>SearchView</code>의 <code>setOnQueryTextListener()</code> 콜백을</p>
<p><strong>확장 함수로 더 간결하게</strong> 만들 수 있습니다.</p>
<pre><code class="language-kotlin">inline fun SearchView.onQueryTextChanged(
    crossinline onQuery: (String) -&gt; Unit
) {
    setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?) = true

        override fun onQueryTextChange(newText: String?): Boolean {
            onQuery(newText.orEmpty()) // crossinline: 안전하게 콜백 호출
            return true
        }
    })
}
</code></pre>
<h3 id="✅-사용-예시">✅ 사용 예시</h3>
<pre><code class="language-kotlin">searchView.onQueryTextChanged { keyword -&gt;
    // 실시간 검색어 추천
    adapter.submitList(allItems.filter { it.contains(keyword, ignoreCase = true) })
}
</code></pre>
<blockquote>
<p>콜백이 익명 객체(다른 컨텍스트) 안에서 실행되므로crossinline을 써서 비지역 반환을 금지 + 안전한 호출을 허용.</p>
</blockquote>
<hr>
<h2 id="7️⃣-마무리-요약">7️⃣ 마무리 요약</h2>
<table>
<thead>
<tr>
<th>키워드</th>
<th>핵심 개념</th>
<th>한 줄 정리</th>
</tr>
</thead>
<tbody><tr>
<td><code>inline</code></td>
<td>코드 인라인화, 호출 오버헤드 제거</td>
<td>함수 호출을 복붙하듯 최적화</td>
</tr>
<tr>
<td><code>noinline</code></td>
<td>인라인 제외, 람다 저장/전달 허용</td>
<td>람다를 변수처럼 다뤄야 할 때</td>
</tr>
<tr>
<td><code>crossinline</code></td>
<td>비지역 반환 금지, 다른 컨텍스트 호출 허용</td>
<td>비동기/리스너 안에서 안전하게 람다 실행</td>
</tr>
<tr>
<td><strong>비지역 반환</strong></td>
<td>인라인된 람다의 <code>return</code>이 바깥 함수까지 탈출</td>
<td><code>crossinline</code>으로 막을 수 있음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - it, .map, .filter]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-it-.map-.filter</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-it-.map-.filter</guid>
            <pubDate>Mon, 13 Oct 2025 04:36:31 GMT</pubDate>
            <description><![CDATA[<p>Kotlin에서 컬렉션(List, Map, Set 등)을 다룰 때 자주 사용하는 함수형 고차함수(Higher-Order Functions) 들이 있습니다.
그중에서도 map, filter, 그리고 람다식의 기본 매개변수 it 은
가장 자주 등장하는 핵심 문법이다.</p>
<h2 id="it--람다식의-기본-매개변수"><code>it</code> — 람다식의 기본 매개변수</h2>
<p>람다식에서 <strong>매개변수가 하나뿐일 때</strong>,</p>
<p>그 값을 직접 이름 붙이지 않아도 자동으로 <strong><code>it</code></strong> 으로 참조할 수 있습니다.</p>
<pre><code class="language-kotlin">numbers.map { it * 2 }</code></pre>
<p>위 코드는 아래와 완전히 동일합니다 👇</p>
<pre><code class="language-kotlin">numbers.map { number -&gt; number * 2 }</code></pre>
<p>즉, <strong><code>it</code>은 “현재 처리 중인 요소”를 나타내는 기본 이름</strong>이에요.</p>
<p>별도의 변수명을 쓰지 않아도 간단하게 표현할 수 있다는 게 장점입니다.</p>
<hr>
<h2 id="map--리스트-변환-transformation"><code>.map()</code> — 리스트 변환 (Transformation)</h2>
<blockquote>
<p>리스트의 각 요소를 변환하여 새로운 리스트를 만듭니다.</p>
</blockquote>
<ul>
<li>기존 리스트는 변경되지 않습니다.</li>
<li>각 요소에 <strong>변환 함수(람다식)</strong> 을 적용합니다.</li>
</ul>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>동작</td>
<td><code>list.map { it * 2 }</code> → 모든 요소에 <code>*2</code> 적용</td>
</tr>
<tr>
<td>반환값</td>
<td>변환된 새로운 리스트</td>
</tr>
<tr>
<td>원본</td>
<td>변경되지 않음</td>
</tr>
</tbody></table>
<p><strong>한 줄 요약:</strong></p>
<blockquote>
<p>map은 “리스트의 모든 요소에 어떤 함수를 적용해서 변환한 결과를 리턴”합니다.</p>
</blockquote>
<hr>
<h2 id="filter--조건-필터링-filtering"><code>.filter()</code> — 조건 필터링 (Filtering)</h2>
<blockquote>
<p>조건식을 만족하는 요소만 걸러서 새로운 리스트로 반환합니다.</p>
</blockquote>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>동작</td>
<td><code>list.filter { it % 2 == 0 }</code> → 짝수만 추출</td>
</tr>
<tr>
<td>반환값</td>
<td>조건을 만족하는 요소들만 담은 리스트</td>
</tr>
<tr>
<td>원본</td>
<td>변경되지 않음</td>
</tr>
</tbody></table>
<p><strong>한 줄 요약:</strong></p>
<blockquote>
<p>filter는 “조건을 통과한 요소만 남기는 함수”입니다.</p>
</blockquote>
<hr>
<h2 id="체이닝-chaining">체이닝 (Chaining)</h2>
<p><code>map</code>과 <code>filter</code>는 함께 이어서 쓸 수 있습니다.</p>
<p>이걸 <strong>함수 체이닝(Function Chaining)</strong> 이라고 부르며,</p>
<p>중간 리스트를 만들지 않고 한 줄로 연속적인 변환 과정을 표현할 수 있습니다.</p>
<pre><code class="language-kotlin">val result = numbers
    .filter { it &gt; 3 }   // 3보다 큰 숫자만 필터링 → [4, 5, 6, 7, 8]
    .map { it * 10 }     // 각 숫자에 10을 곱함 → [40, 50, 60, 70, 80]
    .take(3)             // 앞에서 3개만 가져옴 → [40, 50, 60]
</code></pre>
<p>이런 식으로 코드를 <strong>위에서 아래로 자연스럽게 읽을 수 있어</strong></p>
<p>for문보다 훨씬 간결하고 가독성이 좋아집니다.</p>
<hr>
<h2 id="핵심-정리표">핵심 정리표</h2>
<table>
<thead>
<tr>
<th>함수</th>
<th>역할</th>
<th>반환값</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>map()</code></td>
<td>각 요소를 변환</td>
<td>변환된 리스트</td>
<td><code>list.map { it * 2 }</code></td>
</tr>
<tr>
<td><code>filter()</code></td>
<td>조건에 맞는 요소만 추출</td>
<td>필터링된 리스트</td>
<td><code>list.filter { it % 2 == 0 }</code></td>
</tr>
<tr>
<td><code>take(n)</code></td>
<td>앞에서 n개만 추출</td>
<td>잘린 리스트</td>
<td><code>list.take(3)</code></td>
</tr>
<tr>
<td><code>it</code></td>
<td>람다식의 기본 매개변수</td>
<td>현재 요소</td>
<td><code>{ println(it) }</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="최종-예제-코드">최종 예제 코드</h2>
<pre><code class="language-kotlin">fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)

    // 1️⃣ map — 리스트의 각 요소를 변환 (Transformation)
    val doubled = numbers.map { it * 2 }
    println(&quot;두 배가 된 숫자들: $doubled&quot;) // [2, 4, 6, 8, 10, 12, 14, 16]

    // 2️⃣ filter — 조건을 만족하는 요소만 남김 (Filtering)
    val evens = numbers.filter { it % 2 == 0 }
    println(&quot;짝수들: $evens&quot;) // [2, 4, 6, 8]

    // 3️⃣ 체이닝 — filter + map + take
    val result = numbers
        .filter { it &gt; 3 }   // [4, 5, 6, 7, 8]
        .map { it * 10 }     // [40, 50, 60, 70, 80]
        .take(3)             // [40, 50, 60]

    println(&quot;최종 결과: $result&quot;)
}
</code></pre>
<hr>
<h2 id="✅-마무리-요약">✅ 마무리 요약</h2>
<ul>
<li><code>it</code> 👉 현재 요소를 가리키는 기본 변수</li>
<li><code>.map()</code> 👉 변환 (Transformation)</li>
<li><code>.filter()</code> 👉 선택 (Filtering)</li>
<li>여러 함수를 <strong>체이닝</strong>하면 가독성 높은 “데이터 처리 파이프라인” 완성</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - lateinit]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-lateinit</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-lateinit</guid>
            <pubDate>Mon, 29 Sep 2025 06:45:02 GMT</pubDate>
            <description><![CDATA[<h2 id="lateinit란"><code>lateinit</code>란?</h2>
<ul>
<li><code>lateinit</code>은 <strong>&quot;나중에 초기화하겠다&quot;</strong>(late initialization)는 의미를 가진 키워드입니다.</li>
<li>보통 <code>val</code>(읽기 전용)에는 사용할 수 없고, <strong><code>var</code>(가변 변수)</strong> 와 <strong>Non-null 타입</strong>에만 적용됩니다.</li>
<li>즉, 선언 시 값을 할당하지 않고, <strong>나중에 꼭 초기화할 것을 보장</strong>할 때 사용합니다.</li>
</ul>
<hr>
<h2 id="📌-사용-조건">📌 사용 조건</h2>
<ol>
<li><strong><code>var</code> 이어야 함</strong> (<code>val</code>에는 사용 불가)</li>
<li><strong>Non-null 타입</strong>이어야 함 (<code>String?</code> 같은 Nullable에는 불가)</li>
<li><strong>기본 타입(Int, Double 등)</strong> 에는 사용 불가 → 객체 타입만 가능</li>
</ol>
<hr>
<h2 id="기본-예시">기본 예시</h2>
<pre><code class="language-kotlin">class User {
    lateinit var name: String   // 나중에 반드시 초기화할 변수

    fun initName(newName: String) {
        name = newName
    }

    fun printName() {
        println(&quot;사용자 이름: $name&quot;)
    }
}

val u = User()
u.initName(&quot;민수&quot;)   // 나중에 초기화
u.printName()        // 출력: 사용자 이름: 민수
</code></pre>
<p> 선언 시 값이 없어도 컴파일 오류가 발생하지 않고, 나중에 <code>initName()</code>에서 반드시 초기화함.</p>
<hr>
<h2 id="자주-쓰이는-상황">자주 쓰이는 상황</h2>
<h3 id="1-di-의존성-주입">1) <strong>DI (의존성 주입)</strong></h3>
<pre><code class="language-kotlin">class Service

class Client {
    lateinit var service: Service   // 외부에서 주입 예정
}</code></pre>
<p>→ 스프링(Spring) 같은 프레임워크에서 <strong>객체 주입 시점</strong>에 활용.</p>
<hr>
<h3 id="2-android-개발-view-context-등">2) <strong>Android 개발 (View, Context 등)</strong></h3>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity() {
    lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        textView = findViewById(R.id.myTextView)
        textView.text = &quot;Hello World!&quot;
    }
}</code></pre>
<p>→ <code>onCreate()</code> 시점에서 초기화되므로, <code>lateinit</code>을 사용해 <strong>미리 null 체크 없이</strong> 선언 가능.</p>
<hr>
<h2 id="⚠️-주의사항">⚠️ 주의사항</h2>
<ol>
<li><p>초기화 전에 사용하면 <strong><code>UninitializedPropertyAccessException</code></strong> 발생</p>
<pre><code class="language-kotlin"> lateinit var title: String
 println(title) // ❌ 초기화 안 해서 런타임 오류</code></pre>
</li>
<li><p><strong>기본 타입에는 사용할 수 없음</strong></p>
<pre><code class="language-kotlin"> lateinit var age: Int // ❌ 컴파일 오류</code></pre>
<p> → 해결: <code>Int?</code>(nullable) 또는 <code>Delegates.notNull()</code> 활용</p>
<pre><code class="language-kotlin"> var age: Int by Delegates.notNull()</code></pre>
</li>
<li><p>꼭 &quot;반드시 나중에 초기화된다&quot;는 <strong>논리적 보장</strong>이 필요</p>
</li>
</ol>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>lateinit</code> = <strong>나중에 초기화하는 Non-null var</strong></li>
<li>주로 <strong>DI, Android View 바인딩, 테스트 코드</strong> 등에서 많이 사용</li>
<li><strong>초기화 전 접근 시 런타임 오류</strong>가 나므로, 확실히 초기화 시점을 보장할 때만 사용해야 함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - init]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-init</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-init</guid>
            <pubDate>Mon, 29 Sep 2025 06:41:10 GMT</pubDate>
            <description><![CDATA[<p>코틀린에서 <code>init</code> 키워드는 <strong>초기화 블록(Initializer Block)</strong>을 정의하는 데 사용됩니다. 이는 클래스의 인스턴스가 생성될 때 실행되는 코드를 포함합니다.</p>
<h2 id="기본-개념">기본 개념</h2>
<ul>
<li><strong><code>init</code> 블록</strong>은 클래스의 인스턴스를 생성할 때 (생성자가 호출될 때) 실행되는 코드 블록입니다.</li>
<li>주로 클래스 본체 내에서 <strong>속성 초기화 로직</strong>을 수행하거나 <strong>생성 시 필요한 유효성 검사, 계산, 설정</strong> 등의 추가적인 작업을 수행하는 데 사용됩니다.</li>
<li><em>주 생성자(Primary Constructor)*</em>는 클래스 헤더에 정의되며 실행 가능한 코드를 포함할 수 없기 때문에, 이러한 초기화 코드는 <code>init</code> 블록에 위치해야 합니다.</li>
<li><strong>주 생성자(Primary constructor)</strong>: 클래스 헤더에 정의되는 매개변수</li>
<li><strong><code>init</code> 블록</strong>: 객체 생성 시 자동으로 실행되는 초기화 블록</li>
<li><strong>속성(Property)</strong>: <code>val</code>/<code>var</code>로 클래스 내부에 선언된 멤버 변수</li>
</ul>
<pre><code class="language-kotlin">class Student(val name: String, var age: Int) {
    init {
        require(age &gt;= 0) { &quot;나이는 음수가 될 수 없습니다: $age&quot; }
        println(&quot;학생 생성 완료 → $name, $age&quot;)
    }
}

val s = Student(&quot;민수&quot;, 20)
// 출력: 학생 생성 완료 → 민수, 20
</code></pre>
<hr>
<h2 id="속성-선언-패턴">속성 선언 패턴</h2>
<h3 id="1-가장-간단한-패턴-헤더에-바로-선언">1) 가장 간단한 패턴 (헤더에 바로 선언)</h3>
<pre><code class="language-kotlin">class Car(val brand: String = &quot;Hyundai&quot;, var color: String = &quot;white&quot;)
</code></pre>
<h3 id="2-init-블록에서-가공·검증-후-할당">2) <code>init</code> 블록에서 가공·검증 후 할당</h3>
<pre><code class="language-kotlin">class Car(brand: String, color: String) {
    val brand: String
    var color: String

    init {
        require(brand.isNotBlank()) { &quot;브랜드는 비어 있을 수 없습니다.&quot; }
        this.brand = brand.trim()
        this.color = color.lowercase()
    }
}</code></pre>
<hr>
<h2 id="초기화-실행-순서">초기화 실행 순서</h2>
<ol>
<li>주 생성자 인자 평가</li>
<li>속성 초기화 → <code>init</code> 블록 실행 (위에서 아래 순서대로)</li>
</ol>
<pre><code class="language-kotlin">class Order(id: String) {
    val normalizedId = id.trim()   // 1) 속성 초기화
    init { println(&quot;init 실행: $normalizedId&quot;) }  // 2) init 블록 실행
}</code></pre>
<hr>
<h2 id="보조-생성자와-this">보조 생성자와 <code>this()</code></h2>
<p>보조 생성자(Secondary constructor)는 반드시 <strong>주 생성자에 위임</strong>합니다.</p>
<p>그리고 항상 <code>init</code> 블록이 먼저 실행됩니다.</p>
<pre><code class="language-kotlin">class Person(val name: String, val age: Int) {
    init { println(&quot;init 실행: $name, $age&quot;) }

    constructor(name: String) : this(name, 0) {
        println(&quot;보조 생성자 실행 → $name (age=0)&quot;)
    }
}

val p1 = Person(&quot;민재&quot;)
// 출력: init 실행 → 보조 생성자 실행</code></pre>
<hr>
<h2 id="상속과-초기화-순서">상속과 초기화 순서</h2>
<p>상위 클래스 → 하위 클래스 순으로 초기화가 진행됩니다.</p>
<pre><code class="language-kotlin">open class Base(val id: String) {
    init { println(&quot;Base init: $id&quot;) }
}

class Child(id: String, val tag: String) : Base(id) {
    init { println(&quot;Child init: $tag&quot;) }
}

val c = Child(&quot;ID-1&quot;, &quot;TAG-9&quot;)
// 출력: Base init → Child init</code></pre>
<hr>
<h2 id="자주-쓰는-패턴-모음">자주 쓰는 패턴 모음</h2>
<h3 id="✅-데이터-클래스--검증">✅ 데이터 클래스 + 검증</h3>
<pre><code class="language-kotlin">data class Point(val x: Int, val y: Int) {
    init { require(x in 0..100 &amp;&amp; y in 0..100) }
}</code></pre>
<h3 id="✅-기본값--이름-있는-인자">✅ 기본값 + 이름 있는 인자</h3>
<pre><code class="language-kotlin">class User(val name: String, val age: Int = 0, val country: String = &quot;KR&quot;)

val u1 = User(&quot;지연&quot;)
val u2 = User(name = &quot;Alex&quot;, age = 30)</code></pre>
<h3 id="✅-팩토리-함수-활용-init-깔끔하게">✅ 팩토리 함수 활용 (init 깔끔하게)</h3>
<pre><code class="language-kotlin">class Color private constructor(val r: Int, val g: Int, val b: Int) {
    companion object {
        fun fromHex(hex: String): Color {
            val clean = hex.removePrefix(&quot;#&quot;)
            require(clean.length == 6)
            return Color(
                clean.substring(0, 2).toInt(16),
                clean.substring(2, 4).toInt(16),
                clean.substring(4, 6).toInt(16)
            )
        }
    }
}

val green = Color.fromHex(&quot;#4CAF50&quot;)</code></pre>
<hr>
<h2 id="⚠️-실수-방지-체크리스트">⚠️ 실수 방지 체크리스트</h2>
<ul>
<li>❌ <code>init</code>에 인자를 직접 넣으려 함 → <code>init</code>은 인자를 받지 않습니다.</li>
<li>❌ 생성자에서 <code>val/var</code> 빠뜨림 → 단순 매개변수만 남고 속성이 안 만들어짐.</li>
<li>❌ 모든 검증·가공을 <code>init</code>에 몰아넣음 → 복잡해지면 팩토리 함수 활용 추천.</li>
<li>❌ 자바 연동 시 기본값만 사용 → 필요하면 <code>@JvmOverloads</code> 사용.</li>
</ul>
<hr>
<h2 id="📝-결론">📝 결론</h2>
<ul>
<li><code>init</code> 블록은 <strong>인자를 직접 받지 않고</strong> 주 생성자의 매개변수를 활용하는 구조</li>
<li>초기화 시점에 <strong>검증·로그·가공</strong> 작업을 넣는 용도로 적합</li>
<li>단순 속성은 <strong>생성자 헤더에 바로 <code>val/var</code> 선언</strong>하는 게 더 깔끔</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin문법 - 특별한 타입들: Unit, Any, Nothing]]></title>
            <link>https://velog.io/@subin_k/Kotlin%EB%AC%B8%EB%B2%95-%ED%8A%B9%EB%B3%84%ED%95%9C-%ED%83%80%EC%9E%85%EB%93%A4-Unit-Any-Nothing</link>
            <guid>https://velog.io/@subin_k/Kotlin%EB%AC%B8%EB%B2%95-%ED%8A%B9%EB%B3%84%ED%95%9C-%ED%83%80%EC%9E%85%EB%93%A4-Unit-Any-Nothing</guid>
            <pubDate>Wed, 24 Sep 2025 04:23:49 GMT</pubDate>
            <description><![CDATA[<p>코틀린은 자바의 타입 시스템을 개선하여 <strong>Unit, Any, Nothing</strong> 이라는 세 가지 특별한 타입을 제공합니다.</p>
<p>이들은 <strong>함수의 반환, 타입 계층, 예외 처리</strong>를 명확하게 정의하는 데 중요한 역할을 합니다.</p>
<hr>
<h2 id="1-unit-반환값이-없는-함수">1. Unit: 반환값이 없는 함수</h2>
<ul>
<li><strong>정의</strong>: 반환 값이 없음을 나타내는 타입 (자바의 <code>void</code>와 유사).</li>
<li><strong>차이점</strong>: <code>void</code>는 키워드지만, <code>Unit</code>은 실제 타입이며 단 하나의 인스턴스만 존재.</li>
<li><strong>제네릭 활용 가능</strong>: <code>Unit</code>은 타입이므로 제네릭 인자로 사용할 수 있음.</li>
<li><strong>사용 용도</strong>: 반환할 데이터는 없지만, 함수가 동작을 마쳤음을 명시적으로 표현할 때.</li>
</ul>
<pre><code class="language-kotlin">fun exampleFunction(): Unit {
    println(&quot;This function returns Unit.&quot;)
}

// 반환 타입 생략 시 자동으로 Unit 추론
fun exampleFunction2() {
    println(&quot;This function also returns Unit.&quot;)
}</code></pre>
<hr>
<h2 id="2-any-모든-타입의-부모">2. Any: 모든 타입의 부모</h2>
<ul>
<li><strong>정의</strong>: 코틀린 모든 클래스의 최상위 타입 (자바의 <code>Object</code>와 유사).</li>
<li><strong>특징</strong>: 기본 타입(Int, String 등)까지 포함.</li>
<li><strong>주의점</strong>: <code>Any</code>는 null 불가. null 허용 시 <code>Any?</code> 사용해야 함.</li>
<li><strong>사용 용도</strong>: 다양한 타입을 하나의 변수/컬렉션에 담고 싶을 때.</li>
</ul>
<p>//any 메소드(함수)</p>
<ul>
<li>equals(other:any) , hashCode(), toString() 등 있음 <pre><code class="language-kotlin">fun printAny(value: Any) {
  println(&quot;받은 값: $value&quot;)
  println(&quot;값의 타입: ${value::class.simpleName}&quot;)
}
</code></pre>
</li>
</ul>
<p>fun main() {
    printAny(&quot;안녕하세요!&quot;) // String
    printAny(12345)       // Int
    printAny(true)        // Boolean
}</p>
<pre><code>
---

## 3. Nothing: 존재하지 않는 값

- **정의**: 어떤 값도 가질 수 없는 타입. 인스턴스 생성 불가.
- **의미**: 함수가 **정상적으로 종료되지 않음**을 컴파일러에게 알려줌.
- **사용 용도**: 예외 발생, 무한 루프, 프로그램 흐름이 끊기는 상황에 사용.
- **활용 사례**:
    - `fail()` 같이 반드시 예외를 던지는 함수.
    - `?:` (엘비스 연산자)와 함께 null 처리.

```kotlin
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

fun main() {
    val name: String? = &quot;코틀린&quot;
    val safeName = name ?: fail(&quot;이름이 없습니다!&quot;) // &quot;코틀린&quot; 할당

    val anotherName: String? = null
    val anotherSafeName = anotherName ?: fail(&quot;이름이 없습니다!&quot;)
    println(&quot;이 코드는 실행되지 않습니다.&quot;) // 도달 불가
}</code></pre><hr>
<h2 id="추가로-알면-좋은-점">추가로 알면 좋은 점</h2>
<ul>
<li><p><strong>Void와 Unit의 차이</strong></p>
<ul>
<li><code>Void</code>(자바): null만 가질 수 있는 참조 타입.</li>
<li><code>Unit</code>(코틀린): 단일 인스턴스를 가지며 실제 객체.</li>
</ul>
</li>
<li><p><strong>Nothing? 타입</strong></p>
<ul>
<li><code>Nothing?</code>은 <strong>null만 허용하는 타입</strong>. 일반적으로 잘 쓰이지 않지만, API 설계에서 의미를 명확히 할 때 활용 가능.</li>
</ul>
</li>
<li><p><strong>타입 계층 구조</strong></p>
<pre><code>  Nothing  ←  (모든 하위 타입의 시작점, 값 없음)
       ↑
     String, Int, Boolean ...  ← 일반 타입들
       ↑
      Any   ← 최상위 부모
       ↑
     Unit   ← 반환값 없음(특수 케이스)</code></pre></li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/subin_k/post/77b12d6e-2069-454d-a6ba-bfd7b088a6d7/image.png" alt="output.png"></p>
<p>이 다이어그램은 <strong>Kotlin 타입 계층 구조</strong>를 보여줍니다:</p>
<ul>
<li>최상위는 <code>Any</code></li>
<li><code>String</code>, <code>Int</code>, <code>Boolean</code> 등 모든 구체 타입이 <code>Any</code>를 상속</li>
<li><code>Unit</code> 역시 <code>Any</code>의 하위 타입</li>
<li><code>Nothing</code>은 모든 타입의 하위 타입(즉, &quot;값이 존재하지 않음&quot;)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 코루틴 기초 (Coroutine 소개)]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B8%B0%EC%B4%88-Coroutine-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B8%B0%EC%B4%88-Coroutine-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Sun, 24 Aug 2025 02:21:59 GMT</pubDate>
            <description><![CDATA[<h2 id="1️⃣-코루틴이란-무엇인가-경량-스레드">1️⃣ 코루틴이란 무엇인가? (경량 스레드)</h2>
<p>코루틴은 OS 스레드보다 훨씬 가벼운 <strong>경량 스레드(Light-weight Thread)</strong>입니다.</p>
<p>복잡한 콜백(Callback) 지옥 없이 비동기 코드를 마치 순차적인 코드처럼 작성할 수 있게 해줍니다.</p>
<hr>
<h3 id="🔹-핵심-키워드-suspend">🔹 핵심 키워드: <code>suspend</code></h3>
<p>코루틴의 마법은 <code>suspend</code> 키워드에서 시작됩니다.</p>
<p><code>suspend</code> 함수는 <strong>실행을 잠시 중단(Suspend)</strong>했다가, 필요할 때 <strong>다시 이어서 실행(Resume)</strong>할 수 있는 함수입니다.</p>
<p>이 중단/재개 메커니즘 덕분에, 코루틴은</p>
<p><strong>I/O 작업(네트워크 통신, 파일 읽기 등)</strong>처럼 대기 시간이 긴 작업 동안</p>
<p>스레드를 점유하지 않고 효율적으로 자원을 사용할 수 있습니다.</p>
<hr>
<h2 id="2️⃣-launch와-job-코루틴의-시작과-생명-주기">2️⃣ <code>launch</code>와 <code>Job</code>: 코루틴의 시작과 생명 주기</h2>
<h3 id="21-launch-함수--코루틴을-시작하는-빌더">2.1 <code>launch</code> 함수 — 코루틴을 시작하는 빌더</h3>
<p><code>launch</code>는 새로운 코루틴을 시작하는 가장 기본적인 <strong>코루틴 빌더(Coroutine Builder)</strong>입니다.</p>
<ul>
<li><strong>역할:</strong> 결과를 반환할 필요가 없는, <em>“실행 후 잊어버리는”</em> 비동기 작업 시작 시 사용</li>
<li><strong>반환값:</strong> 실행된 코루틴 자체를 의미하는 <code>Job</code> 객체를 반환</li>
</ul>
<pre><code class="language-kotlin">import kotlinx.coroutines.*

fun main() = runBlocking {
    // launch를 사용해 비동기 작업 시작
    val myJob: Job = launch {
        println(&quot;작업 시작: 네트워크 통신 중...&quot;)
        delay(1000L) // 1초간 suspend
        println(&quot;작업 완료: 데이터 수신됨&quot;)
    }

    // launch는 Job 객체를 반환하므로, 이를 통해 코루틴 제어 가능
}
</code></pre>
<hr>
<h3 id="22-job-객체--코루틴의-생명-주기-제어-핸들">2.2 <code>Job</code> 객체 — 코루틴의 생명 주기 제어 핸들</h3>
<p><code>Job</code>은 실행 중인 코루틴의 <strong>생명 주기(Lifecycle)</strong>를 관리하고 제어하는 역할을 합니다.</p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>cancel()</code></td>
<td>코루틴에게 취소 요청을 보냄</td>
</tr>
<tr>
<td><code>join()</code></td>
<td>해당 Job이 완료될 때까지 현재 코루틴을 일시 중단하고 기다림</td>
</tr>
<tr>
<td><code>isActive</code></td>
<td>코루틴이 현재 활성화(실행 중) 상태인지 확인</td>
</tr>
</tbody></table>
<p>또한, <strong>부모-자식 관계</strong>가 있어 부모 코루틴이 취소되면 모든 자식 코루틴이 자동으로 취소됩니다.</p>
<p>이를 <strong>구조화된 동시성(Structured Concurrency)</strong>이라 합니다.</p>
<pre><code class="language-kotlin">import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            delay(2000L) // 중단 가능한 함수 필요
            println(&quot;이 메시지는 출력되지 않을 수 있습니다.&quot;)
        } catch (e: CancellationException) {
            println(&quot;작업이 취소되었습니다.&quot;)
        }
    }

    delay(500L)
    job.cancel() // 0.5초 후 취소 요청
    job.join()   // 취소가 끝날 때까지 대기
    println(&quot;메인 루틴 종료&quot;)
}
</code></pre>
<hr>
<h2 id="3️⃣-coroutinescope-코루틴의-실행-범위와-생명-주기-관리">3️⃣ <code>CoroutineScope</code>: 코루틴의 실행 범위와 생명 주기 관리</h2>
<p>코루틴은 <strong>혼자서 존재할 수 없으며</strong>, 반드시 <code>CoroutineScope</code> 내에서 실행되어야 합니다.</p>
<p><code>Scope</code>는 코루틴이 활동할 수 있는 <strong>범위(Scope)</strong>를 정의하고, 생명 주기를 관리하는 단위입니다.</p>
<hr>
<h3 id="31-스코프의-역할">3.1 스코프의 역할</h3>
<ul>
<li><p><strong>책임 범위 정의:</strong> 스코프 내에서 시작된 코루틴들은 해당 스코프의 생명 주기를 따름</p>
</li>
<li><p><strong>구조화된 동시성:</strong> 스코프가 종료되거나 취소되면 그 안의 <strong>모든 자식 코루틴(Job)</strong>이 자동으로 취소됨</p>
<p> → 메모리 누수 및 예측 불가능한 동작 방지</p>
</li>
</ul>
<hr>
<h3 id="32-대표적인-스코프">3.2 대표적인 스코프</h3>
<table>
<thead>
<tr>
<th>스코프 유형</th>
<th>설명</th>
<th>주요 사용처</th>
</tr>
</thead>
<tbody><tr>
<td><code>runBlocking</code></td>
<td>현재 스레드를 차단하며 코루틴 실행</td>
<td>테스트 코드, main 함수</td>
</tr>
<tr>
<td><code>CoroutineScope()</code></td>
<td>새로운 스코프 생성</td>
<td>클래스 내 특정 비동기 작업 관리</td>
</tr>
<tr>
<td><code>coroutineScope</code></td>
<td>자식 스코프 생성, 모든 작업 완료 시까지 suspend</td>
<td>병렬 작업 수행 및 완료 대기</td>
</tr>
</tbody></table>
<hr>
<h3 id="33-coroutinescope-사용-예시">3.3 CoroutineScope 사용 예시</h3>
<p><code>launch</code>와 <code>Job</code>은 항상 <code>Scope</code> 내에서 사용됩니다.</p>
<pre><code class="language-kotlin">import kotlinx.coroutines.*

// 1. CoroutineScope 생성
val myApplicationScope = CoroutineScope(Dispatchers.Default)

fun startWork() {
    // 2. 스코프 내에서 launch 호출
    val workJob = myApplicationScope.launch {
        println(&quot;백그라운드에서 작업 시작&quot;)
        delay(3000L)
        println(&quot;긴 작업 완료&quot;)
    }
}

fun cleanup() {
    // 3. 스코프 취소
    myApplicationScope.cancel()
    println(&quot;스코프 취소 완료: 모든 자식 코루틴 종료됨&quot;)
}
</code></pre>
<hr>
<h2 id="✅-결론-세-가지-핵심-요소-정리">✅ 결론: 세 가지 핵심 요소 정리</h2>
<table>
<thead>
<tr>
<th>요소</th>
<th>역할</th>
<th>관계</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>CoroutineScope</code></strong></td>
<td>코루틴이 실행될 범위와 생명 주기를 정의 (부모)</td>
<td><code>launch</code>와 <code>Job</code>을 감싸는 컨테이너</td>
</tr>
<tr>
<td><strong><code>launch</code></strong></td>
<td>코루틴을 실행하는 빌더</td>
<td><code>Job</code> 객체를 반환</td>
</tr>
<tr>
<td><strong><code>Job</code></strong></td>
<td>실행된 코루틴의 생명 주기를 제어하는 핸들 (자식)</td>
<td><code>cancel()</code>, <code>join()</code> 등 제공</td>
</tr>
</tbody></table>
<hr>
<h3 id="한-줄-요약">한 줄 요약</h3>
<blockquote>
<p>“CoroutineScope는 무대, launch는 배우의 등장, Job은 그 배우의 생명줄이다.”</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 고차 함수 및 콜백 함수 (람다 활용)]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98-%EB%B0%8F-%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98-%EB%9E%8C%EB%8B%A4-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98-%EB%B0%8F-%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98-%EB%9E%8C%EB%8B%A4-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Wed, 20 Aug 2025 03:30:15 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-문법---고차-함수-및-콜백-함수-람다-활용">Kotlin 문법 - 고차 함수 및 콜백 함수 (람다 활용)</h1>
<h2 id="학습-요약">학습 요약</h2>
<ul>
<li><strong>고차 함수(Higher-Order Function)</strong><ul>
<li>함수를 인자로 받거나, 함수를 반환하는 함수</li>
<li>Kotlin에서 <strong>콜백 함수</strong>를 간단히 구현할 수 있음</li>
</ul>
</li>
<li><strong>람다 표현식</strong><ul>
<li><code>{ parameter -&gt; body }</code> 형식</li>
<li>파라미터가 하나일 경우 <code>it</code> 키워드로 축약 가능</li>
<li>마지막 인자가 함수일 경우, <strong>소괄호 밖으로 빼기 가능</strong></li>
</ul>
</li>
<li><strong>콜백 함수 (Callback)</strong><ul>
<li>특정 시점에 호출되도록 넘겨주는 함수</li>
<li>비동기 처리나 이벤트 처리에 자주 사용됨</li>
</ul>
</li>
</ul>
<hr>
<h2 id="실습-코드">실습 코드</h2>
<pre><code class="language-kotlin">fun main() {
    // 고차 함수 호출 - 콜백 전달
    myFunc {
        println(&quot;함수 호출&quot;)
    }

    // 매개변수가 두 개일 때 (Int + 콜백)
    myFunc2(5) {
        println(&quot;함수 호출 2&quot;)
    }

    // 괄호 생략 가능 (마지막 인자가 람다일 경우)
    myFunc() {
        println(&quot;괄호 생략 형태&quot;)
    }

    // 람다식에서 파라미터가 하나면 it 사용 가능
    repeatAction(3) {
        println(&quot;반복 실행 $it 번째&quot;)
    }
}

// 콜백을 받는 고차 함수
fun myFunc(callBack: () -&gt; Unit) {
    println(&quot;함수 시작&quot;)
    callBack()
    println(&quot;함수 끝!!&quot;)
}

fun myFunc2(a: Int, callBack: () -&gt; Unit) {
    println(&quot;함수 시작2, a=$a&quot;)
    callBack()
    println(&quot;함수 끝!!2&quot;)
}

// 콜백에 매개변수를 전달하는 예제
fun repeatAction(times: Int, action: (Int) -&gt; Unit) {
    for (i in 1..times) {
        action(i)  // i를 콜백에 전달
    }
}
</code></pre>
<hr>
<h2 id="추가-정리-포인트">추가 정리 포인트</h2>
<ul>
<li><strong><code>() -&gt; Unit</code></strong> : 파라미터 없고 반환값 없는 함수 타입</li>
<li><strong><code>(Int) -&gt; String</code></strong> : Int를 받아서 String을 반환하는 함수 타입</li>
<li>Kotlin에서는 <strong>익명 함수</strong> 대신 <strong>람다</strong>를 더 많이 활용</li>
<li>고차 함수는 <strong>비동기 처리, 이벤트 리스너, DSL</strong> 구현에서 강력하게 쓰임</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 14626번 - ISBN]]></title>
            <link>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-14626%EB%B2%88-ISBN</link>
            <guid>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-14626%EB%B2%88-ISBN</guid>
            <pubDate>Wed, 20 Aug 2025 02:09:03 GMT</pubDate>
            <description><![CDATA[<h1 id="📂-백준-14626번---isbn">📂 백준 14626번 - ISBN</h1>
<p><a href="https://www.acmicpc.net/problem/14626">문제 링크</a></p>
<hr>
<h2 id="📝-문제-설명">📝 문제 설명</h2>
<p>ISBN(International Standard Book Number)은 전 세계 모든 도서에 부여된 고유번호로, 국제 표준 도서번호이다. ISBN에는 국가명, 발행자 등의 정보가 담겨 있으며 13자리의 숫자로 표시된다. 그중 마지막 숫자는 체크기호로 ISBN의 정확성 여부를 점검할 수 있는 숫자이다. 이 체크기호는 일련번호의 앞에서부터 각 자리마다 가중치 1, 3, 1, 3…. 를 곱한 것을 모두 더하고, 그 값을 10으로 나눈 나머지가 0이 되도록 만드는 숫자 m을 사용한다. 수학적으로는 다음과 같다.</p>
<p>ISBN이 abcdefghijklm 일 때, a+3b+c+3d+e+3f+g+3h+i+3j+k+3l+m ≡ 0 (mod 10)</p>
<p>즉, 체크기호 m = 10 - (a+3b+c+3d+e+3f+g+3h+i+3j+k+3l) mod 10 이다.</p>
<p>단, 10으로 나눈 나머지 값이 0일 경우 체크기호는 0이다.</p>
<p>전북대학교 중앙도서관에서 사서로 일하고 있는 영훈이는 책 정리를 하다가 개구쟁이 광현이에 의해서 ISBN이 훼손된 도서들을 발견했다. 광현이때문에 야근해야 하는 불쌍한 영훈이를 위해서 손상된 자리의 숫자를 찾아내는 프로그램을 작성해주자.</p>
<p>ISBN 13자리 숫자가 입력된다. 훼손된 숫자는 *로 표시한다. (훼손된 일련번호는 체크기호를 제외한 무작위 한 자리이다.)</p>
<hr>
<h2 id="출력">출력</h2>
<p>훼손된 숫자 *에 알맞은 숫자를 출력한다.</p>
<hr>
<h2 id="✅-입력-예시">✅ 입력 예시</h2>
<p>9788968322*73</p>
<h2 id="✅-예제-출력">✅ 예제 출력</h2>
<p>2</p>
<h2 id="💡-풀이-코드-풀이코드한-언어">💡 풀이 코드 (풀이코드한 언어)</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class bj_14626 {

    public static void main(String[] args) throws IOException {

        // 가중치가 1,3,1,3 ... 이니까
        // 홀수위치에는 1을 곱하고 짝수 위치에는 3을 곱하여 다 더했을때 10으로 나눈 나머지가 0이면된다.
        //10으로 나눈 나머지가 0이 될 수 있는 훼손된 숫자 값을 구한다.

        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        String s = bf.readLine();

        int idx = 0, sum = 0;        

        for(int i=0; i&lt;s.length(); i++) {
            char ch = s.charAt(i);
            //손상된 자리를 제외한 모든 숫자에 대한 가중치를 적용한 합
            //i%2 == 0 -&gt; 짝수인덱스 -&gt; 홀수번째 -&gt; 그대로 더하기
            //i%2 != 0 -&gt; 홀수인덱스 -&gt; 짝수번째 -&gt; 3곱해서 더하기
            if(ch != &#39;*&#39;) sum += i % 2 == 0? ch - &#39;0&#39; : (ch - &#39;0&#39;) *3;
            //손상된 자리를 넣어줌
            else idx = i;
        }

        int answer = 0;
        for(int i =0; i&lt;10; i++) {
            //손상된 자리에 들어올 숫자 0~9
            //숫자 넣어봐서 가중치 넣어 계산해보기
            //i%2 == 0 -&gt; 짝수인덱스 -&gt; 홀수번째 -&gt; 그대로 더하기
            //i%2 != 0 -&gt; 홀수인덱스 -&gt; 짝수번째 -&gt; 3곱해서 더하기
            if(idx % 2 == 0 &amp;&amp; (sum + i) % 10 == 0) {
                answer = i; 
            }
            else if(idx % 2 != 0 &amp;&amp; (sum + 3 * i) % 10 == 0) {
                answer = i; 
            }
        }

        System.out.print(answer);

    }

}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 타입 확인과 형 변환 (`is`, `as`)]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%ED%83%80%EC%9E%85-%ED%99%95%EC%9D%B8%EA%B3%BC-%ED%98%95-%EB%B3%80%ED%99%98-is-as</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%ED%83%80%EC%9E%85-%ED%99%95%EC%9D%B8%EA%B3%BC-%ED%98%95-%EB%B3%80%ED%99%98-is-as</guid>
            <pubDate>Tue, 19 Aug 2025 03:39:11 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-문법---타입-확인과-형-변환-is-as">Kotlin 문법 - 타입 확인과 형 변환 (<code>is</code>, <code>as</code>)</h1>
<h2 id="타입-확인-is">타입 확인 (<code>is</code>)</h2>
<ul>
<li><code>is</code> 연산자를 이용해 객체가 특정 타입인지 확인 가능</li>
<li><code>is</code>가 <code>true</code>로 확인되면 스마트 캐스트(Smart Cast) 기능이 적용되어, 별도의 형 변환 없이 해당 타입의 멤버를 바로 사용 가능</li>
<li><code>!is</code> 를 사용하면 반대(특정 타입이 아닌 경우) 확인 가능</li>
</ul>
<hr>
<h2 id="타입-변환-as">타입 변환 (<code>as</code>)</h2>
<ul>
<li><code>as</code> 연산자를 사용하면 강제 타입 캐스팅 수행</li>
<li>상속관계에서만 사용이 가능</li>
<li>instance에서 명시적으로 캐스팅 할 때 사용</li>
<li>잘못된 타입으로 변환 시 런타임 오류(<code>ClassCastException</code>) 발생</li>
<li>안전한 캐스팅을 위해 <code>as?</code> 연산자 사용 가능
→ 캐스팅 실패 시 <code>null</code> 반환</li>
</ul>
<hr>
<h2 id="실습-코드">실습 코드</h2>
<pre><code class="language-kotlin">fun main() {
    val dog: Animal = Dog()
    val cat = Cat()

    // 강제 캐스팅 (잘못된 변환 → 런타임 오류 발생)
    // cat as Dog

    // 안전한 캐스팅
    val maybeDog = cat as? Dog
    println(maybeDog) // null

    // 타입 확인
    if (dog is Dog) { // true
        dog.move()   // 스마트 캐스트 적용됨
        dog.draw()
        println(&quot;멍멍이&quot;)
    }

    if (dog is Animal) { // true
            //Animal만 통과했기때문에 move만 있고 draw()는 없다.
        dog.move()
        println(&quot;멍멍&quot;)
    }

    if (dog !is Cat) { // true
        println(&quot;이 객체는 고양이가 아님&quot;)
    }
}

interface Drawable {
    fun draw()
}

abstract class Animal {
    open fun move() {
        println(&quot;이동&quot;)
    }
}

class Dog : Animal(), Drawable {
    override fun move() {
        println(&quot;후다닥&quot;)
    }
    override fun draw() {
        println(&quot;그림 그리기&quot;)
    }
}

class Cat : Animal() {
    override fun move() {
        println(&quot;살금&quot;)
    }
}</code></pre>
<h2 id="정리-포인트">정리 포인트</h2>
<ul>
<li><code>is</code> → 타입 확인, 스마트 캐스트 지원</li>
<li><code>!is</code> → 특정 타입이 아님을 확인</li>
<li><code>as</code> → 강제 캐스팅 (잘못된 경우 예외 발생)</li>
<li><code>as?</code> → 안전한 캐스팅 (실패 시 <code>null</code> 반환)</li>
<li>자바와 달리 코틀린은 <code>instanceof</code> 대신 <code>is</code> 사용, 명확하고 간결함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 30802번 - 웰컴 키트]]></title>
            <link>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-30802%EB%B2%88-%EC%9B%B0%EC%BB%B4-%ED%82%A4%ED%8A%B8</link>
            <guid>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-30802%EB%B2%88-%EC%9B%B0%EC%BB%B4-%ED%82%A4%ED%8A%B8</guid>
            <pubDate>Tue, 19 Aug 2025 02:11:31 GMT</pubDate>
            <description><![CDATA[<h1 id="📂-백준-30802번---웰컴-키트">📂 백준 30802번 - 웰컴 키트</h1>
<p><a href="https://www.acmicpc.net/problem/30802">문제 링크</a></p>
<hr>
<h2 id="📝-문제-설명">📝 문제 설명</h2>
<p>2024년 2월 3일 개최 예정인 온사이트 그랜드 아레나에서는 참가자들에게 티셔츠 한 장과 펜 한 자루가 포함된 웰컴 키트를 나눠줄 예정입니다. 키트를 제작하는 업체는 다음과 같은 조건으로만 주문이 가능합니다.</p>
<p>티셔츠는 S, M, L, XL, XXL, 그리고 XXXL의 6가지 사이즈가 있습니다. 티셔츠는 같은 사이즈의 
$T$장 묶음으로만 주문할 수 있습니다.
펜은 한 종류로, 
$P$자루씩 묶음으로 주문하거나 한 자루씩 주문할 수 있습니다.
총 
$N$명의 참가자 중 S, M, L, XL, XXL, XXXL 사이즈의 티셔츠를 신청한 사람은 각각 
$S, M, L, XL, XXL, XXXL$명입니다. 티셔츠는 남아도 되지만 부족해서는 안 되고 신청한 사이즈대로 나눠주어야 합니다. 펜은 남거나 부족해서는 안 되고 정확히 참가자 수만큼 준비되어야 합니다.</p>
<p>티셔츠를 
$T$장씩 최소 몇 묶음 주문해야 하는지, 그리고 펜을 
$P$자루씩 최대 몇 묶음 주문할 수 있고, 그 때 펜을 한 자루씩 몇 개 주문하는지 구하세요.</p>
<p>첫 줄에 참가자의 수 
$N$이 주어집니다. 
$(1 \le N \le 10^9)$ </p>
<p>둘째 줄에 티셔츠 사이즈별 신청자의 수 
$S, M, L, XL, XXL, XXXL$이 공백으로 구분되어 주어집니다. 
$(0 \le S, M, L, XL, XXL, XXXL \le N;$ 
$S + M + L + XL + XXL + XXXL = N)$ </p>
<p>셋째 줄에 정수 티셔츠와 펜의 묶음 수를 의미하는 정수 
$T$와 
$P$가 공백으로 구분되어 주어집니다. 
$(2 \le T, P \le 10^9)$ </p>
<hr>
<h2 id="출력">출력</h2>
<p>첫 줄에 티셔츠를 
$T$장씩 최소 몇 묶음 주문해야 하는지 출력하세요.
다음 줄에 펜을 
$P$자루씩 최대 몇 묶음 주문할 수 있는지와, 그 때 펜을 한 자루씩 몇 개 주문하는지 구하세요.</p>
<hr>
<h2 id="✅-입력-예시">✅ 입력 예시</h2>
<p>23</p>
<p>3 1 4 1 5 9</p>
<p>5 7</p>
<h2 id="✅-예제-출력">✅ 예제 출력</h2>
<p>7</p>
<p>3 2</p>
<p>ex ) S, M, L, XL, XXL 사이즈 티셔츠를 $1$묶음씩 구매하고 XXXL 사이즈 티셔츠를 $2$묶음 구매합니다.</p>
<h2 id="💡-풀이-코드-풀이코드한-언어">💡 풀이 코드 (풀이코드한 언어)</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class bj_30802 {

    public static void main(String[] args) throws IOException {

        //티셔츠는 남아도 되지만 부족해서는 안됨으로 (사이즈 선택한 사람수/ 티셔츠 묶음 수) 나머지 있으면 추가 주문
        //펜은 정확히 참가자 수 만큼 준비 (전체인원수/펜 묶음 수) 나머지 낱개

        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(bf.readLine()); // 참가자 수

        int[] sizeArr = new int[6]; //사이즈 배열

        StringTokenizer st = new StringTokenizer(bf.readLine());

        //각 사이즈안에 선택한 사람수 넣어주기
        for(int i =0; i&lt;sizeArr.length; i++) {
            sizeArr[i] = Integer.parseInt(st.nextToken());
        }

        //셋째줄 값 받아서 넣기
        st = new StringTokenizer(bf.readLine());
        int T = Integer.parseInt(st.nextToken()); // 정수 티셔츠
        int P = Integer.parseInt(st.nextToken()); // 펜의 묶음 수 

        //묶음 수
        int cnt = 0;
        //(사이즈 선택한 사람수/ 티셔츠 묶음 수) 나머지 있으면 추가 주문
        for(int i = 0; i&lt;sizeArr.length; i++) {
            cnt += sizeArr[i] / T;

            //나머지가 있을경우 +1 아니면 그대로
            cnt = sizeArr[i] % T &gt; 0? cnt + 1 : cnt;
        }

        //t장씩 최소 묶음 주문 수
        System.out.println(cnt);
        //P자루씩 묶음수와 나머지 =&gt; 몫과 나머지
        System.out.println(N/P + &quot; &quot; + N%P);

    }

}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 상속 (`extends`)과 인터페이스 구현]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%83%81%EC%86%8D-extends%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%83%81%EC%86%8D-extends%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 18 Aug 2025 06:39:53 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-문법---상속-extends과-인터페이스-구현">Kotlin 문법 - 상속 (<code>extends</code>)과 인터페이스 구현</h1>
<h2 id="상속-inheritance">상속 (Inheritance)</h2>
<ul>
<li>코틀린에서 클래스는 기본적으로 <strong>final</strong> (상속 불가)</li>
<li>상속이 필요하다면 클래스 선언 앞에 <code>open</code> 키워드를 붙여야 함</li>
<li>메소드나 프로퍼티도 기본적으로 오버라이드 불가 → <code>open</code> 키워드 필요</li>
<li>오버라이드 시 반드시 <code>override</code> 키워드 사용</li>
</ul>
<hr>
<h2 id="인터페이스-interface">인터페이스 (Interface)</h2>
<ul>
<li><code>interface</code> 키워드로 선언</li>
<li>여러 개를 동시에 구현 가능 (<code>class A : B(), C, D</code>)</li>
<li>함수는 기본적으로 추상 메소드이지만, <strong>구현이 포함된 디폴트 메소드</strong>도 정의 가능</li>
<li><code>override</code> 키워드를 사용해 구현</li>
</ul>
<hr>
<h2 id="실습-코드">실습 코드</h2>
<pre><code class="language-kotlin">fun main() {
    val dog = Dog()
    dog.move() // 후다닥
    dog.draw()

    val cat = Cat()
    cat.move() // 살금
}

interface Drawable { // 인터페이스
    fun draw()
}

abstract class Animal { // 추상 클래스
    open fun move() {
        println(&quot;이동&quot;)
    }
}

class Dog : Animal(), Drawable {
    // Animal의 move() 오버라이드
    override fun move() {
        println(&quot;후다닥&quot;)
    }

    // Drawable 인터페이스 구현
    override fun draw() {
        println(&quot;그림 그리기&quot;)
    }
}

class Cat : Animal() {
    override fun move() {
        println(&quot;살금&quot;)
    }
}</code></pre>
<h2 id="정리-포인트">정리 포인트</h2>
<ul>
<li><strong>기본 클래스는 상속 불가</strong> → <code>open</code> 키워드를 붙여야 상속 가능</li>
<li><strong>함수도 마찬가지</strong> → <code>open</code>이 없으면 오버라이드 불가</li>
<li><code>abstract class</code>는 일부 구현만 제공 가능, 객체 생성 불가</li>
<li><code>interface</code>는 다중 구현 가능 → 클래스는 다중 상속 불가하지만 인터페이스로 보완 가능</li>
<li>오버라이드 시 항상 <code>override</code> 키워드 필수</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - Backing Field 개념]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-Backing-Field-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-Backing-Field-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Mon, 18 Aug 2025 05:54:12 GMT</pubDate>
            <description><![CDATA[<h2 id="backing-field란">Backing Field란?</h2>
<ul>
<li>프로퍼티를 정의할 때, 코틀린이 자동으로 생성해주는 <strong>실제 값을 저장하는 공간</strong></li>
<li><code>getter</code>/<code>setter</code>를 직접 정의할 때 <strong>프로퍼티를 자기 자신으로 참조하면 무한 루프</strong>에 빠질 수 있음 → 이를 막기 위해 <code>field</code>라는 키워드를 사용</li>
</ul>
<hr>
<h2 id="기본-동작">기본 동작</h2>
<pre><code class="language-kotlin">class Person {
    var name: String = &quot;홍길동&quot;
        get() = field        // name 프로퍼티의 실제 저장값 반환
        set(value) {
            field = value    // name의 실제 저장값을 변경
        }
}</code></pre>
<ul>
<li><code>field</code> → 프로퍼티의 <strong>backing field</strong>를 의미</li>
<li><code>getter</code>에서 <code>name</code>을 직접 호출하면 <code>getter</code>가 무한히 호출됨 → 대신 <code>field</code>를 사용해야 함</li>
</ul>
<hr>
<h2 id="무한-루프-예시-잘못된-코드">무한 루프 예시 (잘못된 코드)</h2>
<pre><code class="language-kotlin">class Person {
    var name: String = &quot;홍길동&quot;
        get() = name   // ❌ 잘못된 접근 → getter 재귀 호출
}
</code></pre>
<p>➡️ <code>get()</code> 안에서 다시 <code>name</code>을 호출하므로 무한 루프 발생</p>
<hr>
<h2 id="커스텀-활용-예시">커스텀 활용 예시</h2>
<pre><code class="language-kotlin">class Person {
    var age: Int = 0
        set(value) {
            field = if (value &lt; 0) 0 else value // 음수 방지 로직
        }
}

fun main() {
    val p = Person()
    p.age = -5
    println(p.age) // 0
}
</code></pre>
<hr>
<h2 id="정리-포인트">정리 포인트</h2>
<ul>
<li><p><code>field</code>는 오직 <strong>프로퍼티 내부의 getter/setter</strong> 안에서만 사용 가능</p>
</li>
<li><p>backing field는 프로퍼티에 대한 <strong>실제 저장 공간</strong> 역할</p>
</li>
<li><p><code>val</code>은 setter가 없으므로 <strong>읽기 전용 backing field</strong>만 가짐</p>
</li>
<li><p><code>custom getter</code>만 정의하고 <code>field</code>를 쓰지 않으면 backing field 자체가 생성되지 않음</p>
<p>  (즉, 계산된 값만 반환하는 프로퍼티로 동작)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin 문법 - 접근자, 게터/세터 (`getter`, `setter`)]]></title>
            <link>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%A0%91%EA%B7%BC%EC%9E%90-%EA%B2%8C%ED%84%B0%EC%84%B8%ED%84%B0-getter-setter</link>
            <guid>https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-%EC%A0%91%EA%B7%BC%EC%9E%90-%EA%B2%8C%ED%84%B0%EC%84%B8%ED%84%B0-getter-setter</guid>
            <pubDate>Mon, 18 Aug 2025 05:50:51 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-문법---접근자-게터세터-getter-setter">Kotlin 문법 - 접근자, 게터/세터 (<code>getter</code>, <code>setter</code>)</h1>
<h2 id="학습-요약">학습 요약</h2>
<ul>
<li><code>val</code> → 읽기 전용(기본적으로 getter만 생성)</li>
<li><code>var</code> → 읽기/쓰기 가능(getter, setter 모두 생성)</li>
<li><code>private set</code> → 외부에서 setter 접근 차단 (읽기 전용처럼 사용 가능)</li>
<li><code>get()</code> → 커스텀 게터 정의 가능 (<code>field</code> 키워드를 사용)</li>
<li><code>data class</code> → <code>equals</code>, <code>hashCode</code>, <code>toString</code> 등을 자동으로 생성하여 객체 비교나 출력에 용이</li>
</ul>
<hr>
<h2 id="실습-코드">실습 코드</h2>
<pre><code class="language-kotlin">fun main() {
    val john = Person(&quot;john&quot;, 20)
    val john2 = Person(&quot;john&quot;, 20)

    // 기본 클래스는 서로 다른 인스턴스 → 주소값 출력
    println(john)   // Person@735f7ae5
    println(john2)  // Person@180bc464
    println(john == john2) // false

    // 게터(get) 오버라이드 활용
    println(john.hobby) // &quot;취미 : 축구&quot;

    // 데이터 클래스 사용 시, 값 비교로 동등성 판단
    /*
    data class Person(val name: String, var age: Int)
    println(john == john2) // true
    */
}

class Person(
    val name: String,
    var age: Int,
) {
    var hobby = &quot;축구&quot;
        private set // 외부에서 hobby 변경 불가
        get() = &quot;취미 : $field&quot; // field 키워드 사용하여 backing field 접근

    init {
        println(&quot;init&quot;) // 객체 생성 시 자동 실행
    }

    fun some() {
        hobby = &quot;야구&quot; // 클래스 내부에서는 변경 가능
    }
}</code></pre>
<h2 id="정리-포인트">정리 포인트</h2>
<ul>
<li><strong>기본 getter/setter 자동 생성</strong><ul>
<li><code>val</code> → <code>getter</code>만 생성</li>
<li><code>var</code> → <code>getter</code> + <code>setter</code> 생성</li>
</ul>
</li>
<li><strong>커스텀 getter/setter</strong><ul>
<li><code>get()</code> 또는 <code>set()</code> 오버라이드 가능</li>
<li><code>field</code> → 프로퍼티의 backing field를 가리킴</li>
</ul>
</li>
<li><strong>접근 제어</strong><ul>
<li><code>private set</code> → 외부에서 값 변경 차단 (읽기 전용처럼 사용)</li>
</ul>
</li>
<li><strong>객체 비교</strong><ul>
<li>일반 클래스: 참조(주소) 비교</li>
<li><code>data class</code>: 값 비교 (자동으로 <code>equals/hashCode/toString</code> 생성)</li>
</ul>
</li>
</ul>
<h2 id="backing-field란">backing field란?</h2>
<p><a href="https://velog.io/@subin_k/Kotlin-%EB%AC%B8%EB%B2%95-Backing-Field-%EA%B0%9C%EB%85%90">정리한 내용 링크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1546번 - 평균]]></title>
            <link>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-1546%EB%B2%88-%ED%8F%89%EA%B7%A0</link>
            <guid>https://velog.io/@subin_k/%EB%B0%B1%EC%A4%80-1546%EB%B2%88-%ED%8F%89%EA%B7%A0</guid>
            <pubDate>Mon, 18 Aug 2025 04:54:59 GMT</pubDate>
            <description><![CDATA[<h1 id="📂-백준-1546번---평균">📂 백준 1546번 - 평균</h1>
<p><a href="https://www.acmicpc.net/problem/1546">문제 링크</a></p>
<hr>
<h2 id="📝-문제-설명">📝 문제 설명</h2>
<p>세준이는 기말고사를 망쳤다. 세준이는 점수를 조작해서 집에 가져가기로 했다. 일단 세준이는 자기 점수 중에 최댓값을 골랐다. 이 값을 M이라고 한다. 그리고 나서 모든 점수를 점수/M*100으로 고쳤다.</p>
<p>예를 들어, 세준이의 최고점이 70이고, 수학점수가 50이었으면 수학점수는 50/70*100이 되어 71.43점이 된다.</p>
<p>세준이의 성적을 위의 방법대로 새로 계산했을 때, 새로운 평균을 구하는 프로그램을 작성하시오.
첫째 줄에 시험 본 과목의 개수 N이 주어진다. 이 값은 1000보다 작거나 같다. 둘째 줄에 세준이의 현재 성적이 주어진다. 이 값은 100보다 작거나 같은 음이 아닌 정수이고, 적어도 하나의 값은 0보다 크다.</p>
<hr>
<h2 id="출력">출력</h2>
<p>첫째 줄에 새로운 평균을 출력한다. 실제 정답과 출력값의 절대오차 또는 상대오차가 10-2 이하이면 정답이다.</p>
<hr>
<h2 id="✅-입력-예시">✅ 입력 예시</h2>
<p>3</p>
<p>10 20 30</p>
<h2 id="✅-예제-출력">✅ 예제 출력</h2>
<p>66.666667</p>
<h2 id="💡-풀이-코드-풀이코드한-언어">💡 풀이 코드 (풀이코드한 언어)</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class bj_1546 {

    public static void main(String[] args) throws NumberFormatException, IOException {

        //입력 받은 값중 최댓값 M을 사용하여 모든 점수에 (점수/M)*100으로 연산을 해준다
        //그 다음 새로운 평균을 구한다.

        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        //배열 생성
        //시험 본 과목수 받기
        double arr[] = new double[Integer.parseInt(bf.readLine())];



        StringTokenizer st = new StringTokenizer(bf.readLine(),&quot; &quot;);

        //각 과목점수 배열에 넣기
        for(int i = 0; i&lt;arr.length; i++) {
            arr[i] = Double.parseDouble(st.nextToken());
        }

        //새로운 점수값 더할 변수 선언
        double sum = 0;

        //배열 정렬을 하면 오름차순 정렬이됨
        //마지막 원소가 최댓값이 된다. arr[arr.length-1]
        Arrays.sort(arr);

        for(int i =0; i&lt; arr.length; i++) {

            //새로운 값을 전체 더하기
            //(점수/최댓값)*100
            sum += ((arr[i]/arr[arr.length-1])*100);
        }

        //평균 출력
        System.out.println(sum/arr.length);
    }

}

</code></pre>
]]></description>
        </item>
    </channel>
</rss>