<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>first_log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 09 Mar 2026 14:04:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>first_log</title>
            <url>https://velog.velcdn.com/images/first_woosun/profile/c95e8025-090c-4187-9e63-35fc5643139f/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. first_log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/first_woosun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[우테코 레벨 1 회고 (2) - 우테코에서 생존 당하기]]></title>
            <link>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A8-1-%ED%9A%8C%EA%B3%A0-2-%EC%9A%B0%ED%85%8C%EC%BD%94%EC%97%90%EC%84%9C-%EC%83%9D%EC%A1%B4-%EB%8B%B9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A8-1-%ED%9A%8C%EA%B3%A0-2-%EC%9A%B0%ED%85%8C%EC%BD%94%EC%97%90%EC%84%9C-%EC%83%9D%EC%A1%B4-%EB%8B%B9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 09 Mar 2026 14:04:03 GMT</pubDate>
            <description><![CDATA[<h2 id="안녕하세요">안녕하세요!</h2>
<p>크루 닉네임 <strong>투핸더</strong>입니다. 주말동안 야무지게 놀다가 일요일 밤 자기 전에 생각나서 끄적이는 회고록입니다.
<del>일요일에 적다가 미션 제출 찐빠나서 월요일에 마무리했다.</del></p>
<hr>
<h2 id="이번-주엔-뭘-했지">이번 주엔 뭘 했지?</h2>
<ol>
<li>SS 연극</li>
<li>안드로이드 트랙 미션 시작 (KanbanBoard Mission)<ul>
<li>첫 번째 PR 제출 및 피드백 반영</li>
<li>두 번째 PR 제출</li>
</ul>
</li>
<li>SS 워크샵 (학습 마인드셋, 단점 개선을 위한 단기적 실험 계획 세우기)</li>
</ol>
<h2 id="keep">Keep</h2>
<ul>
<li>연극조 크루들 외에도 다른 안드로이드 트랙 크루들과도 이래저래 대화 나누면서 친해진 것 같아서 기분이 좋습니다.</li>
<li>학습 마인드셋 가져가기<ul>
<li>“우테코에 가면 나보다 경험도 많고 개발적으로도, 사회인으로서도 나보다 성숙한 사람이 많을 텐데 그 사람들을 보면서 내 부족한 점을 파악하고 그 사람들의 행동으로 부터 배우자!” 라는 다짐을 우테코 시작 전부터 가지고 있었습니다. 모르는 것도 많고 개발자로써도, 사회인으로써도 부족한 점이 많지만 여러 크루들로부터 배우고 성장하겠습니다. 또한 우테코 생활에서 도움의 손길을 선뜻 내어주시는 여러 크루들에게 감사하다는 말을 전하고 싶습니다!!</li>
</ul>
</li>
</ul>
<h2 id="problem">Problem</h2>
<ul>
<li>과제 진행 과정에서 실수가 많다. (대부분 마음이 급해서 발생한 실수들인 것 같습니다…)<ul>
<li>레포를 로컬로 가져와서 브랜치의 현제 상태를 원격 저장소와 sync 시키고 나서 작업을 했어야 하는데 그냥 진행해서 원격 저장소에 push가 안되는 일이 있었습니다. 이 때도 크루에게 도움을 받아 문제를 해결하고 미션 결과물을 제출할 수 있었습니다.</li>
<li>이번 미션의 2단계 미션을 집에서 진행하고 PR을 생성한 후 리뷰 요청을 남겼는데 slack 리뷰 요청에 대한 메세지가 남지 않아 뭔가 이상함을 느끼고 있었습니다. 이 때 slack에 한 크루로부터 개인 DM 하나가 날아왔습니다. 제가 생성한 PR을 보고 제 PR이 개인 브랜치가 아닌 main 브랜치로 생성되있는 점을 짚어주셔서 문제점을 파악하고 해결할 수 있었습니다.</li>
</ul>
</li>
</ul>
<h2 id="try">Try</h2>
<ul>
<li>뭔가 일을 하기 전에 해야할 일을 정리하고 마음을 가다듬기<ul>
<li>마음만 급해서 확인해야 할 요소 또는 선행 과제를 체크하지 않고 일단 진행했다가 피를 많이 봤습니다. 우테코의 여러 피로 쓰인 규칙과 팁들이 왜 생긴 건지 직접 체감할 수 있었습니다…</li>
<li>이제부턴 미션을 수행하기 전에 요구사항을 정독하고 체크리스트를 만들어 수행해야 할 일과 수행한 일을 관리해보려 합니다.</li>
</ul>
</li>
</ul>
<h2 id="마무리하며">마무리하며</h2>
<p>다른 사람들 보다 뒤쳐지진 않을까, 내가 잘 하고 있는 걸까, 돈 문제 등등 여러 걱정들 때문에 마음이 급해져 허둥대다가 끝난 한 주였습니다. </p>
<p>다음 주를 여유롭게 시작하고 싶어 주말에 과제를 해 놓고 브랜치 분리라던지 README 최신화 라던지 이래저래 빼먹고 머지시킬 브랜치도 틀리고 북치고 장구치고 하다가 새벽까지 문제 해결하고 늦게 자고 등등 많은 일들이 있었습니다.</p>
<p>그 와중에 제가 실수했다는 걸 저보다 빨리 캐치해 알려주신 안드로이드 크루에게 감사드립니다… (사람 여러번 살리셨습니다 아키….)</p>
<p>쥐뿔도 모르는 상태로 시작한 우테코에서 많은 분들의 도움을 받으며 하루하루 넘기고 있는 요즘입니다. 이러다 보면 언젠가 성장해있는 저를 만날 수 있겠죠? 화이팅 하겠습니다. 😌😌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 레벨 1 첫번째 회고]]></title>
            <link>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A8-1-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A8-1-%EC%B2%AB%EB%B2%88%EC%A7%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 01 Mar 2026 14:37:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2026-03-01에 작성한 회고록입니다.</p>
</blockquote>
<h2 id="안녕하세요-저는-투핸더입니다">안녕하세요! 저는 <strong>‘투핸더’</strong>입니다.</h2>
<p>우테코 레벨 1이 시작되고 한 주가 지났습니다. </p>
<p>지난 일주일간 어떤 걸 했고 뭘 느꼈는지 정리하고 공유하고자 합니다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/first_woosun/post/58de76fe-673d-4d6b-87b3-a933ab2d70d7/image.jpg" width="700">
</div>
(책 읽다가 갑자기 찍은 사진입니다. 그냥 올려보고 싶었습니다 ㅋㅋㅋ)


<h2 id="무엇을-했나">무엇을 했나?</h2>
<ul>
<li><p>우테코 OT</p>
<ul>
<li>여러 코치님들의 닉네임과 앞으로의 생활이 어떤 식으로 흘러갈지 알 수 있었습니다.</li>
</ul>
</li>
<li><p>온보딩 미션 (연극, Gemini 웹앱 제작)</p>
<ul>
<li>다음 주에 연극을 하는데 저희 조는 메시지보단 “일단 웃기자!”라는 생각으로 준비했습니다.</li>
<li>웹앱 4개를 만들었는데 개인적으로 퀄리티가 많이 아쉽습니다. 미션 요구사항을 체크하고 빠르게 구현에 들어갔어야 하는데 요구사항을 반복해서 읽느라 구현에 할애해도 아쉬운 시간을 만족스럽게 쓰지 못했습니다.</li>
</ul>
</li>
<li><p>README 작업 (프로젝트, 졸업 작품)</p>
<ul>
<li>프로젝트를 진행하면서 미루고 미루던 작업을 주말 동안 몰아서 하느라 죽을 맛이었습니다…</li>
<li><a href="https://github.com/first-woosun/cozyIoT">졸업 작품</a></li>
<li><a href="https://github.com/HaYoung1018/DDIP_Client_rev">팀 프로젝트</a></li>
</ul>
</li>
</ul>
<h2 id="keep">Keep</h2>
<p>적어보려고 이것저것 떠올려 봤지만 뭔가 “나 이거 잘했다!” 라고 하기엔 당연한 것들인 거 같아서 이번엔 패스하겠습니다.</p>
<h2 id="problem">Problem</h2>
<h3 id="1-까먹는다-너무-잘">1. 까먹는다. 너무 잘..</h3>
<p>우테코가 2월 24일 화요일에 시작돼 4일 동안 출근했고 이 짧은 시간 동안에도 생각보다 할 게 많았습니다.</p>
<p>웹앱도 만들어야 하고, 연극도 준비해야 하고, 책도 읽어야 하고…</p>
<p>이런저런 일들을 다른 사람들과 같이 하다 보니 그 과정에서 정말 많은 대화를 하게 되는데,</p>
<p>방금 대화하고 뒤돌아서면 얼마 지나지 않아 대부분의 내용을 까먹고 이후 작업을 할 때 여러 번 되물었던 것 같습니다.</p>
<p>물어봤던 걸 또 물어보고, 또 기억 안 나서 다시 물어보자니 시간을 뺏는 것 같아 죄송하고 참 난감한 상황을 마주했었습니다.</p>
<h3 id="2-요구사항을-정확히-파악하지-않는다">2. 요구사항을 정확히 파악하지 않는다.</h3>
<p>우테코에서 미션을 진행하려면 LMS에서 미션을 확인한 다음 구현하는 형식으로 흘러가는 것 같습니다.</p>
<p>이번 주에 진행한 Gemini 웹앱 미션도 LMS에서 자세한 내용을 확인할 수 있었고, 제가 온 신경을 쏟은 부분은 구현 요구사항이었습니다.</p>
<p>하지만 우테코의 미션에는 구현 외에도 제출 이후에 해야 할 일들, 피드백 반영, 피드백에 대한 소감 등 해야 할 추가적인 일들이 있었고, 이를 뒤늦게 알아차려 시간이 얼마 남지 않았거나 시간이 지난 후에 작업을 부랴부랴 진행했습니다.</p>
<h2 id="try">Try</h2>
<h3 id="1-일단-적어두자-이런거-저런거-다">1. 일단 적어두자. 이런거 저런거 다</h3>
<p>2개의 Problem에 모두 적용할 수 있는 방법입니다. </p>
<p>간단한 토론, 공지사항, 미션 요구사항 등을 요약하고 기록해 놓으면 하루를 시작할 때 오늘 할 일을 정리 해놓거나 앞으로 해야 할 일들을 정리하는데 도움이 될 거라 생각합니다.</p>
<p>이를 실천하기 위해 26일 목요일부터 Daily Diary를 적고 있습니다.</p>
<p>하루를 시작할 때 1차적으로 TODO를 기록하고 일과를 진행하면서 틈틈히 체크하고 추가하면서 할 일을 관리하고 있습니다.</p>
<h3 id="2-미션-시작-전-요구사항을-정리한-문서를-만들기">2. 미션 시작 전 요구사항을 정리한 문서를 만들기</h3>
<p>본격적으로 미션을 수행하기 전에 LMS의 공지글을 정리하고 작업을 시작해보려 합니다.</p>
<p>아직 템플릿을 구체화하진 않았지만, 간단히 생각해보자면</p>
<ol>
<li>요구사항</li>
<li>TODO</li>
<li>제출 체크리스트</li>
<li>제출 후 해야할 일</li>
</ol>
<p>이 정도를 작성해놓을 것 같습니다.</p>
<h2 id="마무리하며">마무리하며</h2>
<p>4일이라는 짧은 시간동안 우테코를 찍먹 해봤는데, 그 짧은 시간 동안 스스로 부족한 점, 고치면 좋을 점 등 성장할 수 있는 방법들을 몇 개 발견할 수 있었습니다.</p>
<p>첫 주 미션으로 “유연함의 힘”이라는 도서를 읽는 미션도 있었는데, 읽으면서 앞으로의 마인드 셋을 어떻게 잡아야 할지도 감이 오는 것 같습니다.</p>
<p>정말 많은 것들이 몰아쳐서 연극 조 크루들과는 대화를 많이 한 것 같은데 다른 크루들과는 크게 대화를 못해서 아쉽다는 생각도 듭니다.</p>
<p>하지만 한편으론 이름과 얼굴을 잘 기억하지 못하는 점 때문에 여러 크루와 왁왁 대화를 해도 내일 내가 그 크루의 닉네임을 기억할 수 있을까? 하는 걱정도 피어오릅니다.</p>
<p>먼저 다가가는 것은 자신 있지만 그러고 나서 투페이스 마냥 다음 날 닉네임을 기억하지 못하더라도 미워하지 말아 주세요…😂😂</p>
<h2 id="앞으로-잘-부탁드립니다">앞으로 잘 부탁드립니다!!</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[4주차 회고]]></title>
            <link>https://velog.io/@first_woosun/4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@first_woosun/4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 23 Feb 2026 13:16:46 GMT</pubDate>
            <description><![CDATA[<h2 id="계획이-얼마나-지켜졌나">계획이 얼마나 지켜졌나?</h2>
<h3 id="공부를-계획만큼-하지-못했다">공부를 계획만큼 하지 못했다…</h3>
<p>처음 계획을 세울 당시에는 “분량이 많긴 해도 열심히 하면 4주 안에 할 수 있지 않을까?”하는 생각으로 계획을 세웠습니다.</p>
<p>하지만 언제나 그렇듯 현실은 계획대로 흘러가지 않았고, 계획을 세울 당시에 생각나지 않았던 일정, 생각하지 못한 변수 등 여러 요소들로 인해 처음 계획한 분량의 대략 70% 정도 학습을 진행했습니다.</p>
<h3 id="계획은-못-지켰지만-얻은-건-많다">계획은 못 지켰지만 얻은 건 많다!</h3>
<p>일단 가장 큰 건 규칙적인 생활이 어느 정도 자리를 잡았다는 것입니다.</p>
<p>종강만 하면 놀지 못한 것이 아쉬워 밤늦게까지 게임을 하고 다음 날 점심은 돼서야 눈을 떴었는데 </p>
<p>레벨 0을 진행하면서 이런 생활 습관에서 벗어나 아침에 침대에서 일어나 집안일을 하고 공부를 하는 등 부지런한 생활을 할 수 있는 기반이 만들어졌습니다.</p>
<p>한 주를 마무리하며 적는 회고록에서도 얻은 것이 있습니다.</p>
<p>해결해야 하는 일이 있거나 문제가 있을 때 이를 따로 기록해두지 않아 잊혀진 것들이 많았는데 회고록을 작성하면서 문제점들을 글로 작성하니 문제를 잊지 않고 개선하기 위해 노력을 할 수 있었습니다.</p>
<p>회고록을 작성하며 스스로를 돌아보고 돌아본 내용을 글로 적는 것이 어떻게 도움이 되는지 직접 느끼며 앞으로의 여정에서 방향을 잃지 않을 수 있는 이정표를 만들어갈 수 있겠다는 생각이 듭니다.</p>
<hr>
<h2 id="앞으로의-숙제">앞으로의 숙제</h2>
<ul>
<li><p>못 다한 공부 이어가기</p>
<p>  처음 계획 세운 공부를 마치지 못했지만 여기서 끝내지 않고 하루 30분이라도 시간을 내어 코틀린 공부를 이어 나갈 생각입니다. 물론 우테코 과정을 진행하다 보면 자연스레 공부하게 될 수도 있지만 처음 공부하는 것과 한 번 공부한 내용을 다시 다루는 것에는 큰 차이가 있을 거라 생각해 교재를 끝까지 정독하려 합니다.</p>
</li>
<li><p>계획을 지키지 못했을 때 실현 가능한 범위로 분할하기</p>
<p>  레벨 0을 진행하는 동안 계획을 지키지 못한 상황에서 저는 계획을 수정하기보다는 최초의 계획을 따라가기 위해 공부를 강행했습니다. 이렇게 해보니 의욕이 생기기보다는 공부해야 할 양에 짓눌려 의지도 떨어지고, 계획을 지키지 못할 현실이 보이니 점점 집중력도 떨어지는 것을 경험했습니다. 그래서 앞으로는 계획을 수행하는 데 차질이 생기면 현재 상황을 분석하고 대략적인 가용 시간을 작성해 실현 가능한 목표를 재구성하여 의지와 집중력이 떨어지지 않도록 계획을 수정하는 연습을 해보려 합니다.</p>
</li>
</ul>
<hr>
<h2 id="입학을-하루-앞두고-한-특별한-경험">입학을 하루 앞두고 한 특별한 경험</h2>
<p>실제로 출근하기 전 버스 노선 점검과 이동 소요 시간 등을 확인하기 위해 사전 답사 차원으로 우아한 형제들 사옥을 다녀왔습니다.</p>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/2e8a6d6e-d415-483a-80a3-e073797ccd7e/image.jpg" alt=""></p>
<p>직접 두 눈으로 보니 앞으로 이곳으로 출퇴근 한다는 사실이 실감났습니다.</p>
<p>사옥 앞에 저와 마찬가지로 사옥을 촬영하고 있는 사람들이 있었습니다. </p>
<p>같이 우테코를 진행하시는 분들이지 않을까 하여 먼저 다가가 말을 걸었습니다.</p>
<p>다행히 두 분 모두 8기 크루 분들이었고 간단한 스몰 토킹을 할 수 있었습니다.</p>
<p>(BE 정콩이님, FE 도넛님 만나서 정말 반가웠습니다!! ㅋㅋㅋㅋㅋ)</p>
<p>간단한 스몰 토킹 후 두 분께서 카페 가서 같이 공부하자고 먼저 권해주셔서 혼자 할 예정이었던 공부를 다른 사람과 함께 할 수 있어서 외롭지 않았습니다! ㅋㅋㅋㅋㅋㅋ</p>
<p>말주변이 없어 먼저 질문을 하지 못했지만 먼저 이런저런 것들을 질문해주시며 대화를 이끌어주셔서 즐겁게 답사를 진행할 수 있었습니다!! 😆😆</p>
<p>오늘의 경험이 정말 특별하고 즐거웠습니다. 앞으로 쌓아가야 할 인연들에 “내가 잘 할 수 있을까?” 하는 걱정이 있었지만 오늘 만난 소중한 인연 덕분에 걱정에서 기대로 변할 수 있었습니다.</p>
<p>아직 미숙하고 아는 것도 많이 없는 시골 출신 개발자 지망생이지만 소중한 인연으로 기억될 수 있도록 노력하겠습니다!</p>
<p>10개월의 여정 잘 부탁드립니다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어노테이션 선언과 적용]]></title>
            <link>https://velog.io/@first_woosun/%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%84%A0%EC%96%B8%EA%B3%BC-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@first_woosun/%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%84%A0%EC%96%B8%EA%B3%BC-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Mon, 23 Feb 2026 11:23:17 GMT</pubDate>
            <description><![CDATA[<ul>
<li>어노테이션 사용을 통해 선언에 추가적인 메타 데이터를 연관시킬 수 있음</li>
<li>어노테이션 설정 방식에 따라 소스코드, 컴파일된 클래스 파일, 런타임에 대해 작동하는 도구를 통해 메타데이터에 접근할 수 있음</li>
</ul>
<h2 id="어노테이션을-적용해-선언에-표지-남기기">어노테이션을 적용해 선언에 표지 남기기</h2>
<ul>
<li>어노테이션 적용 방법 → @와 어노테이션 이름을 선언 앞에 작성</li>
</ul>
<pre><code class="language-kotlin">@annotaionName
/*선언부*/</code></pre>
<ul>
<li><p>함수, 클래스 등 여러 코드 구성 요소에 어노테이션 적용 가능</p>
</li>
<li><p>JUnit 프레임워크와 kotlin.test를 사용한다면 테스트 메서드 앞에 “@test”를 붙일 수 있음</p>
</li>
</ul>
<pre><code class="language-kotlin">import kotlin.test.*

class MyTest{
        @test // 어놑이션을 통해 JUnit 프레임워크에 이 메서드를 테스트로 호출하라고 지시
        fun testTrue() {
                assertTrue(1 + 1 == 2)
        }
} </code></pre>
<h3 id="deprecated-어노테이션">@Deprecated 어노테이션</h3>
<ul>
<li><p>주석이 달린 선언을 더 이상 사용하지 않음으로 표시</p>
</li>
<li><p>선언이 다른 선언에 의해 대체 되었거나 해당 기능을 더 이상 지원하지 않게 됐음</p>
</li>
<li><p>최대 3가지의 파라미터를 받음</p>
<pre><code class="language-kotlin">  @Target(allowedTargets = [AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.TYPEALIAS])
  annotation class Deprecated(
      val message: String, 
      val replaceWith: ReplaceWith = ReplaceWith(&quot;&quot;), 
      val level: DeprecationLevel = DeprecationLevel.WARNING
  )</code></pre>
<ol>
<li>message → 사용 중단의 이유 설명 및 대체 API 사용을 권장하는 메시지</li>
<li>replaceWith (선택적) → 더 이상 사용하지 않을 API를 대체할 코드 조각을 지정</li>
<li>level → 점진적인 사용 중단<ul>
<li>level의 단계<ol>
<li>WARNING(경고) → 사용자에게 경고를 보내지만 컴파일이나 런타임이 중단되진 않음</li>
<li>ERROR(오류 )→ 이 선언을 사용하는 새로운 코드가 컴파일 되지 못하게 막음</li>
<li>HIDDEN(숨김) → API가 컴파일된 코드가 남아 예전에 컴파일된 코드와의 이진 호환성을 유지</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
<li><p>@Deprecated 예시</p>
</li>
</ul>
<pre><code class="language-kotlin">@Deprecated (&quot;Use RemoveAt(index) instead.&quot;, ReplaceWith(&quot;removeAt(index)&quot;))
fun remove(index: Int) { /*...*/ }</code></pre>
<ul>
<li>이런 선언이 있는 경우 remove함수를 사용하면</li>
</ul>
<h3 id="어노테이션의-인자">어노테이션의 인자</h3>
<ul>
<li>기본 타입의 값</li>
<li>문자열 이넘</li>
<li>클래스 참조</li>
<li>다른 어노테이션 클래스</li>
<li>위 요소들의 배열</li>
</ul>
<ol>
<li>클래스를 어노테이션 인자로 지정 → @MyAnnotation(MyClass::class)</li>
<li>다른 어노테이션을 인자로 지정 <ul>
<li>@Deprecated의 replaceWith도 어노테이션임</li>
<li>어노테이션 이름 앞에 @를 붙이지 않음</li>
</ul>
</li>
<li>배열을 인자로 지정<ul>
<li>@RequestMapping(path = [”/foo”, “/bar”])의 형식</li>
<li>각괄호를 사용하거나 arrayOf() 함수를 사용할 수 있음</li>
<li>자바에 선언한 어노테이션 클래스를 사용하면 value 파라미터가 필요에 따라 자동으로 가변 길이 인자로 변환됨</li>
</ul>
</li>
</ol>
<h3 id="어노테이션-인자-→-컴파일-시점에-알-수-있어야-함">어노테이션 인자 → 컴파일 시점에 알 수 있어야 함</h3>
<ul>
<li>임의의 프로퍼티를 인자로 지정할 수 없음</li>
<li>프로퍼티를 인자로 사용하려면 const로 선언해야 함<ul>
<li>const 변경자 → 프로퍼티를 컴파일 시점 상수로 취급</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">    const val TEST_TIMEOUT = 10L

    class MyTest {
            @Test
            @Timeout(TEST_TIMEOUT)
            fun testMethod() {
                    //...
            }
    }</code></pre>
<ul>
<li>const 변경자를 사용하지 않으면 “Only const val can be used in constant expression” 컴파일 오류가 발생함</li>
</ul>
<hr>
<h2 id="어노테이션-타깃-어노테이션이-참조할-수-있는-정확한-선언-지정">어노테이션 타깃 (어노테이션이 참조할 수 있는 정확한 선언 지정)</h2>
<ul>
<li>한 선언을 컴파일한 결과가 여러 자바 선언과 대응하는 경우가 자주 있음</li>
<li>코틀린 선언과 대응하는 여러 자바 선언에 각각 어노테이션을 붙일 경우가 있음<ul>
<li>코틀린 프로퍼티 → 자바 필드, 게터 메서드, 세터 메서드 및 그 파라미터 선언과 대응</li>
<li>주 생성자에서프로퍼티를 선언한 경우 접근자 메서드와 파라미터 외에 자바 생성자 파라미터와도 대등</li>
</ul>
</li>
<li>어노테이션을 붙일 때 이런 요소 중 어느 요소에 어노테이션을 붙일지 표시해야 함</li>
</ul>
<h3 id="사용-지점-타깃-선언">사용 지점 타깃 선언</h3>
<ul>
<li>어노테이션을 적용할 수 있는 요소의 종류 지정 (클래스, 함수, 속성, 표현식 등)</li>
<li>@와 어노테이션 이름 사이에 타깃을 지정</li>
</ul>
<pre><code class="language-kotlin">@target:AnnotationName

@get:JvmName(&quot;obtainCertificate&quot;)
@set:JvmName(&quot;putCertificate&quot;)</code></pre>
<ul>
<li>자바에서 선언된 어노테이션 → 기본적으로 프로퍼티의 필드에 어노테이션을 선언함</li>
<li>코틀린 어노테이션 → 프로퍼티에 직접 적용할 수 있는 어노테이션을 만들 수 있음</li>
</ul>
<h3 id="사용-지점-타깃-목록">사용 지점 타깃 목록</h3>
<ul>
<li>property → 프로퍼티 전체 (자바에서 선언된 어노테이션에선 사용 불가)</li>
<li>field → 프로퍼티에 의해 생성되는 필드</li>
<li>get → 프로퍼티 게터</li>
<li>set → 프로퍼티 세터</li>
<li>reciver → 확장 함수나 프로퍼티의 수신 객체 파라미터</li>
<li>param → 생성자 파라미터</li>
<li>setparam → 세터 파라미터</li>
<li>delegate → 위임 프로퍼티의 위인 인스턴스를 담아둔 필드</li>
<li>file → 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스<ul>
<li>package 선언보다 더 상위에 사용</li>
</ul>
</li>
</ul>
<h3 id="suppress-어노테이션">@Suppress 어노테이션</h3>
<ul>
<li>어노테이션 인자로 클래스, 함수 선언, 타입, 임의의 식을 받음</li>
<li>컴파일 경고를 억제</li>
</ul>
<pre><code class="language-kotlin">// 안전하지 못한 캐스팅 경고 무시하는 로컬 변수 선언
fun test(list: List&lt;*&gt;) {
        @Suppress(&quot;UNCHECKED_CAST&quot;)
        val strings = list as List&lt;String&gt;
}</code></pre>
<h3 id="자바-api를-제어하는-어노테이션">자바 API를 제어하는 어노테이션</h3>
<ul>
<li>@JvmName → 코틀린 선언이 만들어내는 자바 필드나 메서드의 이름을 변경</li>
<li>@JvmStatic → 객체 선언이나 동반 객체의 메서드에 적용하면 자바 정적 메서드로 노출됨</li>
<li>@JvmOverloads → 디폴트 파라미터가 있는 함수에 대해 자도으로 오버로딩한 함수를 생성</li>
<li>@JvmField → 대상 프로퍼티를 게터나 세터가 없는 public 자바 필드로 노출</li>
<li>@JvmRecord → data class에 사용하여 자바 레코드 클래스를 선언할 수 있음</li>
</ul>
<hr>
<h2 id="어노테이션을-활용해-json-직렬화-제어">어노테이션을 활용해 Json 직렬화 제어</h2>
<ul>
<li>직렬화 → 객체를 저장 장치에 저장하거나 네트워크를 통해 전송하기 위해 텍스트나 이진 형식으로 변환<ul>
<li>Json 형태로 주로 직렬화 함</li>
<li>코틀린 객체를 직렬화하기 위한 kotlinx.serialization 라이브러리가 있음</li>
<li>Jackson, GSON 등 자바 객체를 직렬화하기 위한 라이브러리도 호환됨</li>
</ul>
</li>
<li>역직렬화 → 텍스트나 이진 형식으로 저장된 데이터에서 원래의 객체로 변환</li>
</ul>
<h3 id="person-클래스-직렬화-역질렬화-예제">Person 클래스 직렬화, 역질렬화 예제</h3>
<pre><code class="language-kotlin">data class Person(val name: String, val age: Int)

fun main() {
    val person = Person(&quot;Alice&quot;, 29)
    // 직렬화
    println(serialize(person))
    // {&quot;age&quot; : 29, &quot;name&quot; : &quot;Alice&quot;}

    // 역직렬화
    val json = &quot;&quot;&quot;{&quot;name&quot; : &quot;Alice&quot;, &quot;age&quot; : 29}&quot;&quot;&quot;
    println(deserialize&lt;Person&gt;(json)) // Json 객체의 타입 정보를 명시해줘야 함
    // Person(name=Alice, age=29)
}</code></pre>
<h3 id="어노테이션을-통해-직렬화-역직렬화-방법-제어">어노테이션을 통해 직렬화, 역직렬화 방법 제어</h3>
<ul>
<li>Jkid 라이브러리 → 기본적으로 모든 프로퍼티를 직렬화하며 프로퍼티 이름을 키로 사용<ul>
<li>어노테이션을 통해 이런 동작을 변경할 수 있음</li>
</ul>
</li>
</ul>
<ol>
<li>@JsonExclude → 직렬화, 역직렬화 시 무시할 프로퍼티 지정</li>
<li>@JsonName → 키/값 쌍의 키를 지정</li>
</ol>
<pre><code class="language-kotlin">data class Person(
        @JsonName(&quot;alias&quot;) val firstName: String,
        @JsonExclude val age: Int? = null
)</code></pre>
<ul>
<li>@JsonExclude로 직렬화 대상에서 제외한 프로퍼티에는 반드시 기본값을 지정해야 함<ul>
<li>역직렬화로 인스턴스를 만들 때 필요</li>
</ul>
</li>
</ul>
<h3 id="어노테이션-선언">어노테이션 선언</h3>
<ul>
<li><p>어노테이션 클래스 → 선언이나 식과 관련 있는 메타데이터의 구조만 정의</p>
<ul>
<li><p>내부에 아무 코드도 들어있을 수 없음</p>
</li>
<li><p>@JsonExclude 예시</p>
<pre><code class="language-kotlin">annotation class JsonExclude</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>파라미터가 있는 어노테이션 → 클래스의 주 생성자에 파라미터를 선언</p>
<ul>
<li><p>일반적인 주 생성자 구문을 사용하면서 val로 선언</p>
</li>
<li><p>@JsonName 예시</p>
<pre><code class="language-kotlin">annotation class JsonName(val name: String)</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="자바-어노테이션-선언과-비교">자바 어노테이션 선언과 비교</h3>
<pre><code class="language-java">public @interface JsonName {
        String value();
}</code></pre>
<ul>
<li>자바 어노테이션 → value() 라는 메서드가 있음</li>
<li>어떤 어노테이션을 적용할 떄 value를 제외한 모든 애트리뷰트에 이름을 명시해야 함</li>
</ul>
<h3 id="메타어노테이션-어노테이션을-처리하는-방법-제어">메타어노테이션 (어노테이션을 처리하는 방법 제어)</h3>
<ul>
<li><p>어노테이션 클래스에도 어노테이션을 붙일 수 있음</p>
</li>
<li><p>어노테이션 클래스에 적용할 수 있는 어노테이션 → <strong>메타어노테이션</strong></p>
</li>
<li><p>메타어노테이션은 컴파일러가 어노테이션을 처리하는 방법을 제어함</p>
</li>
<li><p>@Target 메타어노테이션</p>
<ul>
<li><p>어노테이션을 적용할 수 있는 요소의 유형을  지정</p>
</li>
<li><p>@Target을 지정하지 않으면 모든 선언에 적용할 수 있는 어노테이션이 됨</p>
<pre><code class="language-java">@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude</code></pre>
</li>
<li><p>AnnotationTarget → 어노테이션 적용 가능한 타깃이 정의된 이넘</p>
<pre><code class="language-kotlin">  @Target(AnnotationTarget.CLASS, 
                  AnnotationTarget.FUNCTION,
          AnnotationTarget.TYPE_PARAMETER, 
          AnnotationTarget.VALUE_PARAMETER,
          AnnotationTarget.EXPRESSION)</code></pre>
</li>
<li><p>메타어노테이션을 직접 만들 수 있음</p>
<pre><code class="language-java">  @Target(AnnotationTarget.ANNOTATION_CLASS)
  annotation class BindingAnnotation

  @BindingAnnotation
  annotation class MyBinding</code></pre>
</li>
<li><p>대상을 PROPERTY로 지정한 어노테이션 → 자바에서 사용할 수 없음</p>
<ul>
<li>AnotationTarget.FIELD를 두 번째 타깃으로 추가해야 함</li>
<li>어노테이션을 코틀린 프로퍼티와 자바 필드에 적용할 수 있어짐</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="retention-어노테이션">@Retention 어노테이션</h3>
<ul>
<li>정의한 어노테이션의 유지 수준 지정<ul>
<li>소스 수준</li>
<li>.class 파일 수준</li>
<li>실행 시점에 리플렉션을 통한 접근 허용</li>
</ul>
</li>
<li>자바 → .class 수준이 디폴트<ul>
<li>하지만 대부분의 어노테이션을 런타임에도 사용할 수 있어야 함</li>
</ul>
</li>
<li>코틀린 → 디폴트로 RUNTIME으로 지정</li>
</ul>
<hr>
<h2 id="어노테이션-파라미터로-클래스-사용">어노테이션 파라미터로 클래스 사용</h2>
<ul>
<li>클래스 참조를 파라미터로 하는 어노테이션 클래스를 선언하여 클래스를 어노테이션 파라미터로 사용할 수 있음</li>
</ul>
<pre><code class="language-java">interface Company {
        val name: String
}

data class CompanyImpl(override val name: String) : Company

data class Person(
        val name: String,
        @DeserializeInterface(CompanyImpl::class) val company: Company
)</code></pre>
<ul>
<li>역직렬화 과정에서 company 프로퍼티를 표현하는 Json을 읽으면 Json을 역직렬화 하면서 CompanyImpl의 인스턴스를 만들어 Person 인스턴스의 company 프로퍼티를 설정함</li>
</ul>
<h3 id="desirializeinterface-구현">@DesirializeInterface 구현</h3>
<pre><code class="language-java">annotation class DesirializeInterface(val targetClass: KClasee&lt;out Any&gt;)</code></pre>
<ul>
<li>KClass 타입 → 코틀린 클래스에 대한 참조를 저장<ul>
<li>KClass의 타입 파라미터 → 인스턴스가 가리키는 코틀린 타입을 지정</li>
<li>CompanyImpl::class의 타입 → KClass<CompanyImpl><ul>
<li>KClass<out Any>의 하위 타입</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="어노테이션-파라미터로-제네릭-파라미터-받기">어노테이션 파라미터로 제네릭 파라미터 받기</h2>
<ul>
<li><p>jkid → 기본 타입이 아닌 프로퍼티를 내포된 객체로 직렬화 함</p>
</li>
<li><p>이런 기본 동작을 변경하려면 직렬화하는 로직을 직접 제공하면 됨</p>
</li>
<li><p>@CustomSerializer</p>
<ul>
<li><p>커스텀 직렬화 클래스에 대한 참조를 인자로 받음</p>
<ul>
<li><p>ValueSerializer 인터페이스를 구현한 클래스</p>
</li>
<li><p>직렬화, 역직렬화 제공</p>
<pre><code class="language-java">interface ValueSerializer&lt;T&gt; {
      fun toJsonValue(value: T) : Any?
      fun fromJsonValue(jsonValue: Any?): T
}</code></pre>
</li>
</ul>
</li>
<li><p>Person 클래스 적용 예제</p>
<pre><code class="language-java">  data class Person(
          val name: String,
          @CustomSerializer(DateSerializer::class) val birthDate: Date
  )</code></pre>
</li>
<li><p>@CustomSerializer 구현</p>
<pre><code class="language-java">  annotation class CustomSerializer(
          val serializerClass: KClass&lt;out ValueSerializer&lt;*&gt;&gt;
  )</code></pre>
<ul>
<li><p>ValueSerializer → 제네릭 클래스 (타입 파라미터가 있음)</p>
<ul>
<li><p>어떤 파라미터가 쓰일지 알 수 없음 → 스타프로젝션 사용</p>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/58c42533-383f-4ec0-aab0-a4b228089bc7/image.jpg" alt=""></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[변성은 제네릭과 타입 인자 사이의 하위 타입 관계를 기술]]></title>
            <link>https://velog.io/@first_woosun/%EB%B3%80%EC%84%B1%EC%9D%80-%EC%A0%9C%EB%84%A4%EB%A6%AD%EA%B3%BC-%ED%83%80%EC%9E%85-%EC%9D%B8%EC%9E%90-%EC%82%AC%EC%9D%B4%EC%9D%98-%ED%95%98%EC%9C%84-%ED%83%80%EC%9E%85-%EA%B4%80%EA%B3%84%EB%A5%BC-%EA%B8%B0%EC%88%A0</link>
            <guid>https://velog.io/@first_woosun/%EB%B3%80%EC%84%B1%EC%9D%80-%EC%A0%9C%EB%84%A4%EB%A6%AD%EA%B3%BC-%ED%83%80%EC%9E%85-%EC%9D%B8%EC%9E%90-%EC%82%AC%EC%9D%B4%EC%9D%98-%ED%95%98%EC%9C%84-%ED%83%80%EC%9E%85-%EA%B4%80%EA%B3%84%EB%A5%BC-%EA%B8%B0%EC%88%A0</guid>
            <pubDate>Mon, 23 Feb 2026 11:20:32 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<ul>
<li>변성 → 기저 타입이 같고 타입 인자가 다른 타입의 관계를 설명하는 개념</li>
<li>변성을 잘 활용하면 타입 안전성을 보장하는 API를 만들 수 있음</li>
</ul>
<hr>
<h2 id="변성은-인자를-함수에-넘겨도-안전한지-판단하게-해줌">변성은 인자를 함수에 넘겨도 안전한지 판단하게 해줌</h2>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/70828c00-377c-4d04-bab2-7c0dc52a797c/image.png" alt=""></p>
<ul>
<li>어떤 함수가 리스트의 원소를 추가하거나 변경한다면 타입 불일치가 생길 수 있어 Any 타입에 String 타입을 넘길 수 없음<ul>
<li>원소를 변경하거나 추가하지 않는다면 안전함</li>
</ul>
</li>
<li>코틀린에선 리스트의 변경 가능성에 따라 적절한 인터페이스를 선택하면 안전성을 제어할 수 있음</li>
</ul>
<hr>
<h2 id="클래스-타입-하위-타입">클래스, 타입, 하위 타입</h2>
<h3 id="하위-타입">하위 타입</h3>
<ul>
<li>A 타입이 필요한 곳에 B 타입을 넣어도 문제가 없다면 B는 A의 하위 타입임<ul>
<li>Int는 Number의 하위 타입임</li>
<li>String은 Number의 하위 타입이 아님</li>
</ul>
</li>
</ul>
<h3 id="상위-타입">상위 타입</h3>
<ul>
<li>하위 타입의 반대</li>
</ul>
<h3 id="하위-타입인지가-왜-중요한가">하위 타입인지가 왜 중요한가?</h3>
<ul>
<li>컴파일러 → 변수 대입이나 함수 인자 전달 시 하위 타입 검사를 매번 수행</li>
</ul>
<pre><code class="language-kotlin">fun test(i: Int) {
    val n: Number = i

    fun f(s: String) { /*...*/ }
    f(i)
}</code></pre>
<ul>
<li>Int는 Number의 하위 타입이어서 컴파일됨</li>
<li>Int는 String의 하위 타입이 아님 → 함수 f의 호출은 컴파일되지 않음</li>
</ul>
<h3 id="어떤-인터페이스를-구현한하는-클래스의-타입은-그-인처페이스의-하위-타입임">어떤 인터페이스를 구현한하는 클래스의 타입은 그 인처페이스의 하위 타입임</h3>
<ul>
<li>Int 클래스 → Number 클래스의 하위 클래스<ul>
<li>Int 타입 → Number 타입의 하위 타입</li>
</ul>
</li>
<li>String 클래스 → CharSequence 클래스의 하위 클래스<ul>
<li>String 타입 → CharSequence 타입의 하위 타입</li>
</ul>
</li>
</ul>
<h3 id="널이-될-수-있는-타입의-하위-타입과-하위-클래스는-같지-않음">널이 될 수 있는 타입의 하위 타입과 하위 클래스는 같지 않음</h3>
<ul>
<li>널이 될 수 있는 타입 → 널이 될 수 없는 타입의 하위 타입</li>
<li>하지만 두 타입은 같은 클래스에 속함<ul>
<li>널이 될 수 없는 값을 널이 될 수 있는 값에 저장할 수 있음</li>
<li>그 반대는 안됨</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">val s: String = &quot;abc&quot;
val t: String? = s</code></pre>
<h3 id="무공변">무공변</h3>
<ul>
<li>서로 다른 두 타입 A, B에 대해 A가 B의 상위 타입도 하위 타입도 아닌 경우를 <strong>무공변</strong>이라고 함</li>
<li>자바에서는 모든 클래스가 무공변임</li>
</ul>
<hr>
<h2 id="공변성">공변성</h2>
<h3 id="공변적인-클래스인터페이스">공변적인 클래스(인터페이스)</h3>
<ul>
<li><p>제네릭 클래스에 대해 A가 B의 하위 타입일 때 <code>Class&lt;A&gt;</code>가 <code>Class&lt;B&gt;</code>의 하위 타입인 경우</p>
</li>
<li><p>“하위 타입 관계를 유지한다.”라고 함</p>
</li>
<li><p>제네릭 클래스가 타입 파라미터에 대해 공변적임을 표시 → out 키워드</p>
<pre><code class="language-kotlin">  interface Producer&lt;out T&gt;{
          fun produce(): T
  }</code></pre>
</li>
<li><p>클래스의 타입 파라미터를 공변적으로 만들면 파라미터 타입과 타입 인자가 정확히 일치하지 않아도 그 클래스의 인스턴스를 함수 인자나 반환값으로 사용할 수 있음</p>
</li>
</ul>
<h3 id="무공변-컬렉션-역할을-하는-클래스-예시">무공변 컬렉션 역할을 하는 클래스 예시</h3>
<pre><code class="language-kotlin">open class Animal {
    fun feed() { /*...*/ }
}

class Herd&lt;T : Animal&gt; { // 이 타입 파라미터를 공변으로 지정하지 않음
    val size: Int get() = /*...*/

    operator fun get(i: Int): T { /*...*/ }
}

fun feddAll(animals: Herb&lt;Animal&gt;) {
    for(i in 0..&lt;animals.size){
        animals[i].feed()
    }
}</code></pre>
<h3 id="무공변-클래스-사용">무공변 클래스 사용</h3>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/85335cae-b1d9-4c9b-9cff-6ff61ecdd20f/image.png" alt=""></p>
<ul>
<li>feedAll 함수에 cats를 넘기면 타입 불일치 오류가 발생함<ul>
<li>Herb 클래스의 T 타입 파라미터에 대해 아무 변성도 지정하지 않았기 때문에 Cats는 Herb의 하위 클래스가 아님</li>
<li>명시적 타입 캐스팅을 통해 해결은 가능하나 코드가 복잡해짐</li>
</ul>
</li>
<li>Herb 클래스는 List와 비슷한 API를 제공하며 데이터를 추가하거나 변경할 수 없음<ul>
<li>Herb를 공변적인 클래스로 만들 수 있음</li>
</ul>
</li>
</ul>
<h3 id="컬렉션-역할을-하는-공변적인-클래스">컬렉션 역할을 하는 공변적인 클래스</h3>
<pre><code class="language-kotlin">class Herd&lt;out T : Animal&gt; { // T는 공변적임
    /*...*/
}

fun takeCareOfCats(cats: Herb&lt;Cat&gt;) {
    for(i in 0 ..&lt; cats.size) {
        cats[i].cleanLitter()
    }
    feddAll(cats) // 캐스팅을 할 필요 없음
}</code></pre>
<h3 id="모든-클래스를-공변적으로-만들면-안전한가">모든 클래스를 공변적으로 만들면 안전한가?</h3>
<ul>
<li>공변적으로 만들면 안전하지 못한 클래스도 있음</li>
<li>타입 파라미터를 공변적으로 지정 → 클래스 내부에서 그 파라미터를 사용하는 방법을 제한</li>
<li>타입 안전성을 보장하려면 타입 파라미터는 항상 out 위치에 있어야 함<ul>
<li>클래스가 T 타입의 값을 생성할 수는 있으나 소비할 수는 없음</li>
</ul>
</li>
</ul>
<h3 id="타입-파라미터를-사용할-수-있는-지점">타입 파라미터를 사용할 수 있는 지점</h3>
<ul>
<li>인(in)과 아웃(out)으로 나뉨</li>
<li>T라는 타입 파라미터를 사용하는 함수가 맴버로 있는 클래스<ul>
<li>T가 함수의 반환 타입 → 아웃 위치 (T 타입의 값을 생성)</li>
<li>T가 함수의 파라미터 타입 → 인 위치 (T 타입의 값을 소비)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/cd74ed98-213f-45ae-961d-915297bfa168/image.jpg" alt=""></p>
<ul>
<li>제네릭 타입 인자 앞에 out 키워드를 사용하면 클래스 안에서 T를 함수의 파라미터 타입으로 사용하지 못하도록 제한<ul>
<li>T의 사용법을 제한하며 T로 인해 생기는 하위 타입 관계의 타입 안전성을 보장</li>
</ul>
</li>
</ul>
<h3 id="out-키워드의-의미">out 키워드의 의미</h3>
<ul>
<li>하위 타입의 관계가 유지됨</li>
<li>T를 아웃 위치에만 사용할 수 있음</li>
</ul>
<h3 id="list의-공변성">List의 공변성</h3>
<pre><code class="language-kotlin">interface List&lt;out T&gt; : Collection&lt;T&gt; {
        operator fun get(index: Int): T
        /* ... */
}</code></pre>
<ul>
<li>코틀린 List는 읽기 전용 (원소 추가, 변경 불가) → List는 T에 대해서 공변적임</li>
</ul>
<h3 id="타입-파라미터를-다른-타입의-타입-인자로-사용">타입 파라미터를 다른 타입의 타입 인자로 사용</h3>
<pre><code class="language-kotlin">interface List&lt;out T&gt; : Collection&lt;T&gt; {
        fun subList(fromIndex: Int, toIndex: Int): List&lt;T&gt;
}</code></pre>
<ul>
<li>List<T>를 반환하는 subList 메서드</li>
</ul>
<h3 id="mutablelistt를-타입-파라미터-t에-대해-공변적일-수-없음">MutableList<T>를 타입 파라미터 T에 대해 공변적일 수 없음</h3>
<ul>
<li>T를 인자로 받아 그 타입의 값을 반환하는 메서드가 있기 때문</li>
</ul>
<pre><code class="language-kotlin">interface MutableList&lt;T&gt; : List&lt;T&gt;, MutableCollection&lt;T&gt; {
        override fun add(element: T): Boolean
}</code></pre>
<ul>
<li>공변적으로 선언할 경우 “Type parameter T is declared as ‘out’ but occurs in ‘in’ position”이라는 오류를 보고함</li>
</ul>
<h3 id="생서자-파라미터의-위치">생서자 파라미터의 위치</h3>
<ul>
<li>생성자 파라미터는 인, 아웃 어느 쪽도 아님</li>
<li>타입 파라미터가 out이라 하더라고 그 타입을 생성자 파라미터 선언에 사용할 수 있음</li>
</ul>
<pre><code class="language-kotlin">class Herd&lt;T: Animal&gt;(vararg animals: T) { /* ... */ }</code></pre>
<ul>
<li>변성은 코드에서 위험할 여지가 있는 메서드를 호출할 수 없게 만듦<ul>
<li>클래스 인스턴스를 잘못 사용하는 일이 없게 방지</li>
</ul>
</li>
<li>생성자는 인스턴스를 생성한 뒤에 호출할 수 있는 메서드가 아니므로 위험의 여지가 없음</li>
<li>val이나 var 키워드를 생성자 파라미터에 사용하면 getter/setter를 정의하는 것과 같음<ul>
<li>읽기 전용 프로퍼티 → 아웃 위치</li>
<li>변경 가능 프로퍼티 → 인/아웃 위치</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">class Herd&lt;T: Animal&gt;(val leadAnimal: T, vararg animals: T) { /*...*/ }</code></pre>
<ul>
<li>leadAnimal이 변경 가능 프로퍼티 이므로 T를 out으로 표시할 수 없음</li>
</ul>
<h3 id="위치-규칙-→-외부에서-볼-수-있는-클래스-api에만-적용할-수-있음">위치 규칙 → 외부에서 볼 수 있는 클래스 API에만 적용할 수 있음</h3>
<ul>
<li>비공개 메서드의 파라미터 → 인도 아니고 아웃도 아님</li>
<li>변성 규칙 → 클래스 내부 구현에는 적용되지 않음</li>
</ul>
<pre><code class="language-kotlin">class Herd&lt;out T: Animal&gt;(private val leadAnimal: T, vararg animals: T) { /*...*/ }</code></pre>
<ul>
<li>leadAnimal 프로퍼티가 비공개이기 때문에 T를 공변적으로 선언해도 안전함</li>
</ul>
<hr>
<h2 id="반공변성">반공변성</h2>
<ul>
<li>반공변성 → 공변성의 반전<ul>
<li>반공변 클래스의 하위 타입 관계 → 타입 파라미터 상하위 관계와 반대</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">interface Comparator&lt;in T&gt; {
        fun compare(e1: T, e2: T): Int { /*...*/ }
}</code></pre>
<ul>
<li>이 인터페이스의 메서드 → T 타입의 값을 소비하기만 함<ul>
<li>T 앞에 in이 붙어야 함</li>
</ul>
</li>
<li>어떤 타입에 대해 Comparator를 구현하면 그 타입의 하위 타입에 속하는 모든 값을 비교할 수 있음</li>
</ul>
<pre><code class="language-kotlin">sealed class Fruit {
        abstract val weight: Int
}

data class Apple(override val weight: Int, val color: String): Fruit()

data class Orange(override val weight: Int, val juicy: Boolean): Fruit()</code></pre>
<ul>
<li>이 코드에 Comparator<Fruit>를 구현하면 모든 하위 타입을 비교할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun main() {
    val weightComparator = Comparator&lt;Fruit&gt; { a, b -&gt; a.weight - b.weight}

    val fruits: List&lt;Fruit&gt; = listOf(Orange(180, true), 
                              Apple(100, &quot;green&quot;)
                              )

    val apples: List&lt;Apple&gt; = listOf(Apple(50, &quot;red&quot;),        
                                Apple(120, &quot;green&quot;), 
                              Apple(155, &quot;yellow&quot;)
                              )

    println(fruits.sortedWith(weightComparator))
    // [Apple(weight=100, color=green), Orange(weight=180, juicy=true)]
    println(apples.sortedWith(weightComparator))
    // [Apple(weight=50, color=red), Apple(weight=120, color=green), Apple(weight=155, color=yellow)]
}</code></pre>
<ul>
<li>sortedWith 함수 → <code>Comparator&lt;String&gt;</code>을 요구<ul>
<li>String보다 더 일반적인 타입을 비교할 수 있는 Comparator를 넘겨도 안전함</li>
</ul>
</li>
<li>어떤 타입의 객체를 Comparator로 비교할 때 그 타입의 조상 타입을 비교할 수 있는 Comparator를 사용할 수 있음<ul>
<li><code>Comparator&lt;Any&gt;</code>가 <code>Comparator&lt;String&gt;</code>의 하위 타입이라는 뜻</li>
<li>보통 Any는 String의 상위 타입임</li>
<li>서로 다른 타입 인자에 대해 Comparator의 하위 타입 관계는 타입 인자의 하위 타입 관계와 반대</li>
</ul>
</li>
</ul>
<h3 id="반공변성의-정의">반공변성의 정의</h3>
<ul>
<li>타입 B가 타입 A의 하위 타입일 때 <code>Class&lt;A&gt;</code>가 <code>Class&lt;B&gt;</code>의 하위 타입인 관계가 성립하면 제네릭 클래스는 타입 인자 T에 대해 반공변임</li>
</ul>
<h3 id="in-키워드">in 키워드</h3>
<ul>
<li>in 키워드가 붙은 타입이 클래스 내부의 메서드 안으로 전달돼 소비된다는 뜻</li>
<li>공변성과 마찬가지로 타입 파라미터의 사용을 제한하여 특정 하위 타입 관계에 도달할 수 있음</li>
<li>in 키워드를 붙이면 그 타입 파라미터를 in 위치에서만 사용할 수 있음</li>
</ul>
<h3 id="공변성-반공변성-무공변성-요약">공변성, 반공변성, 무공변성 요약</h3>
<ol>
<li>공변성<ul>
<li>타입 인자의 하위 타입 관계가 제네릭 타입에서도 유지</li>
<li>T를 아웃 위치에서만 사용할 수 있음</li>
</ul>
</li>
<li>반공변성<ul>
<li>타입 인자의 하위 타입 관계가 제네릭 타입에서 반전됨</li>
<li>T를 인 위치에서만 사용할 수 있음</li>
</ul>
</li>
<li>무공변성<ul>
<li>하위 타입 관계가 성립하지 않음</li>
<li>T를 아무 위치에서나 사용할 수 있음</li>
</ul>
</li>
</ol>
<hr>
<h2 id="사용-지점-변성">사용 지점 변성</h2>
<ul>
<li>클래스를 선언하면서 변성을 지정<ul>
<li>클래스를 사용하는 모든 장소에 변성 지정자가 영향을 끼침</li>
</ul>
</li>
<li>자바에서는 타입 파라미터를 사용할 때 그 타입 파라미터가 상위/하위 타입 중 어떤 타입으로 대치할 수 있는지 명시해야 함</li>
</ul>
<h3 id="코틀린의-사용-지점-변성">코틀린의 사용 지점 변성</h3>
<ul>
<li><p>특정 타입 파라미터가 나타나는 지점에서 변성을 정할 수 있음</p>
</li>
<li><p>MutableList같은 상당수의 인터페이스 &gt; 타입 파라미터로 지정된 타입을 소비하는 동시에 생산할 수 있음 (공변적이지도 반공변적이지도 않음)</p>
<ul>
<li><p>이런 인터페이스 안에서 한 변수가 함수 안에서 생성자와 소비자 중 단 한 가지 역할을 담당하는 경우가 있음</p>
<pre><code class="language-kotlin">fun &lt;T&gt; copyData(source: MutableList&lt;T&gt;,
               destination: MutableList&lt;T&gt;) {
  for (item in source) {
      destination.add(item)
  }
}</code></pre>
</li>
<li><p>두 컬렉션 모두 무공변 타입이지만 하나는 읽기만 하고 하나는 쓰기만 함</p>
</li>
<li><p>이런 경우 두 컬렉션의 타입이 정확하게 일치하지 않아도 됨</p>
</li>
<li><p>문자열 컬렉션에서 객체의 컬렉션으로 복사해도 문제 없음</p>
</li>
<li><p>여러 리스트 타입에 대응하는 두 번째 제네릭 타입 파라미터를 도입할 수 있음</p>
<pre><code class="language-kotlin">fun &lt;T: R, R&gt; copyData(source: MutableList&lt;T&gt;,
               destination: MutableList&lt;R&gt;) {
               // 원본의 원소 타입 -&gt; 대상 원소 타입의 하위 타입
  for (item in source) {
      destination.add(item)
  }
}

fun main() {
  val ints = mutableListOf(1, 2, 3)
  val anyItems = mutableListOf&lt;Any&gt;()
  copyData(ints, anyItems) // Int가 Any의 하위 타입이기 때문에 호출 가능
  println(anyItems)
  // [1, 2, 3]
}</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="아웃-프로젝션-타입-파라미터를-사용">아웃-프로젝션 타입 파라미터를 사용</h3>
<pre><code class="language-kotlin">fun &lt;T&gt; copyData(source: MutableList&lt;out T&gt;,
                 destination: MutableList&lt;T&gt;) {
    for (item in source) {
        destination.add(item)
    }
}</code></pre>
<ul>
<li>타입 파라미터에 변성 변경자를 사용하여 간단하게 구현</li>
<li>변성 변경자를 사용하지 않은 경우 이런 경고가 출력됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/a7429b3e-4d13-44fa-b354-6f72b3fff107/image.png" alt=""></p>
<h3 id="타입-프로젝션">타입 프로젝션</h3>
<ul>
<li>타입 선언에 사용한 파라미터를 사용하는 위치라면 어디에나 변성 변경자를 붙일 수 있음<ul>
<li>파라미터 타입, 로컬 변수 타입, 함수 반환 타입 등등…</li>
</ul>
</li>
<li>타입 변성에 사용한 파라미터를 사용하는 곳에 변성 변경자를 붙이면 <strong>타입 프로젝션</strong>이 일어남<ul>
<li>일반적인 MutableList가 아닌 MutableList를 프로젝션한(제약을 가한) 타입을 만듬</li>
</ul>
</li>
</ul>
<p>사용 지점 변성을 사용하면 타입 인자로 사용할 수 있는 타입의 범위가 넓어짐</p>
<hr>
<h2 id="스타-프로젝션">스타 프로젝션</h2>
<ul>
<li>제네릭 타입 인자 정보가 없음을 표현<ul>
<li>원소 타입이 알려져 있지 않은 List → List&lt;*&gt;로 표현할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="mutablelist-≠-mutablelistany">MutableList&lt;*&gt; ≠ MutableList&lt;Any?&gt;</h3>
<ul>
<li>Any? → 모든 타입의 원소를 담을 수 있음</li>
<li><ul>
<li>→ 정해진 구체적인 타입의 원소를 담을 수 있지만 그 원소의 타입을 정확히 모름</li>
</ul>
</li>
<li>원소 타입을 모른다고 아무 타입이나 다 넣을 수 있는 건 아님</li>
</ul>
<h3 id="스타-프로젝션-→-아웃-프로젝션-타입으로-인식됨">스타 프로젝션 → 아웃 프로젝션 타입으로 인식됨</h3>
<ul>
<li>어떤 리스트의 원소 타입을 모르더라도 안전하게 Any? 타입으로 꺼내올 수 있음</li>
<li>하지만 타입을 모르는 리스트에 아무 원소나 마음대로 넣을 수는 없음</li>
</ul>
<h3 id="스타-프로젝션을-사용하는-경우">스타 프로젝션을 사용하는 경우</h3>
<ol>
<li>타입 파라미터를 시그니처에 언급하지 않는 경우</li>
<li>데이터를 읽기는 하지만 구체적인 타입은 신경 쓰지 않는 경우</li>
</ol>
<pre><code class="language-kotlin">fun printFirst(list: List&lt;*&gt;) { // 모든 리스트를 인자로 받을 수 있음
    if (list.isNotEmpty()) { // isNoEmpty -&gt; 제네릭 파라미터를 사용하지 않음
        println(list.first()) 
        // first -&gt; Any? 타입을 반환함
    }
}

fun main() {
    printFirst(listOf(&quot;Sveta&quot;, &quot;Seb&quot;, &quot;Dima&quot;, &quot;Roman&quot;))
    // Sveta
}</code></pre>
<ul>
<li>제네릭 파라미터가 어떤 타입인지 굳이 알 필요 없을 때만 스타 프로젝션을 사용<ul>
<li>스타 프로젝션을 사용할 때는 값을 만들어내는 메서드만 호출 가능</li>
<li>반환한 값의 타입은 신경 쓰지 않음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="타입-별명">타입 별명</h2>
<ul>
<li>기존 타입에 다른 이름을 부여</li>
</ul>
<pre><code class="language-kotlin">typealias name = typeName</code></pre>
<ul>
<li>긴 제네릭 타입을 짧게 만들수 있음</li>
</ul>
<pre><code class="language-kotlin">typealias Combiner = (String, String, String, String) -&gt; String
// 4개의 문자열을 받고 새로 조함한 문자열을 반환하는 함수형 타입 파라미터

val authorsCombiner: Combiner = { a, b, c, d -&gt; &quot;$a et al.&quot; }
// 변수 선언에 타입 별명 사용 가능

fun combineAuthors(combiner: Combiner) { // 함수 파라미터에 타입 별명 사용 가능
    println(combiner(&quot;Sveta&quot;, &quot;Seb&quot;, &quot;Dima&quot;, &quot;Roman&quot;))
}

fun main() {
    combineAuthors(authorsCombiner) // 타입 별명은 원래 타입으로 해소됨
    // Sveta et al.
    combineAuthors{ a, b, c, d -&gt; &quot;$d, $c &amp; Co&quot; } // 람다를 전달해도 됨
    // Roman, Dima &amp; Co
}</code></pre>
<ul>
<li>타입 별명을 사용하면 코드의 가독성을 올릴 수 있지만 다는 개발자가 볼 때 타입 별명의 정의를 찾아봐야 한다는 단점도 있음</li>
<li>타입 별명이 타입 안전성을 추가해주진 않음<ul>
<li>컴파일시 원래 타입으로 치환되기 때문</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 시점 제네릭스 동작 (소거, 실체화)]]></title>
            <link>https://velog.io/@first_woosun/%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90-%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%8A%A4-%EB%8F%99%EC%9E%91-%EC%86%8C%EA%B1%B0-%EC%8B%A4%EC%B2%B4%ED%99%94</link>
            <guid>https://velog.io/@first_woosun/%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90-%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%8A%A4-%EB%8F%99%EC%9E%91-%EC%86%8C%EA%B1%B0-%EC%8B%A4%EC%B2%B4%ED%99%94</guid>
            <pubDate>Mon, 23 Feb 2026 11:12:51 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<ul>
<li>JVM의 제네릭스 → 타입 소거를 사용해 구현<ul>
<li>실행 시점에 제네릭 클래스의 인스턴스에 타입 인자 정보가 들어있지 않다는 의미</li>
</ul>
</li>
<li>타입 소거는 어떤 영향을 미치는가?</li>
<li>함수를 inline으로 선언해 제약을 우회하는 방법</li>
<li>실체화된 타입 파라미터</li>
</ul>
<hr>
<h2 id="실행-시점에-제네릭-클래스의-타입-정보를-찾을-때-한계">실행 시점에 제네릭 클래스의 타입 정보를 찾을 때 한계:</h2>
<p>타입 검사와 캐스팅</p>
<ul>
<li>코틀린의 타입 인자 정보 → 런타임에 지워짐(자바와 동일)<ul>
<li>제네릭 클래스 인스턴스를 생성할 때 쓰인 타입 인자에 대한 정보를 유지하지 않음</li>
<li>List<String> 객체를 만들고나면 그 객체를 List로만 볼 수 있음</li>
<li>이 리스트가 어떤 타입의 원소를 저장하는지 실행 시점에는 알 수 없음</li>
</ul>
</li>
</ul>
<h3 id="타입-소거로-생기는-한계">타입 소거로 생기는 한계</h3>
<ol>
<li><p>타입 인자를 저장하지 않음 → 실행 시점에 타입 인자를 검사할 수 없음</p>
<ul>
<li><p>ex) 어떤 리스트가 문자열로 이뤄진 리스트인지 실행 시점 에는 검사할 수 없음</p>
</li>
<li><p>파라미터의 타입 인자에 따라 다른 동작을 수행할 수 없음</p>
<pre><code class="language-kotlin">fun readNumbersOrWords(): List&lt;Any&gt; {
      val input = readln()
      val words: List&lt;String&gt; = input.split(&quot;,&quot;)
      val numbers: List&lt;Int&gt; = words.mapNotNull{ it.toIntOrNull() }
      return numbers.ifEmpty { words }
}

fun printList(l: List&lt;Any&gt;) {
      when(l) {
          is List&lt;String&gt; -&gt; println(&quot;Strings: $l&quot;)
          is List&lt;Int&gt; -&gt; println(&quot;Integers: $l&quot;)
      }
      // when 식에서 Cannot check for an instance of erased type 오류 발생
}

fun main() {
      val list = readNumbersOrWords()
      printList(list)
}</code></pre>
</li>
<li><p>실행 시점에서 어떤 값이 List임은 확실히 알수 있지만 그 리스트가 어떤 타입을 저장하는지 알 수 없음</p>
</li>
<li><p>타입 인자를 저장하지 않기 때문에 애플리케이션의 전체 메모리 사용량이 줄어든다는 장점은 있음</p>
</li>
<li><p>스타 프로젝션( * )을 사용하면 최소한 대상이 List 임은 확인할 수 있음 (추후 자세히 다룸)</p>
<ul>
<li>여전히 원소 타입은 알 수 없음</li>
</ul>
</li>
</ul>
</li>
<li><p>as나 as? 같은 캐스팅</p>
<ul>
<li><p>캐스팅에도 제네릭 타입을 사용할 수 있음</p>
</li>
<li><p>기저 클래스는 같지만 타입 인자가 다른 타입으로 캐스팅을 해도 성공함</p>
<ul>
<li>캐스팅은 항상 성공</li>
</ul>
</li>
<li><p>이런 캐스팅은 컴파일러가 unchecked cast라는 경고를 해줌</p>
<ul>
<li>단순 경고일 뿐 컴파일은 진행됨</li>
</ul>
<pre><code class="language-kotlin">fun printSum(c: Collection&lt;*&gt;) {
      val intList = c as? List&lt;Int&gt;
              ?: throw IllegalArgumentException(&quot;List is expected&quot;)
      println(intList.sum())
}

fun main() {
      printSum(listOf(1, 2, 3))
      // 6
      printSum(setOf(1, 2, 3))
      // IllegalArgumentException: List is expected
      printSum(listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;))
      // ClassCastException: String connot cast to Number
}</code></pre>
</li>
<li><p>잘못된 타입의 원소가 들어있는 경우 ClassCastException이 발생</p>
<ul>
<li>as ? 캐스트는 성공하지만 sum 함수에서 예외가 발생</li>
<li>sum은 Number 타입의 값에 대해서 작동</li>
<li>String을 Number로 사용하려 했기 때문에 ClassCastException이 발생</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="실행-시점에-타입-정보가-주어진-경우">실행 시점에 타입 정보가 주어진 경우</h3>
<ul>
<li>이 경우 is 검사를 수행할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun printSum(c: Collection&lt;Int&gt;) { // 컴파일 시점에 원소 타입이 알려짐
    when(c) {
        is List&lt;Int&gt; -&gt; println(&quot;List sum: ${c.sum()}&quot;)
        is Set&lt;Int&gt; -&gt; println(&quot;Set sum: ${c.sum()}&quot;)
    }
}

fun main() {
    printSum(listOf(1, 2, 3))
    // List sum: 6
    printSum(setOf(3, 4, 5))
    // Set sum: 12
}</code></pre>
<hr>
<h2 id="실체화된-타입-파라미터">실체화된 타입 파라미터</h2>
<ul>
<li><p>인라인 함수의 타입 파라미터 → 실체화됨</p>
<ul>
<li>실행 시점에 인라인 함수의 실제 타입 인자를 알 수 있음</li>
</ul>
</li>
<li><p>reified 키워드를 통해 타입 파라미터를 실체화 시킬 수 있음</p>
<ul>
<li><p>일반 함수에 reified 키워드를 사용하면 경고 메시지가 나옴</p>
<p>  <img src="attachment:ef02daed-aa83-4435-869f-5d10a6e9e7e3:image.png" alt="image.png"></p>
</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">inline fun &lt;reified T&gt; isA(value: Any) = value is T

fun main() {
    println(isA&lt;String&gt;(&quot;abc&quot;))
    // true
    println(isA&lt;String&gt;(123))
    // false
}</code></pre>
<h3 id="filterisinstance-예제">filterIsInstance 예제</h3>
<ul>
<li>표준 라이브러리 함수로, 인자로 받은 컬렉션에서 지정한 타입의 원소만 모아서 리스트로 반환</li>
</ul>
<pre><code class="language-kotlin">fun main() {
    val items = listOf(&quot;one&quot;, 2, &quot;three&quot;)
    println(items.filterIsInstance&lt;String&gt;())
    // [one, three]
}</code></pre>
<ul>
<li>filterIsInstance 함수의 내부 (간소화 예시)</li>
</ul>
<pre><code class="language-kotlin">inline fun &lt;reified T&gt; Iterable&lt;*&gt;.filterIsInstance(): List&lt;T&gt; {
    val destination = mutableListOf&lt;T&gt;()
    for (element in this) {
        if(element is T) { // 각 원소가 인자로 지정한 클래스의 인스턴스인지 검사
            destination.add(element)
        }
    }
    return destination
}</code></pre>
<h3 id="inline-함수에서만-reified-키워드를-쓸-수-있는-이유">inline 함수에서만 reified 키워드를 쓸 수 있는 이유</h3>
<ul>
<li>inline 함수 → 함수를 호출한 모든 위치에 바이트 코드를 삽입<ul>
<li>컴파일러는 타입 인자로 쓰인 구체적인 클래스를 참조하는 바이트코드를 생성해 삽입할 수 있음</li>
<li>만들어진 바이트 코드는 구체적인 타입을 사용하므로 타입 소거의 영향을 받지 않음</li>
</ul>
</li>
</ul>
<h3 id="자바-코드에서-reified-키워드를-사용한-inline-함수를-호출할-수-없음">자바 코드에서 reified 키워드를 사용한 inline 함수를 호출할 수 없음</h3>
<ul>
<li>자바에서는 코틀린 inline 함수를 일반 함수처럼 호출 → 실제로 인라이닝 되지 않음</li>
<li>바이트코드를 직접 삽입하지 않기 때문에 구체적인 클래스를 참조하지 못함</li>
</ul>
<hr>
<h2 id="클래스-참조를-실체화된-타입-파라미터로-대신해-javalangclass-파라미터-피하기">클래스 참조를 실체화된 타입 파라미터로 대신해 java.lang.Class 파라미터 피하기</h2>
<ul>
<li>java.lang.Class 타입 인자를 파라미터로 받는 API에 대한 코틀린 어댑터 구축 → 실체화된 파라미터를 자주 사용<ul>
<li>JDK의 ServiceLoader가 있음<ul>
<li>어떤 추상 클래스나 인스턴스를 구현한 인스턴스를 반환</li>
</ul>
</li>
<li>실체화된 타입 파라미터를 통해 이런 API를 쉽게 호출 할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="serviceloader-사용-방법">ServiceLoader 사용 방법</h3>
<pre><code class="language-kotlin">val serviceImpl = ServiceLoader.load(Service::class.java)</code></pre>
<ul>
<li>::class.java 구문 → 코틀린 클래스에 대응하는 java.lang.Class 참조를 얻는 방법</li>
<li>Service::class.java → Service.class라는 자바 코드와 완전히 같음</li>
</ul>
<h3 id="serviceloader에서-타입-파라미터-사용">ServiceLoader에서 타입 파라미터 사용</h3>
<pre><code class="language-kotlin">val serviceImpl = loadService&lt;Service&gt;()</code></pre>
<ul>
<li><p>읽어 들일 서비스 클래스를 loadService 함수의 타입 인자로 지정</p>
</li>
<li><p>loadService 함수</p>
<pre><code class="language-kotlin">  inline fun &lt;reified T&gt; loadService(): ServiceLoader&lt;T?&gt;? {
      return ServiceLoader.load(T::class.java)
  }</code></pre>
</li>
</ul>
<h3 id="안드로이드의-startactivity-함수-간단하게-만들기">안드로이드의 startActivity 함수 간단하게 만들기</h3>
<pre><code class="language-kotlin">inline fun &lt;reified T : Activity&gt; Context.startActivity() {
    val intent = Intent(this, T::class.java)
    startActivity(intent)
}

startActivity&lt;DetailActivity&gt;() // 액티비티를 표시하는 메서드를 호출</code></pre>
<hr>
<h2 id="실체화된-타입-파라미터가-있는-접근자-정의">실체화된 타입 파라미터가 있는 접근자 정의</h2>
<ul>
<li>제네릭 타입에 대해 프로퍼티 접근자를 정의할 수 있음<ul>
<li>프로퍼티를 inline으로 표시</li>
<li>타입 파라미터를 reified로 표시</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">inline val &lt;reified T&gt; T.cannonical: String
    get() = T::class.java.canonicalName

fun main() {
    println(listOf(1, 2, 3).cannonical)
    // java.util.List
    println(1.cannonical)
    // java.lang.Integer
}</code></pre>
<hr>
<h2 id="실체화된-파라미터의-제약">실체화된 파라미터의 제약</h2>
<ul>
<li>실체화의 개념으로 인해 생기는 제약과 현제 코틀린이 실체화를 구현하는 방식에 의해 생기는 제약이 있음</li>
</ul>
<ol>
<li>실체화된 타입 파라미터를 사용할 수 있는 경우<ul>
<li>타입 검사와 캐스팅 (is, !is, as, as?)</li>
<li>코틀린 리플렉션 API (::class)</li>
<li>코틀린 타입에 대응하는 java.lang.Class 얻기 (::class.java)</li>
<li>다른 함수를 호출할 때 타입 인자로 사용</li>
</ul>
</li>
<li>실체화된 타입 파라미터의 제약<ul>
<li>타입 파라미터 클래스의 인스턴스 생성</li>
<li>타입 파라미터의 동반 객체 메서드 호출</li>
<li>실체화된 타입 파라미터를 요구하는 함수에 실체화되지 않은 타입 인자를 넘기기</li>
<li>클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정</li>
</ul>
</li>
</ol>
<h3 id="클래스-프로퍼티-인라인-함수가-아닌-함수의-타입-파라미터를-reified로-지정">클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정</h3>
<ul>
<li>실체화된 타입 파라미터를 사용하는 함수 → 자신에게 전달되는 모든 람다를 인라이닝 함<ul>
<li>람다 내부에서 타입 파라미터를 사용하는 방식에 따라 람다를 인라이닝 할 수 없는 경우가 생기기도 함</li>
<li>성능 문제로 람다를 인라이닝 하고 싶지 않을 수도 있음</li>
<li>noinline 변경자를 통해 인라이닝을 금지할 수 있음</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 타입 파라미터: 타입 인자를 받는 타입]]></title>
            <link>https://velog.io/@first_woosun/%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%ED%83%80%EC%9E%85-%EC%9D%B8%EC%9E%90%EB%A5%BC-%EB%B0%9B%EB%8A%94-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@first_woosun/%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%ED%83%80%EC%9E%85-%EC%9D%B8%EC%9E%90%EB%A5%BC-%EB%B0%9B%EB%8A%94-%ED%83%80%EC%9E%85</guid>
            <pubDate>Mon, 23 Feb 2026 11:12:04 GMT</pubDate>
            <description><![CDATA[<ul>
<li>제네릭스를 사용해 타입 파라미터를 받는 타입을 정의할 수 있음</li>
<li>제네릭 클래스에 구체적인 타입을 넘기면 타입을 인스턴스화 할 수 있음</li>
</ul>
<pre><code class="language-kotlin">val authors = listOf(&quot;Dmitry&quot;, &quot;Svetlana&quot;)</code></pre>
<ul>
<li><p>컴파일러는 자동으로 author의 타입이 List<String>임을 추론함</p>
</li>
<li><p>빈 리스트를 정의할 때는 타입 인자를 명시해줘야 함</p>
</li>
</ul>
<pre><code class="language-kotlin">val readers: MutableList&lt;String&gt; = mutableListOf() // 변수 타입 지정
val readers = mutableListOf&lt;String&gt;() // 함수 타입 지정</code></pre>
<hr>
<h2 id="제네릭-타입과-함께-동작하는-함수와-프로퍼티">제네릭 타입과 함께 동작하는 함수와 프로퍼티</h2>
<h3 id="제네릭-함수-→-여러-타입에-대응-가능">제네릭 함수 → 여러 타입에 대응 가능</h3>
<ul>
<li>타입 파라미터를 받음</li>
<li>호출 시 구체적인 타입을 인자로 넘겨야 함</li>
</ul>
<h3 id="컬렉션-함수-→-대부분-제네릭-함수">컬렉션 함수 → 대부분 제네릭 함수</h3>
<pre><code class="language-kotlin">// slice함수 예시
fun &lt;T&gt; List&lt;T&gt;.slice(indices: IntRange) : List&lt;T&gt;</code></pre>
<ul>
<li><T>의 의미 (순서대로)<ol>
<li>타입 파라미터 선언</li>
<li>수신객체의 타입 파라미터</li>
<li>반환 객체의 타입 파라미터</li>
</ol>
</li>
</ul>
<h3 id="컬렉션-호출-시-타입-인자를-명시적으로-지정할-수-있으나-대부분-컴파일러가-추론할-수-있음">컬렉션 호출 시 타입 인자를 명시적으로 지정할 수 있으나 대부분 컴파일러가 추론할 수 있음</h3>
<pre><code class="language-kotlin">fun main() {
        val letters = (&#39;a&#39;..&#39;z&#39;).toList()
        println(letters.slice&lt;Char&gt;(0..2))
        // [a, b, c]
        println(letters.slice(10..13))
        // [k, l, m, n]
}</code></pre>
<h3 id="filter-함수">filter 함수</h3>
<pre><code class="language-kotlin">// 함수 시그니처
fun &lt;T&gt; List&lt;T&gt;.filter(predicate: (T) -&gt; Boolean): List&lt;T&gt;</code></pre>
<pre><code class="language-kotlin">fun main() {
        val authors = listOf(&quot;Sveta&quot;, &quot;Seb&quot;, &quot;Roman&quot;, &quot;Dima&quot;)
        val readers = mutableListOf(&quot;Seb&quot;, &quot;Hadi&quot;)
        println(readers.filter { it !in authors })
        // [Hadi]
}</code></pre>
<ul>
<li>람다 파라미터의 it 변수의 타입은 컴파일러에 의해 추론됨<ul>
<li>filter의 수신 객체인 readers의 타입인 List<String>로 부터 it의 타입이 String임을 추론</li>
</ul>
</li>
</ul>
<h3 id="타입-파라미터를-사용할-수-있는-위치">타입 파라미터를 사용할 수 있는 위치</h3>
<ul>
<li>클래스나 인터페이스 안에 정의된 메서드</li>
<li>최상위 함수</li>
<li>확장 함수<ul>
<li>수신 객체, 파라미터 타입</li>
</ul>
</li>
</ul>
<h3 id="제네릭-확장-프로퍼티">제네릭 확장 프로퍼티</h3>
<ul>
<li>제네릭 함수를 구현하는 구문으로 제네릭 확장 프로퍼티를 선언할 수 있음</li>
</ul>
<pre><code class="language-kotlin">// 모든 List 타입에 대한 확장 파라미터
val &lt;T&gt; List&lt;T&gt;.penultimate: T
        get() = this[size - 2]

fun main() {
        println(listOf(1, 2, 3, 4).penultimate) // 파라미터 타입이 Int로 추론됨
        //3
}</code></pre>
<hr>
<h2 id="제네릭-클래스-선언">제네릭 클래스 선언</h2>
<ul>
<li>자바와 마찬가지로 홑화살괄호를 클래스나 인터페이스 이름 뒤에 작성<ul>
<li>타입 파라미터를 다른 일반 타입처럼 사용할 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">interface List&lt;T&gt; {
    operator fun get(index: Int): T
    /* ... */
} </code></pre>
<h3 id="제네릭-클래스의-확장-클래스">제네릭 클래스의 확장 클래스</h3>
<ul>
<li>기반 타입의 제네릭 파라미터에 대해 타입 인자를 지정<ul>
<li>구체적인 타입을 넘기거나</li>
<li>타입 파라미터로 받은 타입을 넘길 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">// 구체적인 타입 지정
class StringList : List&lt;T&gt; {
        override fun get(index: Int): String = this[index]
        /* ... */
}

// 제네릭 타입 파라미터를 List의 타입 인자로 넘김
class ArrayList&lt;T&gt;: List&lt;T&gt; {
        override fun get(index: Int): T = this[index]
        /* ... */
}</code></pre>
<ol>
<li>StringList<ul>
<li>String 타입의 원소만 저장 → String을 기반 타입으로 사용</li>
<li>하위 클래스에서 함수를 오버라이드 하거나 사용하려면 타입 인자에 String을 명시해야 함<ul>
<li>ex) get 함수의 반환 타입으로 String을 명시함</li>
</ul>
</li>
</ul>
</li>
<li>ArrayList<ul>
<li>자신만의 타입 파라미터를 정의하면서 기반 클래스의 타입 인자로 사용<ul>
<li>ArrayList의 T와 List의 T는 같지 않음</li>
</ul>
</li>
</ul>
</li>
</ol>
<ul>
<li>클래스가 자신을 타입 인자로 참조할 수 있음</li>
</ul>
<pre><code class="language-kotlin">// Comparable 인터페이스 구현 예제
interface Comparable&lt;T&gt; {
        fun compareTo(other: T): Int
}

class String : Comparable&lt;String&gt; {
        override fun compareTo(other: String): Int = TODO()
}</code></pre>
<hr>
<h2 id="타입-파라미터-제약">타입 파라미터 제약</h2>
<ul>
<li>클래스나 함수에 사용할 수 있는 타입을 제한하는 기능<ul>
<li>ex) 특정 함수에 대해 타입 파라미터로 숫자 타입 만을 허용하도록 정의할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="어떤-타입을-제네릭-타입의-상계upper-bound로-정의">어떤 타입을 제네릭 타입의 상계(upper bound)로 정의</h3>
<ul>
<li>제네릭 타입을 인스턴스화할 때 사용하는 타입 인자는 반드시 그 상계 타입이거나 그 상계 타입의 하위 타입이어야 함</li>
</ul>
<pre><code class="language-kotlin">// 상계 지정 방법
fun &lt;T : Number&gt; List&lt;T&gt;.sum(): T</code></pre>
<ul>
<li>타입 파라미터 이름 뒤에 콜론( : )을 표시하고 그 뒤에 상계 타입 명시</li>
</ul>
<pre><code class="language-kotlin">fun main() {
        println(ListOf(1, 2, 3, 4).sum())
        // 10
}</code></pre>
<ul>
<li>Number → 코틀린 표 준 라이브러리의 숫자 타입의 최상위 클래스</li>
<li>상계를 정하고 나면 T 타입의 값을 상계 타입으로 취급할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun &lt;T:Number&gt; oneHalf(value: T): Double {
        return value.toDouble() / 2.0
}

fun main() {
        println(oneHalf(3))
        // 1.5
}</code></pre>
<h3 id="두-값-비교-예제">두 값 비교 예제</h3>
<pre><code class="language-kotlin">fun &lt;T: Comparable&lt;T&gt;&gt; max(first: T, second: T): T {
        return if (first &gt; second) first else second
}

fun main() {
        println(max(&quot;kotlin&quot;, &quot;java&quot;))
        //kotlin
}</code></pre>
<ul>
<li>비교할 수 없는 값을 입력하면 커파일 오류 발생</li>
</ul>
<pre><code class="language-kotlin">println(max(&quot;kotlin&quot;, 42))
// ERROR: Type parameter bound for T is not satisfied:
//         inferred type Any is not a subtype of Comparable&lt;Any&gt;</code></pre>
<h3 id="타입-파라미터에-둘-이상의-제약-선언">타입 파라미터에 둘 이상의 제약 선언</h3>
<pre><code class="language-kotlin">fun &lt;T&gt; ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { // 타입 파라미터 제약 목록
        if(!seq.endWith(&#39;.&#39;)) { // CharSequence 인터페이스의 확장 함수 호출
                seq.append(.) // Appendable 인터페이스에 정의된 메서드 호출
        }
}

fun main() {
        val helloWorld = StringBuilder(&quot;Hello World&quot;)
        ensureTrailingPeriod(helloWorld)
        println(helloWorld)
        //Hello World.
}</code></pre>
<ul>
<li>StringBuilder → CharSequence와 Appendable을 모두 사용하는 클래스</li>
</ul>
<hr>
<h2 id="널이-될-수-없는-타입-제한하기">널이 될 수 없는 타입 제한하기</h2>
<ul>
<li>상계를 정하지 않은 타입 파라미터 → Any? 타입으로 치환</li>
<li>널 가능성을 제한하려면 명시적으로 타입 상계를 지정해줘야 함</li>
</ul>
<pre><code class="language-kotlin">class Processor&lt;T: Any&gt; {
        fun process(value: T) {
                value.hashCode()
        }
}</code></pre>
<ul>
<li>&lt;T: Any&gt; → T 타입이 항상 널이 될 수 없는 타입이 되도록 보장</li>
<li>Any 타입 외에도 다른 널이 될 수 없는 타입을 사용해 상계를 정할 수 있음</li>
</ul>
<h3 id="자바와의-상호-운용">자바와의 상호 운용</h3>
<ul>
<li>자바에서 어노테이션 @NotNull을 사용해 널값을 차단한 경우<ul>
<li>&lt;t : T &amp; Any&gt;와 같은 구문으로 절대로 널이 될 수 없는 값으로 표시할 수 있음</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[람다에서 반환: 고차 함수에서 흐름 제어]]></title>
            <link>https://velog.io/@first_woosun/%EB%9E%8C%EB%8B%A4%EC%97%90%EC%84%9C-%EB%B0%98%ED%99%98-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98%EC%97%90%EC%84%9C-%ED%9D%90%EB%A6%84-%EC%A0%9C%EC%96%B4</link>
            <guid>https://velog.io/@first_woosun/%EB%9E%8C%EB%8B%A4%EC%97%90%EC%84%9C-%EB%B0%98%ED%99%98-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98%EC%97%90%EC%84%9C-%ED%9D%90%EB%A6%84-%EC%A0%9C%EC%96%B4</guid>
            <pubDate>Fri, 20 Feb 2026 05:28:31 GMT</pubDate>
            <description><![CDATA[<h2 id="람다-안의-return문-람다를-둘러싼-함수에서-반환">람다 안의 return문: 람다를 둘러싼 함수에서 반환</h2>
<ul>
<li>람다 안에서 return → 그 람다를 호출한 함수의 실행이 종료되고 반환됨</li>
<li>return이 선언된 람다를 호출한 함수를 반환하게 만드는 return 문을 <strong>“비로컬 return”</strong>이라고 함</li>
</ul>
<pre><code class="language-java">data class Person(val name: String, val age: Int)

val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))

fun lookForAlice(people: List&lt;Person&gt;) {
        people.forEach {
                if(it.name == &quot;Alice&quot;){
                        println(&quot;Found!&quot;)
                        return // 비로컬 return
                }
        }
        println(&quot;Alice is not found&quot;)
}</code></pre>
<ul>
<li>return이 바깥 블록을 반환시키는 경우 → 인라인 함수에서만 가능</li>
</ul>
<hr>
<h2 id="람다로부터-반환-레이블을-사용한-return">람다로부터 반환: 레이블을 사용한 return</h2>
<ul>
<li><p>람다식에도 로컬 return문을 쓸 수 있음</p>
</li>
<li><p>람다의 로컬 return → break와 비슷한 역할</p>
<ul>
<li>람다의 실행을 멈추고 람다를 호출한 함수의 실행을 이어감</li>
</ul>
</li>
<li><p>로컬 return과 비로컬 return을 구분하는 방법 → 레이블 사용</p>
<pre><code class="language-java">  fun lookForAlice(people: List&lt;Person&gt;) {
          people.forEach label@{ // 람다 식 앞에 레이블 작성
                  if(it.name != &quot;Alice&quot;) return@label // 리턴 식 뒤에 레이블 작성
                  println(&quot;Found Alice!&quot;) // return이 실행되지 않았을 경우 출력
          }
  }</code></pre>
<ul>
<li><p>람다 식 앞에 레이블을 붙이고 return 키워드 뒤에 레이블을 추가</p>
<ul>
<li>람다 레이블 → 레이블 이름 뒤에 @</li>
<li>리턴 레이블 → 리턴 키워드 뒤에 @와 레이블 이름</li>
</ul>
</li>
<li><p>람다를 인자로 밭은 함수의 이름을 사용해도 됨</p>
<pre><code class="language-java">  people.forEach {
          if(it.name != &quot;Alice&quot;) return@forEach
          println(&quot;Found Alice!&quot;)
  } </code></pre>
</li>
</ul>
</li>
<li><p>람다식에는 레이블 이름이 1개만 붙을 수 있음</p>
</li>
</ul>
<h3 id="this-식의-레이블">this 식의 레이블</h3>
<ul>
<li>수신 객체 지정 람다 → this로 수신 객체 지정 (13장 내용)</li>
<li>수신 객체 지정 람다 앞에 레이블을 붙인 경우 this 뒤에 그 레이블을 붙여 수신객체를 지정할 수 있음</li>
</ul>
<pre><code class="language-java">fun main() {
        println(StringBuilder().apply sb@{ // this@sb를 통해 이 람다의 수신 객체에 접근 가능
                listOf(1, 2, 3).apply {
                        this@sb.append(this.toString())
                }
        })
        // [1, 2, 3]
}</code></pre>
<hr>
<h2 id="익명-함수--기본적으로-로컬-return">익명 함수 : 기본적으로 로컬 return</h2>
<ul>
<li>익명 함수 → 람다를 정의하는 또 다른 방법<ul>
<li>함수에 전달할 수 있는 코드 블록 작성 가능</li>
</ul>
</li>
<li>익명 함수에서 return을 쓰면 기본적으로 로컬 return임<ul>
<li>레이블을 지정할 필요 없음</li>
</ul>
</li>
</ul>
<pre><code class="language-java">fun findForAlice(people: List&lt;Person&gt;) {
        people.forEach(fun (person) {
                if(person.name == &quot;Alice&quot;) return
                println(&quot;${person.name} is not Alice&quot;)
        })
}</code></pre>
<ul>
<li>함수 이름 생략</li>
<li>파라미터 타입을 컴파일러가 추론해줌</li>
</ul>
<pre><code class="language-java">people.filter (fun (person): Boolean {
        return person.age &lt; 30
})</code></pre>
<ul>
<li>익명 함수에도 반환 타입을 지정할 수 있음</li>
</ul>
<pre><code class="language-java">people.filter(fun (person) = person.age &lt; 30)</code></pre>
<ul>
<li>식을 본문으로 사용할 수 있음</li>
<li>식을 본문으로 사용할 경우 반환 타입은 생략할 수 있음</li>
</ul>
<h3 id="return은-가장-가까운-fun-키워드를-반환시킴">return은 가장 가까운 fun 키워드를 반환시킴</h3>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/ba0cd9db-eac0-40ad-a6fc-04f759207d50/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인라인 함수로 람다의 부가 비용 없애기]]></title>
            <link>https://velog.io/@first_woosun/%EC%9D%B8%EB%9D%BC%EC%9D%B8-%ED%95%A8%EC%88%98%EB%A1%9C-%EB%9E%8C%EB%8B%A4%EC%9D%98-%EB%B6%80%EA%B0%80-%EB%B9%84%EC%9A%A9-%EC%97%86%EC%95%A0%EA%B8%B0</link>
            <guid>https://velog.io/@first_woosun/%EC%9D%B8%EB%9D%BC%EC%9D%B8-%ED%95%A8%EC%88%98%EB%A1%9C-%EB%9E%8C%EB%8B%A4%EC%9D%98-%EB%B6%80%EA%B0%80-%EB%B9%84%EC%9A%A9-%EC%97%86%EC%95%A0%EA%B8%B0</guid>
            <pubDate>Fri, 20 Feb 2026 05:27:21 GMT</pubDate>
            <description><![CDATA[<h3 id="람다의-부가비용">람다의 부가비용?</h3>
<ul>
<li>람다 → 익명 클래스로 컴파일<ul>
<li>람다식마다 새로운 클래스가 생김</li>
<li>람다가 변수를 캡처한 경우 호출할 때마다 새로운 객체가 생김</li>
<li>같은 기능을 하는 코드를 직접 실행하는 것보다 효율성이 떨어짐</li>
</ul>
</li>
</ul>
<h3 id="인라인-함수">인라인 함수?</h3>
<ul>
<li>inline 변경자를 통해 선언</li>
<li>inline 변경자가 선언된 함수를 호출하면 컴파일러는 그 위치에 함수를 구현한 코드를 넣어줌</li>
</ul>
<hr>
<h2 id="인라이닝-작동-방식">인라이닝 작동 방식</h2>
<ul>
<li>어떤 함수를 inline으로 선언하면 그 함수의 본문이 인라인됨<ul>
<li>인라인으로 선언한 함수를 호출하는 코드가 함수 본문을 번역한 바이트코드로 컴파일 됨</li>
</ul>
</li>
<li>인라인 함수에 람다를 인자로 넘기면 람다의 본문도 함께 인라이닝 됨<ul>
<li>이 람다로 만들어진 바이트코드는 람다를 인자로 받은 함수의 일부분으로 간주되어 익명 클래스를 생성하지 않음</li>
</ul>
</li>
<li>인라인 함수에 람다 대신 함수 타입의 변수를 넘길 수도 있음<ul>
<li>변수에 저장된 람다의 코드를 알 수 없어 람다 본문은 인라이닝 되지 않음</li>
<li>일반적인 람다 호출과 같이 컴파일 됨</li>
</ul>
</li>
</ul>
<hr>
<h2 id="인라인-함수의-제약">인라인 함수의 제약</h2>
<ul>
<li>람다를 사용하는 모든 함수를 인라이닝할 수는 없음<ul>
<li>함수 본문에서 파라미터로 받은 람다를 호출하면 인라이닝됨</li>
<li>람다를 변수에 저장하고 그 변수를 호출하면 람다를 표현하는 객체가 존재해야 하기 때문에 인라이닝 할 수 없음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">class FunctionStorage {
        var myStorageFunction((Int) -&gt; Unit)? = null
        inline fun storerFunction(f: (Int) -&gt; Unit) {
                myStorageFunction = f // 전달된 파라미터를 저장
        }
}</code></pre>
<ul>
<li>컴파일러는 Illegal usage of inline-parameter라는 메시지와 함께 인라이닝을 금지시킴</li>
</ul>
<h3 id="여러-람다-중-일부만-인라이닝-하기">여러 람다 중 일부만 인라이닝 하기</h3>
<ul>
<li>noinline 변경자를 파라미터 이름 앞에 붙여 인라이닝을 제한할 수 있음</li>
</ul>
<pre><code class="language-kotlin">inline fun foo(inline: () -&gt; Unit, noinline NotInLined: () -&gt; Unit) {
        /* ... */
}</code></pre>
<hr>
<h2 id="컬렉션-연산-인라이닝">컬렉션 연산 인라이닝</h2>
<ul>
<li>코틀린 표준 라이브러리의 컬렉션 함수 → 대부분 람다를 인자로 받음<ul>
<li>이 람다가 성능에 악형향을 끼치는가?</li>
</ul>
</li>
</ul>
<ol>
<li><p>람다 사용</p>
<pre><code class="language-kotlin"> data class Person(val name: String, val age: Int)

 val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))

 fun main() {
         println(people.filter { it.age &lt; 30 })
         // [Person(name=Alice, age=29)]
 }</code></pre>
</li>
<li><p>직접 기능 구현</p>
<pre><code class="language-kotlin"> data class Person(val name: String, val age: Int)

 val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 31))

 fun main() {
         val result = mutableListOf&lt;Person&gt;()
         for (person in people) {
                 if(person.age &gt; 30) result.add(person)
         }
         println(result)
         // [Person(name=Alice, age=29)]
 }</code></pre>
<ul>
<li>1번 코드와 2번 코드의 바이트 코드는 거의 동일함<ul>
<li>filter 함수 → 인라인 함수</li>
<li>filter 함수의 바이트코드는 전달된 람다 본문의 바이트코드와 함께 filter를 호출한 위치에 인라이닝 됨</li>
</ul>
</li>
<li>코틀힌 표준 라이브러리에서 제공하는 인라인 함수는 성능 걱정없이 사용해도 무관함</li>
</ul>
</li>
</ol>
<h3 id="중간-리스트의-부가-비용">중간 리스트의 부가 비용</h3>
<pre><code class="language-kotlin">fun main() {
        println(
                people.filter { it.age &gt; 30 }
                .map(Person::name)
        )
        // [Bob]
}</code></pre>
<ul>
<li>filter와 map의 본문은 인라이닝됨</li>
<li>하지만 filter에서 걸러낸 결과를 저장하는 중간 리스트를 만듬</li>
<li>map에서 이 중간 리스트를 읽어 최종 결과를 만들어냄</li>
</ul>
<p>처리할 원소가 적다면 중간 리스트의 부가 비용이 크지 않다면 원소가 많아질수록 중간 리스트의 부가 비용을 고려해야 함</p>
<h3 id="assequence를-통한-부가-비용-감소">asSequence를 통한 부가 비용 감소</h3>
<ul>
<li>asSequence를 통해 리스트 대신 시퀀스로 처리하면 중간 리스트로 인한 부가 비용을 줄일 수 있음<ul>
<li>각 중간 시퀀스는 람다를 필드에 저장하는 객체로 표현됨</li>
<li>최종 연산은 중간 시퀀스에 있는 여러 람다를 연쇄 호출함</li>
</ul>
</li>
<li>시퀀스 연산에서는 람다가 인라이닝 되지 않음<ul>
<li>크기가 작은 컬렉션은 일반 컬렉션 연산이 더 성능이 좋을 때도 있음</li>
<li>시퀀스를 통해 성능을 향상시킬 수 있는 경우는 컬렉션의 크기가 큰 경우임</li>
</ul>
</li>
</ul>
<hr>
<h2 id="인라인-함수는-언제-선언하는가">인라인 함수는 언제 선언하는가</h2>
<p>인라인 키워드를 사용한다고 무조건 성능 개선 효과를 얻는 것은 아님</p>
<p>람다를 인자로 받는 함수만 성능이 좋아질 가능성이 있음</p>
<ul>
<li>일반 함수 → 인라인 키워드를 사용해도 이익이 거의 없음<ul>
<li>일반 함수는 바이트코드에서 각 함수에 대한 구현이 딱 한번만 있으면 됨</li>
<li>함수를 호출하는 부분에서 따로 중복이 발생하지 않음</li>
</ul>
</li>
<li>코틀린 인라인 함수<ul>
<li>바이트코드에서 각 함수 호출 지점을 인라인 함수 본문으로 대치하여 코드 중복이 발생함</li>
</ul>
</li>
<li>람다를 인자로 받는 인라인 함수<ul>
<li>인라이닝을 통해 없앨 수 있는 부가 비용이 많음<ul>
<li>함수 호출 비용을 줄일 수 있음</li>
<li>람다를 표현하는 익명 클래스의 객체를 만들 필요 없음</li>
</ul>
</li>
<li>일반 람다에서는 사용할 수 없는 몇 가지 기능을 사용할 수 있음 (비로컬 return 등)</li>
</ul>
</li>
</ul>
<p>함수의 크기가 큰 경우 인라인이 비효율적일 수 있음</p>
<ul>
<li>함수의 바이트코드를 모든 호출 지점에 복사해 넣으면 바이트 코드 전체의 크기가 커질 수 있음</li>
</ul>
<hr>
<h2 id="자원-관리를-위해-인라인된-람다-사용">자원 관리를 위해 인라인된 람다 사용</h2>
<p>람다로 중복을 없앨 수 있는 일반적인 패턴 중 한가지 → 자원 관리</p>
<ul>
<li>보통 try/finally문으로 자원 획득과 자원 해제를 제어</li>
</ul>
<h3 id="withlock">withLock</h3>
<ul>
<li>Lock 인터페이스의 확장 함수</li>
</ul>
<pre><code class="language-kotlin">// 코틀린 라이브러리의 withLock 함수 정의
fun &lt;T&gt; Lock.withLock(action: () -&gt; T): T {
        lock()
        try {
                return action()
        } finally {
                unlock()
        }
}

// withLock 사용 방법
val l: Lock = ReentrantLock()
l.withLock{
        // 락에 의해 보호되는 자원 사
}</code></pre>
<h3 id="use-함수">use 함수</h3>
<ul>
<li>닫을 수 있는 자원(Closeable)에 대해 호출하는 확장 함수</li>
<li>람다를 호출하고 사용 후 자원을 확실하게 닫음</li>
<li>use 함수는 인라인 함수임</li>
</ul>
<pre><code class="language-kotlin">import java.io.BufferedReader
import java.io.FileReader

fun readFirstLineFromfile(fileName: String): String {
        BufferReader(FileReader(fileReader)).use { br -&gt; 
                return br.readLine()        
        }
}</code></pre>
<h3 id="uselines-함수">useLines 함수</h3>
<ul>
<li>File, Path 객체에 대해 정의돼 있음</li>
<li>람다가 문자열 시퀀스에 접근하게 해줌</li>
</ul>
<pre><code class="language-kotlin">import kotlin.io.path.Path
import kotlin.io.path.useLines

fun readFirstLineFromFile(fileName: String): String {
        Path(fileName).useLines {
                return it.first()
        }
}</code></pre>
<blockquote>
<h3 id="코틀린에서는-try-with-resources를-사용하지-말라">코틀린에서는 try-with-resources를 사용하지 말라</h3>
<ul>
<li>try-with-resources → 자바에서 Closeable 자원에 사용할 수 있는 구문</li>
</ul>
<pre><code class="language-java">static String readFirstLineFromFile(String fileName) throw IOException {
        try(BufferfReader br = new BufferdReader(new FileReader(fileName))) {
                return br.readLine();
        }
}</code></pre>
<ul>
<li>이 기능을 코틀린에서 use 같은 함수를 통해 간단하게 구현할 수 있음</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[다른 함수를 인자로 받거나 반환하는 함수 정의 : 고차 함수]]></title>
            <link>https://velog.io/@first_woosun/%EB%8B%A4%EB%A5%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%9D%B8%EC%9E%90%EB%A1%9C-%EB%B0%9B%EA%B1%B0%EB%82%98-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98-%EC%A0%95%EC%9D%98-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@first_woosun/%EB%8B%A4%EB%A5%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%9D%B8%EC%9E%90%EB%A1%9C-%EB%B0%9B%EA%B1%B0%EB%82%98-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98-%EC%A0%95%EC%9D%98-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98</guid>
            <pubDate>Mon, 16 Feb 2026 11:46:38 GMT</pubDate>
            <description><![CDATA[<p>고차 함수 → 다른 함수를 인자로 받거나 반환하는 함수</p>
<ul>
<li>람다 or 함수 참조를 통해 함수를 값으로 표현할 수 있음</li>
<li>이를 통해 함수를 인자로 넘기거나 반환 받는 것이 가능</li>
</ul>
<p>filter 함수도 고차 함수임</p>
<ul>
<li>람다를 인자로 받기 때문</li>
</ul>
<pre><code class="language-jsx">list.filter { it &gt; 0 }</code></pre>
<p>이제 고차 함수를 정의하는 방법을 알아본다. 고차 함수를 정의하려면 먼저 함수 타입을 알아야 함</p>
<hr>
<h2 id="함수-타입은-람다의-파라미터-타입과-반환-타입을-지정한다">함수 타입은 람다의 파라미터 타입과 반환 타입을 지정한다</h2>
<h3 id="람다를-로컬-변수에-대입">람다를 로컬 변수에 대입</h3>
<ul>
<li>코틀린의 타입 추로으로 변수 타입을 지정하지 않아도 람다를 변수에 대입할 수 있음</li>
</ul>
<pre><code class="language-kotlin">val sum = { x: int, y: Int -&gt; x + y } 
val action = { println(42) }</code></pre>
<ul>
<li>컴파일러 → sum과 action이 함수 타입임을 추론함</li>
</ul>
<h3 id="각-변수에-구체적인-타입-선언-추가">각 변수에 구체적인 타입 선언 추가</h3>
<pre><code class="language-kotlin">val sum: (Int, Int) -&gt; Int = {x, y -&gt; x + y}
val action: () -&gt; Unit = { println(42) }</code></pre>
<ul>
<li>함수 파라미터의 타입을 괄호 안에 명시</li>
<li>그 뒤에 화살표를 추가</li>
<li>화살표 다음에 함수의 반환 타입을 지정</li>
<li>Unit 타입 → 값을 반환하지 않는 함수 반환 타입에 쓰는 특별한 타입<ul>
<li>함수 타입을 선언할 때는 반환할 때는 반환 타입을 반드시 명시해야 하므로 Unit 타입을 명시해야 함</li>
</ul>
</li>
<li>함수 타입을 명시한다면 람다 내부에는 타입을 생략해도 됨</li>
</ul>
<h3 id="함수-타입도-반환-타입을-null이-될-수-있는-타입으로-지정할-수-있음">함수 타입도 반환 타입을 null이 될 수 있는 타입으로 지정할 수 있음</h3>
<pre><code class="language-kotlin">var canReturnNull: (Int: Int) -&gt; Int? = { x, y -&gt; null }</code></pre>
<h3 id="함수-타입-자체를-null이-될-수-있는-타입으로-정의할-수-있음">함수 타입 자체를 null이 될 수 있는 타입으로 정의할 수 있음</h3>
<ul>
<li>힘수 타입을 괄호로 감싸고 그 뒤에 물음표를 붙여야 함</li>
</ul>
<pre><code class="language-kotlin">var funOrNull: ((Int, Int) -&gt; Int)? = null</code></pre>
<hr>
<h2 id="인자로-전달-받은-함수-호출">인자로 전달 받은 함수 호출</h2>
<p>고차 함수를 어떻게 구현하는지 알아본다</p>
<h3 id="간단한-고차-함수-정의">간단한 고차 함수 정의</h3>
<pre><code class="language-kotlin">fun twoAndThree(operation: (Int, Int) -&gt; Int) { // 함수 타입인 파라미터를 선언
        val result = operation(2, 3) // 함수 타입인 파라미터를 호출
        println(&quot;The result is $result&quot;)
}

fun main() { 
        twoAndThree { a, b -&gt; a + b }
        // The result is 5
        twoAndThree { a, b -&gt; a * b }
        // The result is 6
}</code></pre>
<ul>
<li>인자로 받은 함수를 호출하는 방법은 일반 함수를 호출하는 구문과 같음</li>
</ul>
<blockquote>
<h3 id="파라미터-이름과-함수-타입">파라미터 이름과 함수 타입</h3>
</blockquote>
<ul>
<li>함수 타입에서 파라미터 이름을 지정할 수 있음<pre><code class="language-kotlin">fun twoAndThree(operation: (operandA: Int, operandB: Int) -&gt; Int) { // 함수 타입인 파라미터를 선언
      val result = operation(2, 3) // 함수 타입인 파라미터를 호출
      println(&quot;The result is $result&quot;)
}
fun main() { 
      twoAndThree { operandA, operandB -&gt; operandA + operandB } // API에서 지정한 이름을 람다에 사용할 수 있음
      // The result is 5
      twoAndThree { alpha, beta -&gt; alpha + beta } // 그냥 원하는 이름도 사용할 수 있음
      // The result is 5
}</code></pre>
<blockquote>
</blockquote>
</li>
</ul>
<h3 id="filter-함수-구현-예제">filter 함수 구현 예제</h3>
<p>예제를 단순하게 유지하기 위해 String에 대한 filter를 구현</p>
<pre><code class="language-kotlin">fun String.filter(predicate: (Char) -&gt; Boolean): String { 
        return buildString { 
                for (char in this@filter) { //입력 문자열 이터레이션
                        if (predicate(char)) append(char) // predicte 파라미터로 전달받은 함수를 호출
                }
        }
}

fun main() { 
        println(&quot;ab1c&quot;.filter { it in &#39;a&#39; .. &#39;z&#39; })
        ///abc
}</code></pre>
<ul>
<li>filter 함수 → 문자열의 각 문자가 술어를 만족하는지 검사</li>
<li>술어를 만족하는 문자는 StringBuilder의 append를 사용해 결과를 만들고 반환</li>
<li>레이블이 붙은 this → 10.6절에서 자세히 다룸</li>
</ul>
<hr>
<h2 id="자바에서-코틀린-함수-타입-사용">자바에서 코틀린 함수 타입 사용</h2>
<ul>
<li>SAM 변환을 통해 코틀린 람다를 자바 메서드에 옮길 수 있음</li>
<li>함수 타입을 사용하는 코틀린 코드를 자바에서 쉽게 호출할 수 있음’</li>
<li>자바 람다는 자동으로 코틀린 함수 타입으로 변환됨</li>
</ul>
<pre><code class="language-kotlin">/* 코틀린 선언*/
fun processTheAnswer(f: (Int) -&gt; Int) {
        println(42)
}

/* 자바 호출 */
processTheAnswer(number -&gt; number + 1);</code></pre>
<h3 id="자바에서-코틀린-함수-타입-호출시-수신-객체를-명시해줘야-함">자바에서 코틀린 함수 타입 호출시 수신 객체를 명시해줘야 함</h3>
<pre><code class="language-kotlin">import kotlin.collections.CollectionsKt;

/ *... */
public static void main(String[] arg) {
        List&lt;String&gt; strings = new ArrayList();
        strings.add(&quot;42&quot;);
        CollectionsKt.forEach(strings, s -&gt; { // 코틀린 표준 라이브러리 함수 호출 가능
                System.out.println(s);
                return Unit.INSTANCE; // Unit 타입의 값을 명시적으로 반환해야 함
        });
}</code></pre>
<ul>
<li>Unit을 반환하는 함수나 람다를 호출할 때는 Unit 타입의 값을 명시적으로 반환해줘야 함<ul>
<li>Unit 타입에는 값이 존재하기 때문</li>
</ul>
</li>
<li>값이 존재하기 때문에 Unit 타입을 반환하는 코틀린 함수에 void 타입 자바 람다를 넘길 수 없음</li>
</ul>
<h3 id="함수-타입의-자세한-동작-방식">함수 타입의 자세한 동작 방식</h3>
<ul>
<li>함수 타입의 변수 → FunctionN 인터페이스를 구현<ul>
<li>인자의 개수에 따라 FunctionN의 N값이 바뀜</li>
</ul>
</li>
<li>각 인터페이스에는 invoke라는 유일한 메서드가 있음<ul>
<li>이 메서드를 호출하면 함수가 호출됨</li>
<li>invoke에 대해선 13장에 자세히 다룸</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">interface Function1&lt;P1, out R&gt;{
        operator fun invoke(p1: P1): R
}</code></pre>
<ul>
<li>함수 타입의 변수 → 함수에 대응하는 FunctionN 인터페이스를 구현하는 클래스의 인스턴스<ul>
<li>invoke 메서드에는 람다 본문이 들어감</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun processTheAnswer(f: Function1&lt;Int, Int&gt;) {
        println(f.invoke(42))
}</code></pre>
<ul>
<li>FunctionN 인터페이스 → 컴파일러가 생성한 합성 타입으로 코틀린 표준 라이브러리에서 이들의 정의를 찾을 수 없음<ul>
<li>컴파일러가 필요할 때 이런 함수를 생성</li>
<li>파라미터의 개수 제한 없이 파라미터를 사용하는 함수에 대한 인터페이스를 사용할 수 있음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="함수-타입의-파라미터-기본값-지정과-null-이-될-수-있는-타입">함수 타입의 파라미터 기본값 지정과 null 이 될 수 있는 타입</h2>
<h3 id="함수-타입에-기본값-지정">함수 타입에 기본값 지정</h3>
<p>함수 타입의 파라미터에 대한 기본값으로 람다식을 지정해놓으면 호출할 때마다 람다를 넘겨주지 않아도 되고 필요 시 람다를 넘겨 원하는 기능을 추가할 수 있음</p>
<pre><code class="language-kotlin">fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
        separator: String = &quot;,&quot;,
        prefix: String,
        postfix: String,
        transform: (T) -&gt; String = { it.toString() } // 함수 타입 파라미터를 선언하면서 람다를 기본값으로 지정
): String {
        val result = StringBuilder(prefix)

        for((index, element) in this.withIndex()) {
                if(index &gt; 0) result.append(separator)
                result.append(transform(element)) // transform 파라미터에 대한 인자로 받은 함수 호출
        }

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

fun main() {
        val letters = listOf(&quot;Alpha&quot;, &quot;Beta&quot;)
        println(letters.joinToString()) //transform 기본 함수 사용
        // Alpha, Beta
        println(letters.joinToString { it.lowercase() }) // 람다를 인자로 전달
        // alpha, beta
        println(letters.joinToString(seperator=&quot;! &quot;, postfix=&quot;! &quot;, transform={ it.uppercase() }))
        // 이름 붙은 인자 구문을 사용해 람다를 포함하는 여러 인자를 전달
        // ALPHA! BETA!
}</code></pre>
<ul>
<li>함수 타입에 대한 기본값 선언도 = 뒤에 람다를 넣으면 됨</li>
</ul>
<h3 id="null이-될-수-있는-함수-타입">null이 될 수 있는 함수 타입</h3>
<ul>
<li>null이 될 수 있는 함수 타입으로 하수를 받으면 그 함수를 직접 호출할 수 없음<ul>
<li>null 여부를 명시적으로 검사하면 호출은 할 수 있음</li>
<li>invoke를 사용하여 간결하게 해결할 수도 있음</li>
</ul>
</li>
<li>함수 타입 → invoke 메서드를 구현하는 인터페이스<ul>
<li>일반 메서드처럼 invoke도 안전한 호출을 사용할 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun &lt;T&gt; Collection&lt;T&gt;.joinToString(
        separator: String= &quot;, &quot;,
        prefix: String=&quot;&quot;,
        postfix: String = &quot;&quot;,
        transform: ((T) -&gt; String)? = null
): String {
        val result = StringBuilder(prefix)

        for((index, element) in this.withIndex()) {
                if (index &gt; 0) result.append(separator)
                val str = transform?.invoke(element) ?: element.toString()
                result.append(str)
        }

        result.append(postfix)
        return result.toString()
}</code></pre>
<hr>
<h2 id="함수를-함수에서-반환">함수를 함수에서 반환</h2>
<p>프로그램 상태나 다른 조건에 따라 달라질 수 있는 로직이 있다면 함수를 반환하는 기능이 유용함</p>
<h3 id="함수를-반환하는-함수">함수를 반환하는 함수</h3>
<pre><code class="language-kotlin">enum class Delivery { STANDARD, EXPEDITED }

class Order(val itemCount: Int)

fun getShppingCostCalculator(delivery: Delivery): (Order) -&gt; Double { // 함수를 반환하는 함수 선언
        if(delivery == Delivery.EXPEDITED) {
                return { order -&gt; 6 + 2.1 * order.itemCount } // 함수에서 람다를 반환
        }
        return { order -&gt; 1.2 * order.itemCount } // 함수에서 람다를 반환
}

fun main() {
        val calculator = getShippingCostCalculator(Delivery.EXPEDITED) // 함수를 반환받아 저장
        println(&quot;Shipping costs ${calculator(Order(3))}&quot;) // 반환받은 함수 호출
        // Shipping costs 12.3
}</code></pre>
<ul>
<li>함수에서 함수를 반한하려면 반환 타입으로 함수 타입을 지정해줘야 함<ul>
<li>getShppingCostCalculator는 Order 객체를 받아 Double 타입을 반환하는 함수를 반환함</li>
</ul>
</li>
<li>함수를 반환하려면 return식에 람다, 맴버 참조, 함수 타입의 값을 계산하는 식을 넣으면 됨</li>
</ul>
<h3 id="함수를-반환하는-함수를-ui-코드에서-사용하기">함수를 반환하는 함수를 UI 코드에서 사용하기</h3>
<pre><code class="language-kotlin">data class Person(
    val firstName: String,
    val lastName: String,
    val phoneNumber: String?
)

class ContactListFilters {
    var prefix:String = &quot;&quot;
    var onlyWithPhoneNumber: Boolean = false

    fun getPrediction(): (Person) -&gt; Boolean { // 함수를 반환하는 함수 정의
        val startWithPrefix = { p: Person -&gt;
            p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
        }
        if(!onlyWithPhoneNumber) {
            return startWithPrefix // 함수 타입의 변수 반환
        }
        return { startWithPrefix(it) &amp;&amp; it.phoneNumber != null } // 람다를 반환
    }
}

fun main() {
    val contact = listOf(
        Person(&quot;Dmitry&quot;, &quot;Jemerov&quot;, &quot;123-4567&quot;),
        Person(&quot;Svetlana&quot;, &quot;Isakova&quot;, null)
    )
    val contactListFilters = ContactListFilters()
    with (contactListFilters) {
        prefix = &quot;Dm&quot;
        onlyWithPhoneNumber = true
    }

    println(
        contact.filter(contactListFilters.getPrediction()) // 반환한 함수를 filter에 인자로 넘김
    )
    // [Person(firstName=Dmitry, lastName=Jemerov, phoneNumber=123-4567)]
}</code></pre>
<ul>
<li>getPredicate 메서드 → filter 함수에 인자로 넘길 수 있는 함수를 반환함</li>
</ul>
<hr>
<h2 id="람다를-통한-코드-재사용성-높이기">람다를 통한 코드 재사용성 높이기</h2>
<p>람다식 → 재사용성을 높이는 훌륭한 도구</p>
<ul>
<li>반복적으로 사용되는 코드를 람다로 추출해 함수에 인자로 넘기면 코드를 재사용 하면서 코드 중복을 제거할 수 있음</li>
</ul>
<h3 id="웹-사이트-방문-기록-분석-예시">웹 사이트 방문 기록 분석 예시</h3>
<p>데이터 정의</p>
<pre><code class="language-kotlin">data class SiteVisit(
    val path: String,
    val duration: Double,
    val os: OS
)

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

val log = listOf(
    SiteVisit(&quot;/&quot;, 34.0, OS.WINDOW),
    SiteVisit(&quot;/&quot;, 22.0, OS.MAC),
    SiteVisit(&quot;/login&quot;, 12.0, OS.WINDOW),
    SiteVisit(&quot;/signup&quot;, 8.0, OS.IOS),
    SiteVisit(&quot;/&quot;, 16.3, OS.ANDROID)
)</code></pre>
<ol>
<li><p>사이트 방문을 하드 코딩한 필터</p>
<pre><code class="language-kotlin"> val averageWindowsDuration = log
     .filter { it.os == OS.WINDOW }
     .map(SiteVisit::duration)
     .average()

 fun main() {
     println(averageWindowsDuration)
     //23.0
 }</code></pre>
</li>
<li><p>일반 함수를 통해 코드 중복 제거</p>
<pre><code class="language-kotlin"> fun List&lt;SiteVisit&gt;.averageDurationFor(os: OS) =
     filter { it.os == os }.map(SiteVisit::duration).average()

 fun main() {
     println(log.averageDurationFor(OS.WINDOW))
     // 23.0
     println(log.averageDurationFor(OS.MAC))
     // 22.0
 }</code></pre>
<ul>
<li>OS를 파라미터로 뽑아내 코드 중복을 해결</li>
</ul>
</li>
<li><p>하드 코딩된 필터를 로컬 함수로 정의하기</p>
</li>
</ol>
<pre><code class="language-kotlin">fun main() {
    val averageMobileDuration = log
        .filter { it.os in setOf(OS.IOS, OS.ANDROID) }
        .map(SiteVisit::duration)
        .average()
    println(averageMobileDuration)
    //12.15
}</code></pre>
<ol start="4">
<li>고차 함수를 사용해 중복 제거하기</li>
</ol>
<pre><code class="language-kotlin">fun List&lt;SiteVisit&gt;.averageDurationFor(predicate: (SiteVisit) -&gt; Boolean) =
    filter(predicate).map(SiteVisit::duration).average()

fun main() {
    println(
        log.averageDurationFor { it.os in setOf(OS.ANDROID, OS.IOS) }
    )
    // 12.15
    println(
        log.averageDurationFor { it.os == OS.IOS &amp;&amp; it.path == &quot;/signup&quot; }
    )
    // 8.0
}</code></pre>
<ul>
<li>중복되는 코드를 고차 함수로 뽑아냄으로써 코드 중복을 제거할 수 있음</li>
<li>뿐 만 아니라 함수 타입을 사용하면 필요한 조건을 파라미터로 뽑아낼 수 있음<ul>
<li>복잡한 로직을 간단하게 구현 가능</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로퍼티 접근자 로직 재활용: 위임 프로퍼티]]></title>
            <link>https://velog.io/@first_woosun/%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%A0%91%EA%B7%BC%EC%9E%90-%EB%A1%9C%EC%A7%81-%EC%9E%AC%ED%99%9C%EC%9A%A9-%EC%9C%84%EC%9E%84-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0</link>
            <guid>https://velog.io/@first_woosun/%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%A0%91%EA%B7%BC%EC%9E%90-%EB%A1%9C%EC%A7%81-%EC%9E%AC%ED%99%9C%EC%9A%A9-%EC%9C%84%EC%9E%84-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0</guid>
            <pubDate>Mon, 16 Feb 2026 11:44:18 GMT</pubDate>
            <description><![CDATA[<ul>
<li>프로퍼티 접근자 로직을 매번 재구현할 필요 없이 쉽게 구현할 수 있음<ul>
<li>자신의 값을 필드가 아닌 DB 테이블, 브라우저 세션, 맵 등에 저장할 수 있음</li>
</ul>
</li>
<li>위임 → 작업을 도우미 객체가 처리하도록 맡기는 디자인 패턴<ul>
<li>도우미 객체 = 위임 객체</li>
</ul>
</li>
<li>도우미 객체를 직접 작성할 수도 있지만 코틀린 언어가 제공하는 기능을 활용할 수도 있음</li>
</ul>
<hr>
<h2 id="위임-프로퍼티의-기본-문법과-내부-동작">위임 프로퍼티의 기본 문법과 내부 동작</h2>
<pre><code class="language-kotlin">import java.lang.reflect.Type

class Delegate(){
    operator fun getValue(/*...*/) { /*...*/ } // getter를 구현하는 로직

    operator fun setValue(/*...*/, value: Type) { /*...*/ } // setter를 구현하는 로직

    operator fun provideDelegate(/*...*/): Delegate { /*...*/ } // 위임 객체 생성, 제공
}

class Foo {
    var p : Type by Delegate() // by 키워드 : 프로퍼티와 위임 객체를 연결
}

fun main() {
    val foo = Foo() // provideDelegate 호출
    val oldValue = foo.p // delegate.getValue 호출
    foo.p = newValue // delegate.setValue 호출
}</code></pre>
<ul>
<li>관례에 따라 Delegate 클래스는 getValue와 setValue를 제공해야 함</li>
<li>변경 가능한 프로퍼티만 setValue응 사용</li>
<li>provideDelegate 함수는 선택적으로 구현<ul>
<li>최초 생성시 검증 로직</li>
<li>위임이 인스턴스화 되는 방식 변경</li>
</ul>
</li>
<li>foo.p → Delegate 타입의 위임 프로퍼티 객체에 있는 메서드를 호출</li>
</ul>
<hr>
<h2 id="by-lazy를-사용한-지연-초기화">by lazy()를 사용한 지연 초기화</h2>
<ul>
<li>객체의 일부분을 초기화하지 않고 나중에 필요할 때 초기화하는 패턴<ul>
<li>초기화 과정에서 자원을 많이 사용하거나 객체를 사용할 때마다 꼭 초기화 하지 않아도 되는 프로퍼티에 사용할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="뒷받침하는-프로퍼티를-사용해-지연-초기화-구현">뒷받침하는 프로퍼티를 사용해 지연 초기화 구현</h3>
<pre><code class="language-kotlin">class Email { /*...*/ }

fun loadEmails(person: Person): List&lt;Email&gt; {
    println(&quot;${person.name}의 이메일을 가져옴&quot;)
    return listOf(/*...*/)
}

class Person(val name: String) {
    private var _emails: List&lt;Email&gt;? = null // 데이터를 저장하고 emails의 위임 객체 역할을 하는 프로퍼티

    val emails: List&lt;Email&gt;
        get() {
            if(_emails == null) {
                _emails = loadEmails(this) // 최초 접근시 이메일을 가져옴
            }
            return _emails!! // 저장해놓은 데이터가 있으면 그 데이터를 반환
        }
}

fun main() {
    val p = Person(&quot;Alice&quot;)
    p.emails // 최초로 emails에 접근할 때만 이메일을 가져옴
    // Alice의 이메일을 가져옴
    p.emails
}</code></pre>
<ul>
<li><p>뒷바침하는 프로퍼티를 사용</p>
<ul>
<li>외부로는 읽기 전용 프로퍼티를 제공하고 내부에는 가변 프로퍼티를 사용</li>
<li>데이터 캡슐화 강화, 가변 데이터의 안전한 관리, 읽기 전용 API 제공</li>
</ul>
</li>
<li><p>_emails → 값을 저장, null이 될 수 있는 타입</p>
</li>
<li><p>emails → _emails에 대한 읽기 연산을 제공, null이 될 수 없는 타입</p>
</li>
<li><p>뒷바침하는 프로퍼티의 이름은 관례를 따름</p>
<ul>
<li>비공개 프로퍼티 앞에 언더바( _ )를 붙임</li>
<li>공개 프로퍼티는 아무것도 붙이지 않음</li>
</ul>
</li>
<li><p>뒷받침하는 프로퍼티 사용의 단점</p>
<ol>
<li>코드가 복잡함</li>
<li>스레드 안전한 구현이 아님<ul>
<li>loadEmails 함수에 여러 스레드에서 동시 접근할 경우 일관성이 망가질 수 있음</li>
</ul>
</li>
</ol>
<ul>
<li>이 문제들을 위임 프로퍼티를 사용해 해결할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="위임-프로퍼티를-통한-지연-초기화-구현">위임 프로퍼티를 통한 지연 초기화 구현</h3>
<ul>
<li>위임 프로퍼티를 사용하면 간결하게 구현할 수 있음<ul>
<li>데이터를 저장할 때 쓰이는 뒷파침하는 프로퍼티와 값이 오직 한 번만 초기화됨을 보장하는 게터 로직을 함께 캡슐화해줌</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">class Person(val name: String) {
        val emails by lazy { loadEmails(this) }
}</code></pre>
<ul>
<li>lazy 함수<ul>
<li>코틀린 관례에 맞는 시그니처의 getValue 메서드가 들어있는 객체를 반환함</li>
<li>by 키워드를 함께 사용해 위임 프로퍼티를 만들 수 있음</li>
<li>인자 → 값을 초기화 할 때 호출할 람다임</li>
<li>기본적으로 thread-safe한 함수임</li>
<li>필요하면 동기화 락을 lazy 함수에 전달할 수 있음</li>
<li>다중 스레드 환경에서 사용하지 않을 프로퍼티를 위해 동기화를 생략할 수도 있음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="위임-프로퍼티-구현">위임 프로퍼티 구현</h2>
<h3 id="observable-예시">observable 예시</h3>
<ul>
<li>observable : 어떤 객체를 UI에 표시하는 경우 객체가 바뀌면 자동으로 UI도 바뀌는 프로그램</li>
</ul>
<ol>
<li>위임 프로퍼티 없이 구현</li>
</ol>
<pre><code class="language-kotlin">fun interface Observer {
        fun onChange(name: String, oldValue: Any?, newValue: Any?)
}

open class Observable {
        val observers = mutableList&lt;Observer&gt;()
        fun notifyObservers(propName: String, oldValue: Any?, newValue: Any?) {
                for(obs in observers){
                        obs.onChange(propName, oldValue, newValue)
                }
        }
}</code></pre>
<ul>
<li>Observable 클래스 → Observer 리스트 관리</li>
<li>NotifyObservers → 등록된 모든 Observer의 onChange 함수를 통해 프로퍼티의 이전 값과 새 값을 전달</li>
<li>Observers → onChage에 대한 구현만 제공하면 됨</li>
</ul>
<pre><code class="language-kotlin">class Person(val name: String, age: Int, salary: Int): Observable() {
        var age: Int = age
                set(newValue){
                        val oldValue = field // 뒷받침하는 프로퍼티에 접근할 때 field 식별자를 사용
                        field = newValue
                        notifyObservers(&quot;age&quot;, oldValue, newValue)
                }

        var salary: Int = salary
                set(newValue){
                        val oldValue = field
                        field = newValue
                        notifyObservers(&quot;salary&quot;, oldValue, newValue)
                }
}

fun main() {
        val p = Person(&quot;seb&quot;, 28, 1000) 
        p.observers += Observer {propName, OldValue, newValue -&gt; 
                // 함수형 인터페이스에 대한 간편한 구문을 사용해
                // 옵저버를 생성하고 이를 등록하여 프로퍼티의 변경을 기다림
                println(                                             
                    &quot;&quot;&quot;
                    Property $propName changed from $oldValue to $newValue!
                    &quot;&quot;&quot;.trimIndent()
                )
        } 
        p.age = 29
        // Property age changed from 28 to 29
        p.salary = 1500
        // Property salary changed from 1000 to 1500
}</code></pre>
<ul>
<li>Field 키워드를 사용해 age, salary 프로퍼티를 뒷받침하는 필드에 접근</li>
<li>Setter코드의 중복이 많음</li>
</ul>
<ol start="2">
<li>도우미 클래스를 통해 프로퍼티 변경 통지 구현</li>
</ol>
<pre><code class="language-kotlin">class ObservableProperty(
        val propName: String,
        var propValue: Int,
        val observable: Observable
) {
        fun getValue(): Int = propValue
        fun setValue(newValue: Int) {
                val oldValue = propName
                propValue = newValue
                observable.notyiObservers(propName, oldValue, newValue)
        }
}

class Person(val name: String, age: Int, salary: Int): Observable() {
        val _age = observableProperty(&quot;age&quot;, age, this)
        var age: Int
                get() = _age.getValue()
                set(newValue) {
                        _age.setValue(newValue)
                }
        val _salary = ObservableProperty(&quot;salary&quot;, salary, this)
        var salary: Int
                get() = _salary.getValue()
                set(newValue) {
                        _salary.setValue(newValue)
                }
}</code></pre>
<ul>
<li>프로퍼티 값을 저장하고 그 값이 바뀌면 자동으로 변경을 통지</li>
<li>중복을  상당 부분 제거했으나 아직 각각의 프로퍼티마다 ObservableProperty를 만들고 작업을 위임하는 코드가 중복됨</li>
<li>코틀린 위임 프로퍼티 기능ㅇ로 이런 중복 조차 없앨 수 있음</li>
</ul>
<ol start="3">
<li>프로퍼티 위임 객체</li>
</ol>
<pre><code class="language-kotlin">import kotiln.reflect.KProperty 

class ObservableProperty(var propValue: Int, val observable: Obvservable) { 
        operator fun getValue(thisRef: Any?, prop: KProperty&lt;*&gt;): Int = propValue
        operator fun setValue(thisRef: Any?, prop: KProperty&lt;*&gt;, newValue: Int) { 
                val oldValue = propValue
                propValue = newValue
                observable.notifyObservers(prop.name, oldValue, newValue)
        }
}</code></pre>
<ul>
<li>코틀린 관례에 사용하는 다른 함수처럼 getValue, setValue 함수에도 operator 변경자가 붙음</li>
<li>GetValue와 setValue는 파라미터 2개를 받음<ul>
<li>ThisRef → 설정하거나 읽을 프로퍼티가 들어있는 인스턴스</li>
<li>Prop → 프로퍼티를 표현하는 객체<ul>
<li>KProperty 타입의 객체를 사용하여 프로퍼티를 표현</li>
</ul>
</li>
</ul>
</li>
<li>KProperty 인자를 통해 프로퍼티 이름을 전달받으므로 주 생성자에서는 name 프로퍼티를 없앰</li>
</ul>
<pre><code class="language-kotlin">class Person(val name: String, age: Int, salary: Int): Observable() { 
        var age by ObservableProperty(age, this)
        var salary by ObservableProperty(salary, this)
}</code></pre>
<ul>
<li>By 키워드를 사용하여 위임 객체를 지정<ul>
<li>Getter/setter를 직접 지저하는 등의 작업을 컴파일러가 자동으로 해줌</li>
</ul>
</li>
</ul>
<ol start="4">
<li>Delegates.observable을 사용해 프로퍼티 변경 통지 구현하기</li>
</ol>
<ul>
<li>ObservableProperty 클래스를 직접 구현하는 대신 표준 라이브러리 기능 활용하기</li>
<li>앞에서 구현한 Observable 클래스오는 연결되어 있지 않음</li>
</ul>
<pre><code class="language-kotlin">import kotiln..properties.Delegates

class Person(val name: String, age: Int, salary: Int): Observable() { 
        private val onChange = { 
        property: KProperty&lt;*&gt;, 
        oldValue: Any?, 
        newValue: Any? -&gt; notifyObservers(property.name, oldValue, newValue)
        }

        var age by Delegates.observable(age, onChange)
        var salary by Delegates.observable(salary, onChange)
}</code></pre>
<ul>
<li>프로퍼티 값의 변경을 통지받을 때 쓰일 람다를 Delegates 라이브러리 클래스에 넘겨야 함</li>
<li>By의 오른쪽에 있는 식 → 꼭 새 인스턴스를 만들 필요는 없음<ul>
<li>함수 호출, 다른 프로퍼티, 다른 식 등도 올 수 있음</li>
<li>다만 결과 객체는 컴파일러가 호출할 수 있는 올바른 타입의 getValue와 setValue를 반드시 제공해야 함</li>
</ul>
</li>
</ul>
<blockquote>
<p>프로퍼티 위임은 완전히 제네릭하여 모든 타입에 사용할 수 있음</p>
</blockquote>
<hr>
<h2 id="위임-프로퍼티의-동작-방식">위임 프로퍼티의 동작 방식</h2>
<p>위임 프로퍼티 클래스 예시</p>
<pre><code class="language-kotlin">class C { 
        var prop: Type by MyDelegate()
}

val c = C()</code></pre>
<ul>
<li>MyDelegate 클래스의 인스턴스 → 감춰진 프로퍼티에 저장됨<ul>
<li><delegate>라는 이름으로 부름</li>
</ul>
</li>
<li>프로퍼티 표현 → KProperty 타입의 객체를 사용<ul>
<li><property>라는 이름으로 부름</li>
</ul>
</li>
</ul>
<p>컴파일러는 다음의 코드를 생성</p>
<pre><code class="language-kotlin">class C { 
        private val &lt;delegate&gt; = MyDelegate()

        var prop: Type
                get() = &lt;delegate&gt;.getValue(this, &lt;property&gt;)
                set(value: Type) = &lt;delegate&gt;.setValue(this, &lt;property&gt;, value)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/dd201e3e-41a6-4d76-b82b-99a4e863e6ff/image.jpeg" alt=""></p>
<ul>
<li>프로퍼티 값이 저장될 장소를 바꿀 수 있음 (맵, db 테이블, 웹 쿠키 등)</li>
<li>프로퍼티를 읽거나 쓸 때 실행할 작업을 변경할 수 있음</li>
</ul>
<hr>
<h2 id="맵에-위임해서-동적으로-애트리뷰트-접근">맵에 위임해서 동적으로 애트리뷰트 접근</h2>
<p>자신의 프로퍼티를 동적으로 정의할 수 있는 객체를 만들 때 위임 ㅍ로퍼티를 활용하는 경우가 자주 있음</p>
<ul>
<li>C++에선 그런 객체를 <strong>확장 가능한 객체(expando object)</strong>라고 부름</li>
<li>속성을 모두 맴에 저장하되 특별한 처리가 필요한 정보에 접근하도록 프로퍼티를 제공할 수 있음</li>
</ul>
<pre><code class="language-kotlin">class Person { 
        private val _attributes = mutableMap&lt;String, String&gt;()

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

        var name: String
                get() = _attributes[&quot;name&quot;]!!
                set(value) { 
                        _attributes[&quot;name&quot;] = value
                }
}

fun main() { 
        val p = Person()
        val data = mapOf(&quot;name&quot; to &quot;Seb&quot;, &quot;company&quot; to &quot;JetBrains&quot;)
        for((attrName, value) in data)
                p.setAttribute(attrName, value)
        println(p.name)
        // Seb
        p.name = &quot;Sebastian&quot;
        println(p.name)
        // Sebastian    
}</code></pre>
<ul>
<li>추가 데이터를 객체에 읽어 들이기 위해 일반적인 API를 사용</li>
<li>한 프로퍼티를 처리하기 위해 구체적인 API를 제공</li>
</ul>
<h3 id="값을-맵에-저장하는-위임-프로퍼티-사용">값을 맵에 저장하는 위임 프로퍼티 사용</h3>
<p>By 키워드 뒤에 맵을 직접 넣음</p>
<pre><code class="language-kotlin">class Person {
        private val _attributes = mutableMapOf&lt;String, String&gt;()

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

        var name: String by _attributes // 위임 프로퍼티로 맵 사용
}</code></pre>
<ul>
<li>코드가 작동하는 이유 → 표준 라이브러리가 Map과 MutableMap 인터페이스에 getValue와 setValue 확장 함수를 제공하기 때문</li>
<li><a href="http://P.name">P.name</a> → _attribute.getValue(p, prop)을 호출</li>
<li>_attribute.getValue(p, prop)는 -attributes[prop.name]을 통해 구현</li>
</ul>
<hr>
<h2 id="실전-프레임워크가-위임-프로퍼티를-활용하는-방법">실전 프레임워크가 위임 프로퍼티를 활용하는 방법</h2>
<ul>
<li>객체 프롶티를 저장하거나 병경하는 방법을 바꿔 프레임워크 개발에 활용</li>
</ul>
<h3 id="위임-프로퍼티를-사용해-데이터베이스-칼럼에-접근">위임 프로퍼티를 사용해 데이터베이스 칼럼에 접근</h3>
<ul>
<li>User라는 데이터베이스 테이블이 있음<ul>
<li>Name이라는 문자열 타입의 칼럼</li>
<li>Age라는 정수 타입의 칼럼</li>
</ul>
</li>
<li>User와 Users라는 클래스를 정의해 데이터베이스에 있는 모든 사용자 엔티티를 User클래스로 가져오고 저장할 수 있음</li>
</ul>
<pre><code class="language-kotlin">object Users: IdTable() { // 데이터베이스 테이블
        val name = varchar(&quot;name&quot;, length = 50).index() // 테이블 칼럼
        val age = integer(&quot;age&quot;)
}

class User(id: EntityID): Entity(id) { // 각 테이블에 들어있는 구체적인 엔티티
        var name: String by Users.name // 데잍베이스에 저장된 사용자의 이름 값
        var age: Int by Users.age
}</code></pre>
<ul>
<li>Users 객체 → 데잍베이스 테이블을 표현<ul>
<li>데이터베이스는 전체에 단 하나만 존재하는 테이블을 표현하므로 싱글턴 객체로 선언</li>
<li>객체의 프로퍼티는 칼럼을 표현</li>
</ul>
</li>
<li>User의 상위 클래스인 Entity 클래스 → 데이터베이스 칼럼을 엔티티의 속성값으로 연결해주는 매핑<ul>
<li>데이터베이스에서 가져온 name, age가 있음</li>
<li>User의 프로퍼티에 접근할 때 자동으로 Entity 클래스에 정의된 데이터베이스 매핑으로부터 필요한 값을 가져옴</li>
<li>객체를 변경하면 객체가 변경됨(dirty) 상태로 변하고 나중에 데이터베이스에 적절히 변경을 반영해줌</li>
</ul>
</li>
</ul>
<h3 id="데이터베이스-접근-분석">데이터베이스 접근 분석</h3>
<ul>
<li>Users의 칼럼 타입을 명시적으로 지정한 모습</li>
</ul>
<pre><code class="language-kotlin">object Users: IdTable() {
        val name: Column&lt;String&gt; = varchar(&quot;name&quot;, 50).index()
        val age: Column&lt;Int&gt; = integer(&quot;age&quot;)
}</code></pre>
<ul>
<li><p>프레임워크는 Column 클래스 안에 getValue, setValue 메서드를 정의</p>
<ul>
<li><p>코틀린 위임 객체에 관례에 따른 시그니처 요구 사항을 충족</p>
<pre><code class="language-kotlin">operator fun &lt;T&gt; Column&lt;T&gt;.getValue(o: Entity, desc: KProperty&lt;*&gt;): T { 
      // 데이터베이스에서 칼럼 값 가져오기
}

operator fun &lt;T&gt; Column&lt;T&gt;.setValue(o: Entity, desc: KProperty&lt;*&gt;, value: T) { 
      // 데이터베이스의 값 변경하기
}</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="column-프로퍼티를-위임-프로퍼티에-대한-위임-객체로-사용할-수-있음">Column 프로퍼티를 위임 프로퍼티에 대한 위임 객체로 사용할 수 있음</h3>
<ul>
<li>User.age += 1 → user.ageDelegate.setValue(user.Delegate.getValue()+1)와 비슷한 코드로 변환됨</li>
<li>GetValue/setValue → 데이터베이스에서 데이터를 가져오고 기록하는 작업을 처리</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[component 함수를 사용해 구조 분해 선언 제공]]></title>
            <link>https://velog.io/@first_woosun/component-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EA%B5%AC%EC%A1%B0-%EB%B6%84%ED%95%B4-%EC%84%A0%EC%96%B8-%EC%A0%9C%EA%B3%B5</link>
            <guid>https://velog.io/@first_woosun/component-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EA%B5%AC%EC%A1%B0-%EB%B6%84%ED%95%B4-%EC%84%A0%EC%96%B8-%EC%A0%9C%EA%B3%B5</guid>
            <pubDate>Mon, 16 Feb 2026 11:41:26 GMT</pubDate>
            <description><![CDATA[<ul>
<li>구조 분해를 사용하면 복합적인 값을 분해해서 별도의 여러 지역 변수를 한꺼번에 초기화할 수 있음</li>
</ul>
<h3 id="사용-방법">사용 방법</h3>
<pre><code class="language-kotlin">fun main() {
        val p = Point(10, 20)
        val (x, y) = p // p의 여러 컴포넌트로 초기화
        println(x)
        // 10
        println(y)
        // 20
}</code></pre>
<ul>
<li>일반 변수 선언과 비슷해 보이지만 = 왼쪽에 여러 변수를 괄호로 묶음</li>
<li>구조 분해 선언은 관례를 사용함 → componenN 이라는 함수를 호출</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/59aa347d-5a5c-47c9-93a5-1e797c7d21de/image.png" alt=""></p>
<ul>
<li>data  클래스의 주 생성자에 들어있는 프로퍼티 → 컴파일러가 자동으로 componentN 함수를 만들어줌</li>
</ul>
<pre><code class="language-kotlin">class Person(val x: Int, val y: Int) {
        operator fun component1() = x
        operator fun component2() = y
}</code></pre>
<h3 id="예시">예시</h3>
<pre><code class="language-kotlin">data class NameComponent(val name: String, val extension: String)

fun splitFilename(fullName: String): NameComponent {
    val (name, extension) = fullName.split(&#39;.&#39;, limit = 2)
    return NameComponent(name, extension)
}

fun main() {
    val (name, ext) = splitFilename(&quot;example.txt&quot;)
    println(name)
    // example
    println(ext)
    // txt
}</code></pre>
<ul>
<li>componentN을 무한히 선언할 수는 없음<ul>
<li>코틀린 표준 라이브러리에서는 맨 앞의 다섯 원소에 대한 componentN을 제공</li>
</ul>
</li>
</ul>
<hr>
<h2 id="구조-분해-선언과-루프">구조 분해 선언과 루프</h2>
<ul>
<li>변수 선언이 들어갈 수 있는 모든 곳에 구조 분해 선언을 사용할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun printEntries(map: Map&lt;String, String&gt;) {
    for((key, value) in map) {
        println(&quot;$key -&gt; $value&quot;)
    }
}

fun main() {
    val map = mapOf(&quot;Oracle&quot; to &quot;Java&quot;, &quot;JetBrains&quot; to &quot;Kotlin&quot;)
    printEntries(map)
    // Oracle -&gt; Java
    // JetBrains -&gt; Kotlin
}</code></pre>
<ul>
<li><p>위 예제는 2가지의 관례를 사용함</p>
<ol>
<li>이터레이션 관례<ul>
<li>코틀린 표준 라이브러리 → map에 대한 확장함수로 iteration이 들어있음</li>
<li>맵 항목에 대한 이터레이터를 반환</li>
</ul>
</li>
<li>구조 분해 선언<ul>
<li>코틀린 라이브러리 → map.Entry에 대한 확장함수로 component1, component2를 제공</li>
</ul>
</li>
</ol>
</li>
<li><p>람다가 data class나 map 같은 복합적인 파라미터로 받을 때도 구조 분해 선언을 쓸 수 있음</p>
</li>
</ul>
<pre><code class="language-kotlin">map.forEach { (key, value) -&gt;
        println(&quot;$key -&gt; $value&quot;)
}</code></pre>
<hr>
<h2 id="_-문자를-사용해-구조-분해-값-무시">_ 문자를 사용해 구조 분해 값 무시</h2>
<ul>
<li>컴포넌트가 여럿 있는 객체에 대한 구조 분해 선언 중 일부 필요하지 않은 컴포넌트를 무시</li>
</ul>
<pre><code class="language-kotlin">data class Person(
    val firstName: String,
    val lastName: String,
    val age: Int,
    val city: String
)

fun introducePerson(p: Person) {
    val (firstName, _, age) = p
    println(&quot;This is $firstName, aged $age&quot;)
}

fun main() {
    val me = Person(&quot;yoo&quot;, &quot;wuseon&quot;, 26, &quot;ganwondo&quot;)
    introducePerson(me)
    // This is yoo, aged 26
}</code></pre>
<blockquote>
<h3 id="코틀린-구조-분해의-한계">코틀린 구조 분해의 한계</h3>
</blockquote>
<ul>
<li>구조 분해 연산의 결과 → 인자의 위치에 따라 결정됨<ul>
<li>코드를 리펙터링 할 때 클래스 프로퍼티의 순서를 바꾸면 미묘한 문제가 발생할 수 있음</li>
<li>이 문제를 해결하기 위해 이름 기반 구조 분해가 개발되고 있는 중임</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컬렉션과 범위에 대해 쓸 수 있는 관례]]></title>
            <link>https://velog.io/@first_woosun/%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EB%B2%94%EC%9C%84%EC%97%90-%EB%8C%80%ED%95%B4-%EC%93%B8-%EC%88%98-%EC%9E%88%EB%8A%94-%EA%B4%80%EB%A1%80</link>
            <guid>https://velog.io/@first_woosun/%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EB%B2%94%EC%9C%84%EC%97%90-%EB%8C%80%ED%95%B4-%EC%93%B8-%EC%88%98-%EC%9E%88%EB%8A%94-%EA%B4%80%EB%A1%80</guid>
            <pubDate>Mon, 16 Feb 2026 11:39:48 GMT</pubDate>
            <description><![CDATA[<ul>
<li>컬렉션에서 가장 자주 쓰는 연산<ol>
<li>인덱스로 원소를 읽거나 쓰기</li>
<li>어떤 값이 컬렉션에 속해 있는지 검사</li>
</ol>
<ul>
<li>이 연산들은 연산자 구문으로 사용할 수 있음</li>
</ul>
</li>
<li>인덱스 접근 → collection[index]</li>
<li>원소가 컬렉션이나 범위에 속해 잇는지 검사 → in 연산자</li>
</ul>
<hr>
<h2 id="인덱스로-원소에-접근-get--set">인덱스로 원소에 접근 (get / set)</h2>
<ul>
<li>코틀린의 인덱스 접근 연산자 → 관례를 따름<ul>
<li>원소를 읽는 연산 → get 연산자 메서드</li>
<li>원소를 쓰는 연산 → set 연산자 메서드</li>
</ul>
</li>
</ul>
<h3 id="get-메서드">get 메서드</h3>
<pre><code class="language-kotlin">class Point(val x: Int, val y: Int)

operator fun Point.get(index: Int) : Int {
    return when(index) {
        0 -&gt; x
        1 -&gt; y
        else -&gt; throw IndexOutOfBoundsException(&quot;Invalid coordinate $index&quot;)
    }
}

fun main() {
    val p = Point(10, 20)
    println(p[1])
    // 20
}</code></pre>
<ul>
<li>get 메서드의 파라미터 → Int가 아니어도 됨<ul>
<li>컬렉션 클래스가 다양한 키 타입을 지원해야 한다면 파라미터 타입에 대해 오버로딩한 get 메서드를 여럿 정의할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="set-메서드">set 메서드</h3>
<pre><code class="language-kotlin">data class MutablePoint(var x: Int, var y: Int)

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

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

fun main() {
    val p = MutablePoint(10, 20)
    p[1] = 30
    println(p[1])
    // 20
}</code></pre>
<hr>
<h2 id="어떤-객체가-컬렉션에-들어있는지-검사-in-관례">어떤 객체가 컬렉션에 들어있는지 검사 (in 관례)</h2>
<ul>
<li>in : 객체가 컬렉션에 들어있는지 검사</li>
<li>대응하는 함수 : contain()</li>
</ul>
<pre><code class="language-kotlin">data class Point(val x: Int, val y: Int)

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x ..&lt; lowerRight.x &amp;&amp; p.y in upperLeft.y ..&lt; lowerRight.y
    // ..&lt; : 열린 범위
}

fun main() {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    println(Point(20, 20) in rect)
    // true
    println(Point(5, 5) in rect)
    // false
}</code></pre>
<ul>
<li>in 오른쪽에 있는 객체 → contain 메서드의 수신 객체</li>
<li>in 오른쪽에 있는 객체 → contain 메서드에 인자로 전달</li>
</ul>
<hr>
<h2 id="객체로부터-범위-만들기-rangeto-rangeuntil-관례">객체로부터 범위 만들기 (rangeTo, rangeUntil 관례)</h2>
<h3 id="rangeto-연산자">rangeTo 연산자</h3>
<ul>
<li>닫힌 범위를 만듦 (상계 값 포함 o)</li>
<li>.. 연산자 → rangeTo 함수 호출로 컴파일</li>
</ul>
<pre><code class="language-kotlin">start .. end -&gt; start.rangeTo(end)</code></pre>
<ul>
<li><p>rangeTo 함수 → 범위를 반환함</p>
</li>
<li><p>어떤 클래스가 Comparable 인터페이스를 구현한다면 rangeTo를 정의할 필요가 없음</p>
</li>
<li><p>코틀린 표준 라이브러리 → 모든 Comparable 객체에 대해 적용 가능한 rangeTo 함수가 들어있음</p>
</li>
</ul>
<pre><code class="language-kotlin">operator fun &lt;T: Comparable&lt;T&gt;&gt; T.rangeTo(that: T): ClosedRange&lt;T&gt;</code></pre>
<pre><code class="language-kotlin">import java.time.LocalDate

fun main() {
    val now = LocalDate.now()
    val vacation = now .. now.plusDays(10)
    println(now.plusDays(1) in vacation)
    // true
}</code></pre>
<ul>
<li>now .. now.plusDays(10) → now.rangeTo(now.plusDays(10)) 으로 변환</li>
<li>rangeTo 연산자는 다른 산술 연산자들 보다 우선순위가 낮음</li>
</ul>
<h3 id="rangeuntil-연산자">rangeUntil 연산자</h3>
<ul>
<li>열린 범위를 만듦 (상계 값 포함 x)</li>
</ul>
<pre><code class="language-kotlin">fun main() {
        (0 ..&lt; 9).forEach{ println(it) }
}</code></pre>
<hr>
<h2 id="자신의-타입에-대해-루프-수행-iterator-관례">자신의 타입에 대해 루프 수행 (iterator 관례)</h2>
<ul>
<li>코틀린의 for 루프 → in 연산자를 사용<ul>
<li>하지만 contain 함수가 아닌 iterator 함수를 호출</li>
<li>iterator(를 호출해 이터레이터를 얻은 다음 그 이터레[이터에 대해 hashNext와 next 호출을 반복</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">import java.time.LocalDate

operator fun ClosedRange&lt;LocalDate&gt;.iterator(): Iterator&lt;LocalDate&gt; =
    object : Iterator&lt;LocalDate&gt; {
        var current = start
        override fun hasNext(): Boolean = current &lt;= endInclusive
        override fun next(): LocalDate {
            val thisDate = current
            current = current.plusDays(1)
            return thisDate
        }
    }

fun main() {
    val newYear = LocalDate.ofYearDay(2042, 1)
    val daysOff = newYear.minusDays(1) .. newYear
    for(dayOff in daysOff) {println(dayOff)}
    // 2041-12-31
        // 2042-01-01
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[비교 연산자 오버로딩]]></title>
            <link>https://velog.io/@first_woosun/%EB%B9%84%EA%B5%90-%EC%97%B0%EC%82%B0%EC%9E%90-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</link>
            <guid>https://velog.io/@first_woosun/%EB%B9%84%EA%B5%90-%EC%97%B0%EC%82%B0%EC%9E%90-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</guid>
            <pubDate>Mon, 16 Feb 2026 11:38:48 GMT</pubDate>
            <description><![CDATA[<ul>
<li>코틀린은 기본 타입 뿐만 아니라 객체에 대해 비교 연산을 수행할 수 있음<ul>
<li>자바에서는 equals나 compareTo를 호출해야 함</li>
<li>코틀린에선 산술 연산자를 사용하여 코드를 간소화 할 수 있음</li>
</ul>
</li>
</ul>
<h2 id="동등성-연산자-equals">동등성 연산자 equals</h2>
<ul>
<li>== 연산자 → equals 메서드로 컴파일<ul>
<li>관례를 적용한 것</li>
</ul>
</li>
<li>!= 연산자도 equals 호출로 컴파일 됨</li>
<li>==와 !=는 인자가 null인지 검사<ul>
<li>null이 될 수 있는 값에도 적용할 수 있음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/50dd3e53-d451-490c-9244-2005dd928b30/image.png" alt=""></p>
<h3 id="메서드-구현">메서드 구현</h3>
<pre><code class="language-kotlin">class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean {
        if(other === this) return true // 파라미터가 this와 같은 객체인지 검사
        if(other !is Point) return false // 파라미터 타입 검사
        return other.x == x &amp;&amp; other.y == y 
    }
}

fun main( ) {
    println(Point(10, 20) == Point(10, 20))
    // ture
    println(Point(10, 20) != Point(5, 5))
    // true
    println(null == Point(1, 2))
    // false
}</code></pre>
<ul>
<li>동등성 연산자 (===)를 사용해 두 피연산자가 서로 같은 객체를 가리키는지 비교<ul>
<li>동등성 연산자는 오버로딩 할 수 없음</li>
</ul>
</li>
<li>equals 함수 → Any 클래스에 정의된 메서드<ul>
<li>상위 클래스에 정의된 메서드를 오버라이드 함</li>
<li>Any 클래스에서 operator 키워드를 사용하여 정의한 메서드이기 때문에 오버라이드 메서드는 자동으로 상위 클래스의 operator 지정이 적용됨</li>
<li>상속받은 equals가 확장 함수보다 우선순위가 높음 → equals를 확장 함수로 정의할 수 없음</li>
</ul>
</li>
<li>!= 호출은 equals 메서드를 호출한 결과를 반전시켜 반환함<ul>
<li>추가적인 구현은 필요 없음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="순서-연산자-compareto----≤-≥-">순서 연산자 compareTo ( &lt;, &gt;, ≤, ≥ )</h2>
<ul>
<li>자바에서 정렬, 최댓값/최솟값 비교 등을 수행하는 클래스<ul>
<li>Comparable 인터페이스를 구현해야 함</li>
<li>하지만 인터페이스를 구현한 메서드를 간결하게 호출할 수 없음</li>
<li>&lt;, &gt; 의 연산자는 기본 타입의 값만 비교할 수 있음</li>
<li>element1.compareTo(element2)</li>
</ul>
</li>
<li>코틀린의 Comparable 인터페이스<ul>
<li>compareTo 메서드를 호출하는 관례를 제공</li>
<li>비교 연산자를 compareTo 호출로 컴파일</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/4c9939c1-5e70-407c-a780-a20fc5f68bc4/image.png" alt=""></p>
<pre><code class="language-kotlin">class Person(
    val firstName: String,
    val lastName: String
): Comparable&lt;Person&gt; {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other, Person::lastName, Person::firstName)
    }
}

fun main() {
    val p1 = Person(&quot;Alice&quot;, &quot;Smith&quot;)
    val p2 = Person(&quot;Bob&quot;, &quot;Johnson&quot;)
    println(p1 &lt; p2)
    // false
}</code></pre>
<ul>
<li>Comparable 인터페이스를 구현함<ul>
<li>Person 객체를 코틀린 뿐만 아니라 자바 에서도 사용할 수 있음</li>
<li>compareTo도 equals와 마찬가지로 상위 클래스의 operator 지정을 물려받음</li>
</ul>
</li>
<li>compareValuesBy<ul>
<li>두 객체와 여러 비교 함수를 인자로 받음<ul>
<li>첫 번째 비교 함수 → 두 객체를 비교함<ul>
<li>두 객체가 같지 않으면 그 결과를 바로 반환함</li>
<li>두 객체가 같으면 두 번째 비교 함수를 통해 객체를 비교함</li>
</ul>
</li>
</ul>
</li>
<li>인자로 받은 값을 차례대로 호출해 두 값을 비교하면서 같다는 결과가 나올 때까지 반복함</li>
</ul>
</li>
<li>필드를 직접 비교하면 코드는 복잡해지지만 비교 속도는 더 빨라짐<ul>
<li>처음에는 간결하게 구현하고 성능 문제가 발생하면 개선하기</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[산술 연산자 오버로드]]></title>
            <link>https://velog.io/@first_woosun/%EC%82%B0%EC%88%A0-%EC%97%B0%EC%82%B0%EC%9E%90-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%93%9C</link>
            <guid>https://velog.io/@first_woosun/%EC%82%B0%EC%88%A0-%EC%97%B0%EC%82%B0%EC%9E%90-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%93%9C</guid>
            <pubDate>Mon, 16 Feb 2026 11:37:17 GMT</pubDate>
            <description><![CDATA[<ul>
<li>자바 → 기본 타입에 대해서 만 산술 연산자를 사용할 수 있음<ul>
<li>String 값에 대해 + 연산자를 사용할 수 있음</li>
</ul>
</li>
<li>클래스에 메서드를 호출하는것 보다 산술 연산자를 사용하는 편이 편할 때가 있음<ul>
<li>BigInteger 클래스에서 add를 호출하는것 보다 + 연산을 사용하는 편이 편함</li>
</ul>
</li>
<li>컬렉션에 원소를 추가하는 경우에도 +=을 사용할 수 있으면 편할 것</li>
</ul>
<h2 id="이항-산술-연산자-오버로딩-plus-times-divide-등">이항 산술 연산자 오버로딩 (plus, times, divide 등)</h2>
<h3 id="plus-연산자-오버로딩">plus 연산자 오버로딩</h3>
<pre><code class="language-kotlin">data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main() {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
    // Point(x=40, y=60)
}</code></pre>
<ul>
<li>연산자 오버로딩은 operator 키워드를 붙여야 함<ul>
<li>어떤 함수가 관례를 따르는 함수임을 명확히 할 수 있음</li>
<li>관례에서 사용하는 함수 이름을 사용하는 실수를 방지해줌</li>
</ul>
</li>
</ul>
<h3 id="확장-함수로-연산자-오버로딩">확장 함수로 연산자 오버로딩</h3>
<pre><code class="language-kotlin">data class Point(val x: Int, val y: Int) {}

operator fun Point.plus(other: Point): Point = Point(x + other.x, y + other.y)

fun main() {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
    // Point(x=40, y=60)
}</code></pre>
<ul>
<li>연산자를 맴버 함수로 만드는 대신 확장 함수로 정의할 수 있음</li>
</ul>
<h3 id="다른-언어와-비교">다른 언어와 비교</h3>
<ul>
<li>코틀린은 자신만의 연산자를 정의할 수 없음<ul>
<li>오버로딩한 연산자를 정의하고 사용하기 쉬움</li>
</ul>
</li>
<li>중위 함수를 제공함<ul>
<li>a myOp b</li>
<li>함수의 양변에 피연산자를 둘 수 있음</li>
</ul>
</li>
</ul>
<h3 id="관례로-정의된-함수-이름과-연산자">관례로 정의된 함수 이름과 연산자</h3>
<ul>
<li><p>a * b → <strong>times</strong></p>
</li>
<li><p>a / b → <strong>div</strong></p>
</li>
<li><p>a % b → <strong>mod</strong></p>
</li>
<li><p>a + b → <strong>plus</strong></p>
</li>
<li><p>a - b → <strong>minus</strong></p>
</li>
<li><p>오버로딩으로 함수를 구현해도 연산자 우선순위는 변하지 않음</p>
</li>
</ul>
<h3 id="서로-다른-타입의-피-연산자">서로 다른 타입의 피 연산자</h3>
<ul>
<li>연산자를 정의할 때 두 피 연산자가 같은 타입일 필요는 없음</li>
</ul>
<pre><code class="language-kotlin">operator fun Point.times(scale: Double): Point = 
        Point((x * scale).toInt(), (y * scale).toInt())

fun main() {
    val p1 = Point(10, 20)
    println(p1 * 1.5)
    // Point(x=15, y=30)
}</code></pre>
<ul>
<li>코틀린 연산자는 교환법칙을 자동으로 제공하지 않음<ul>
<li>교환 법칙 : a op b == b op a</li>
</ul>
</li>
<li>1.5 * p1을 지원하려면 순서에 대응하는 연산자 함수를 정의해줘야 함</li>
</ul>
<pre><code class="language-kotlin">operator fun Double.times(p: Point) : Point =
    Point((absoluteValue * p.x).toInt(), (absoluteValue * p.y).toInt())

fun main() {
    val p1 = Point(10, 20)
    println(1.5 * p1)
    // Point(x=15, y=30)
}</code></pre>
<h3 id="반환값의-타입">반환값의 타입</h3>
<ul>
<li>반환 타입이 꼭 두 피연산자 중 하나와 일치해야 하는 것은 아님</li>
</ul>
<pre><code class="language-kotlin">operator fun Char.times(count: Int): String =
    toString().repeat(count)

fun main() {
    println(&#39;a&#39; * 3)
    // aaa
}</code></pre>
<h3 id="operator-함수-오버로딩">operator 함수 오버로딩</h3>
<ul>
<li>일반 함수처럼 operator 함수도 오버로딩 할 수 있음</li>
<li>이름은 같지만 파라미터 타입이 서로 다른 연산자 함수를 여러 개 만들 수 있음</li>
</ul>
<h3 id="비트-연산자">비트 연산자</h3>
<ul>
<li>코틀린은 표준 숫자 타입에 대해 비트 연산자를 정의하지 않음<ul>
<li>커스텀 타입에서 비트 연산자를 정의할 수 없음</li>
</ul>
</li>
<li>대신 중위 연산자 표기법을 지원하는 일반 함수로 비트 연산을 수행</li>
</ul>
<ol>
<li>shl : 왼쪽 시프트</li>
<li>shr : 오른쪽 시프트 (부호 비트 유지)</li>
<li>ushr : 오른쪽 시프트 (0으로 부호 비트 설정)</li>
<li>and</li>
<li>or</li>
<li>xor</li>
<li>inv : 비트 반전</li>
</ol>
<hr>
<h2 id="복합-대입-연산자-오버로딩">복합 대입 연산자 오버로딩</h2>
<ul>
<li>복합 대입 연산자 : +=, -= 등등…</li>
<li>코틀린은 연산자 오버로딩하면 복합 대입 연산자를 자동으로 지원</li>
</ul>
<pre><code class="language-kotlin">operator fun Point.plus(other: Point): Point = 
        Point(x + other.x, y + other.y)

fun main() {
    var point = Point(1, 2)
    point += Point(3, 4)
    println(point)
    // Point(x=4, y=6)
}</code></pre>
<h3 id="복합-대입-연산자-커스텀하기">복합 대입 연산자 커스텀하기</h3>
<ul>
<li>반환 타입이 Unit인 plusAssign 함수를 정의하면서 operator 키워드를 사용<ul>
<li>minusAssign, timesAssign 등도 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">operator fun &lt;T&gt; MutableCollection&lt;T&gt;.plusAssign(element: T) {
        this.add(element)
}</code></pre>
<ul>
<li>일반 연산자와 복합 대입 연산자를 동시에 오버로딩 해놓으면 컴파일러는 오류를 발생시킴</li>
</ul>
<h3 id="컬렉션에-대한-접근-방식">컬렉션에 대한 접근 방식</h3>
<ol>
<li>+, - <ul>
<li>새로운 컬렉션 반환</li>
</ul>
</li>
<li>+=, -=<ul>
<li>메모리에 있는 객체 상태를 변화시킴</li>
<li>var로 선언한 읽기전용 컬렉션에만 적용할 수 있음</li>
</ul>
</li>
</ol>
<hr>
<h2 id="단항-연산자-오버로딩">단항 연산자 오버로딩</h2>
<pre><code class="language-kotlin">operator fun Point.unaryMinus(): Point = Point(-x, -y)

fun main() {
    val p = Point(10, 20)
    println(-p)
    // Point(x=-10, y=-20)
}</code></pre>
<ul>
<li>단항 연산자 오버로딩은 인자를 취하지 않음</li>
</ul>
<h3 id="단항-연산자-종류">단항 연산자 종류</h3>
<ol>
<li>+a : unaryPlus</li>
<li>-a : unaryMinus</li>
<li>!a : not</li>
<li>++a, a++ : inc</li>
<li>--a, a-- : dec</li>
</ol>
<ul>
<li>증감 연산자(inc, dec)를 오버로딩 하는 경우 일반적인 값에 대해 전위/후위 연산자와 같은 의미를 제공함</li>
</ul>
<pre><code class="language-kotlin">import java.math.BigDecimal

operator fun BigDecimal.inc() = this + BigDecimal.ONE

fun main() {
    var bd = BigDecimal.ZERO
    println(bd++)
    // 0
    println(bd)
    // 1
    println(++bd)
    // 2
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 레벨0 3주차 회고]]></title>
            <link>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A80-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@first_woosun/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A80-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 16 Feb 2026 11:01:08 GMT</pubDate>
            <description><![CDATA[<p>많은 크루들이 KPT 회고 방식을 적용하여 작성한 글들을 읽어보며 ‘나도 적용해볼까?’라는 생각이 들었습니다.</p>
<p>그래서 3주차 회고록에는 KPT를 적용해 저의 3주차를 돌아보고자 합니다.</p>
<hr>
<h2 id="3주차-학습-목표">3주차 학습 목표</h2>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/e1fbdd5e-3b98-4d0e-b579-22e48232ace6/image.png" alt=""></p>
<h2 id="이번-주엔-뭘-했나">이번 주엔 뭘 했나?</h2>
<ul>
<li>공부한 내용<ul>
<li>7장, 널이 될 수 있는 값</li>
<li>8장, 기본 타입, 컬렉션, 배열</li>
<li>9장, 컬렉션 오버로딩과 관례</li>
</ul>
</li>
<li>회사 면접 (면접 회고는 따로 작성하기)</li>
<li>자취방으로 짐 옮기기</li>
<li>명절 준비</li>
</ul>
<h2 id="keep"><strong>K</strong>eep</h2>
<ol>
<li><p>규칙적인 생활</p>
<p> 매일 아침 8시에 일어나 반려견과 산책으로 하루를 시작하고 있습니다. 덕분에 아침 잠도 깨고 활기차게 하루를 시작할 수 있는 것 같습니다. 자취방으로 이동하고 나서도 아침 산책을 이어가고자 합니다.</p>
</li>
<li><p>잠시라도 공부하기</p>
<p> 이런 저런 일정들로 정말 정신이 없는 3주차 였습니다. 월요일에는 면접을 보고, 화요일에는 취업 관련 상담을 받고 토요일에 집을 옮겨야 해 이삿짐도 싸고 정말 어떻게 지나갔나 모를 정도로 정신 없었습니다. 그런 와중에도 공부를 놓지 않고 1~2시간 만이라도 책상에 앉았습니다.</p>
</li>
</ol>
<h2 id="problem">Problem</h2>
<ol>
<li><p>쓸 수 있는 시간에 비해 할게 너무 많다.</p>
<p> 저번주에도 느꼈지만 세운 계획에 비해 시간이 너무 부족하다라는 걸 느꼈습니다. 매일매일 공부만 한다면 계획을 지킬 수 있었을수도 있지만, 공부만 하기에는 현실적으로 해야 할 일도 많고 생각치 못한 변수도 생기면서 계획을 지키기 어려워졌다고 생각합니다.</p>
</li>
<li><p>공부 계획을 너무 크게 세웠다.</p>
<p> 다른 크루의 회고록에서 이런 말을 본 기억이 있습니다. “나를 과대평가 한거 같다.” 저도 그런것 같습니다. 교재 초반에는 비교적 간단하고 이해하는데 어려움이 적은 내용들이 많았으나, 진도를 나가면서 내용은 조금씩 복잡해지고 그 만큼 이해하는데 들어가는 시간이 늘어나 진도를 나가는 속도가 많이 느려져 1부 내용을 포함하여 2부를 겨우 시작했습니다.</p>
</li>
<li><p>질문을 던지지 않는다.</p>
<p> 크루들의 회고록을 읽어보며 발견한 공통점이 있습니다. 많은 분들이 “이게 뭔가?”에 집중하는 것이 아닌 “이게 뭐고 왜 쓰지?”에 초점을 두고 있었으며, 이에 그치지 않고 꼬리 질문을 생각해보고 그 질문에 대해 스스로 탐구하고 있었습니다. 이런 점들을 보며 현재 하고 있는 공부에 대해 다시 생각해보게 되었습니다. 코틀린을 처음 다뤄본다는 이유로 진도를 빼는데만 급급하지 않았나? 진도를 많이 뺀다고 해서 그것들을 온전히 흡수 할 수 있는가? 등 많은 자문들을 해보는 시간이었습니다.</p>
</li>
<li><p>커뮤니케이션을 안한다.</p>
<p> 최종 테스트를 준비할 당시 같은 관심사를 갖고 함께 공부할 사람이 없어 힘들었고 최종 테스트 회고록에도 이를 언급하며 소통에 힘을 쓰겠다고 다짐했지만 바쁘고 피곤하다는 핑계로 다른 크루와 소통을 하지 못했습니다.</p>
</li>
</ol>
<h2 id="try">Try</h2>
<ol>
<li><p>공부량보단 공부의 질에 집중하기</p>
<p> 지금까지는 진도를 빼기 바빠 깊이있게 공부하지 못했습니다. 마지막 주차인 만큼 내용 정리 외에도 정리한 내용을 토대로 꼬리 질문을 생각해보고 그 질문에 대한 답을 탐구하면서 “깊이 있는 공부”를 연습해보려 하려합니다.</p>
</li>
<li><p>커뮤니케이션에 힘쓰기</p>
<p> 좀 늦은 감이 있지만 이번주에는 다른 크루들과 소소하게 나마 소통해보고자 합니다…!! 크루들의 회고록을 읽으면서 많은 자극을 받고 있습니다. 이런 긍정적인 자극들을 이어나가기 위해 여러 사람들과 소통하며 함께 성장하고 싶습니다.</p>
</li>
</ol>
<h2 id="마무리하며">마무리하며</h2>
<p>3주차도 고생 많으셨습니다. 레벨 1 시작까지 1주일 남은 지금 남은 기간동안 다같이 힘내서 레벨1을 준비해갑시다! 🫡</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컬렉션과 배열]]></title>
            <link>https://velog.io/@first_woosun/%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@first_woosun/%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Thu, 12 Feb 2026 12:04:03 GMT</pubDate>
            <description><![CDATA[<h2 id="null이-될-수-있는-값의-컬렉션과-null이-될-수-있는-컬렉션">null이 될 수 있는 값의 컬렉션과 null이 될 수 있는 컬렉션</h2>
<ul>
<li>인자로 쓰인 타입에도 물음표( ? )를 붙이면 null을 저장할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun readNumbers(text: String): List&lt;Int?&gt; {
    val result = mutableListOf&lt;Int?&gt;() // null이 될 수 있는 값으로 이뤄진 가변 리스트
    for(line in text.lineSequence()){ // 입력 문자열을 한 줄씩 순회
        val numberOrNull = line.toIntOrNull()
        result.add(numberOrNull)
    }
    return result
}</code></pre>
<ul>
<li>list&lt;Int?&gt; → Int? 타입을 저장할 수 있음<ul>
<li>Int 또는 null을 저장할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="변수-타입의-null-가능성과-타입-파라미터의-null-가능성">변수 타입의 null 가능성과 타입 파라미터의 null 가능성</h3>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/418fa8ba-3826-4562-bb0f-ae186cee9c0e/image.jpg" alt=""></p>
<ol>
<li>List&lt;Int?&gt;<ul>
<li>리스트 자체는 항상 null이 아님</li>
<li>리스트에 들어가는 각 원소는 null이 될 수 있음</li>
</ul>
</li>
<li>List<Int>?<ul>
<li>리스트를 가리키는 변수에는 null이 들어갈 수 있음</li>
<li>리스트의 각 원소에는 null이 들어갈 수 없음</li>
</ul>
</li>
</ol>
<h3 id="변수-타입과-타입-파라미터의-null-가능성">변수 타입과 타입 파라미터의 null 가능성</h3>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/e45fa134-af86-42d0-a8f2-efa9caff7c00/image.jpg" alt=""></p>
<ul>
<li>리스트를 가리키는 변수에 대한 null 검사와 원소에 대한 null 검사를 해줘야 함</li>
</ul>
<h3 id="filterornot-함수">filterOrNot 함수</h3>
<pre><code class="language-kotlin">fun addValidNumbers(numbers: List&lt;Int?&gt;){
    val validNumbers = numbers.filterNotNull()
    println(&quot;Sum of valid numbers: ${validNumbers.sum()}&quot;)
    println(&quot;Invalid numbers: ${numbers.size - validNumbers.size}&quot;)
}</code></pre>
<ul>
<li>null이 아닌 값들을 걸러냄</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/b347bccd-0e34-4966-8915-561fc2564888/image.jpg" alt=""></p>
<hr>
<h2 id="읽기-전용과-변경-가능한-컬렉션">읽기 전용과 변경 가능한 컬렉션</h2>
<ul>
<li>코틀린 컬렉션 → 데이터 접근 인터페이스와 데이터 변경 인터페이스를 분리함<ul>
<li>kotiln.collections.Collection부터 존재</li>
</ul>
</li>
<li>Collections 인터페이스<ul>
<li>원소 이터레이션</li>
<li>컬렉션의 크기</li>
<li>저장된 값 검사</li>
<li>데이터 읽기</li>
<li>원소를 추가하는 인터페이스가 없음</li>
</ul>
</li>
<li>MutableCollection 인터페이스<ul>
<li>Collections 인터페이스를 확장</li>
<li>원소 추가, 삭제 등의 기능 확장</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/327c1b71-e967-48d5-aff6-92bace03e299/image.jpg" alt=""></p>
<ul>
<li>인터페이스를 구분한 이유 → 프로그램에서 데이터에 어떤 일이 벌어지는지 쉽게 이해하기 위해<ul>
<li>어떤 인터페이스를 인자로 받는지에 따라 어떤 작업을 하는지 유추할 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun &lt;T&gt; copyElements(source: Collection&lt;T&gt;, target: MutableCollection&lt;T&gt;) {
    for(item in source)
            target.add(item)
}

fun main() {
    val source: Collection&lt;Int&gt; = arrayListOf(3, 5, 7)
    val target: MutableCollection&lt;Int&gt; = arrayListOf(1)
    copyElements(source, target)
    print(target)
    // [1, 3, 5, 7]
}</code></pre>
<ul>
<li>변경 가능한 컬렉션에 읽기 전용 컬렉션을 넘길 수 없음</li>
</ul>
<pre><code class="language-kotlin">fun main() {
    val source: Collection&lt;Int&gt; = arrayListOf(3, 5, 7)
    val target: Collection&lt;Int&gt; = arrayListOf(1)
    copyElements(source, target)
    print(target)
    // Argument type mismatch: 
    // actual type is &#39;Collection&lt;Int&gt;&#39;, but &#39;MutableCollection&lt;Int&gt;&#39; was expected.
}</code></pre>
<hr>
<h2 id="코틀린-컬렉션과-자바-컬렉션의-연관성">코틀린 컬렉션과 자바 컬렉션의 연관성</h2>
<ul>
<li>코틀린 컬렉션 → 상응하는 자바 컬렉션 인터페이스의 인스턴스임<ul>
<li>코틀린과 자바를 오갈 때 아무 변환도 필요 없음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/8936af5d-65bc-4ee4-bd30-a450237bdac7/image.jpg" alt=""></p>
<ul>
<li>코틀린의 컬렉션 인터페이스<ul>
<li>기본구조 → java.util 패키지의 컬렉션 인터페이스와 구조가 같음</li>
<li>변경 가능 인터페이스 → 읽기 전용 인터페이스를 확장</li>
</ul>
</li>
<li>자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않음<ul>
<li>코틀린에서 읽기 전용 Collection으로 선언된 객체라도 자바 코드에서는 그 컬렉션 객체의 내용을 변경할 수 있음</li>
</ul>
</li>
<li>자바는 컬렉션에 null을 저장할 수 있음<ul>
<li>코틀린에서 null이 아닌 원소로 이뤄진 컬렉션을 자바에 넘기면 자바에서는 컬렉션에 null을 넣을 수 있음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="자바에서-선언한-컬렉션은-코틀린에서-플랫폼-타입으로-보임">자바에서 선언한 컬렉션은 코틀린에서 플랫폼 타입으로 보임</h2>
<ul>
<li>플랫폼 타입 → 널 관련 정보가 없음<ul>
<li>null이 될 수 있는 타입과 없는 타입 어느 쪽으로든 사용할 수 있음</li>
</ul>
</li>
<li>자바에서 선언한 컬렉션 → 코틀린에선 플랫폼 타입으로 보임<ul>
<li>컬렉션 타입이 시그니처에 들어간 자바 메서드 구현을 오버라이드 하려는 경우 읽기 전용 컬렉션과 변경 가능 컬렉션의 차이에 주의해야 함</li>
<li>오버라이드 하려는 메서드의 자바 컬렉션 타입을 어떤 코틀린 컬렉션 타입으로 표현할지 결정해야 함</li>
</ul>
</li>
</ul>
<h3 id="코틀린-컬렉션으로-변환-시-고려사항">코틀린 컬렉션으로 변환 시 고려사항</h3>
<ul>
<li>컬렉션이 null이 될 수 있는가?</li>
<li>컬렉션의 원소가 null이 될 수 있는가?</li>
<li>메서드가 컬렉션을 변경할 수 있는가?</li>
</ul>
<h3 id="예시">예시</h3>
<pre><code class="language-java">import java.io.File;
import java.util.List;

interface FileContentProcessor {
    void processContents(
            File path,
            byte[] binaryContents,
            List&lt;String&gt; textContents
    );
}</code></pre>
<ul>
<li>일부 파일이 이진 파일이고 이진 파일의 내용을 텍스트로 표현할 수 없을 수 있으므로 리스트는 null이 될 수 있음</li>
<li>파일의 각 줄은 null일 수 없으므로 이 리스트의 원소는 null일 수 없음</li>
<li>파일의 내용을 표현하며 그 내용을 바꿀 필요 없으므로 읽기 전용임</li>
</ul>
<pre><code class="language-java">import java.io.File

class FileIndexer: FileContentProcessor {
    override fun processContents(
        path: File?,
        binaryContents: ByteArray?,
        textContents: List&lt;String?&gt;?) 
    {
        /*...*/
    }
}</code></pre>
<hr>
<h2 id="성능과-상호운용을-위해-객체-배열이나-원시-타입-배열-만들기">성능과 상호운용을 위해 객체 배열이나 원시 타입 배열 만들기</h2>
<ul>
<li>코틀린 배열 → 타입 파라미터를 받는 클래스<ul>
<li>원소의 타입은 타입 파라미터에 의해 결정됨</li>
</ul>
</li>
</ul>
<h3 id="코틀린에서-배열을-만드는-방법">코틀린에서 배열을 만드는 방법</h3>
<ol>
<li>arrayOf 함수 : 인자로 받은 원소들을 포함해 배열을 만듬</li>
<li>arrayOfNulls : 모든 원소가 null인 정해진 크기의 배열을 만듬</li>
<li>Array 생성자 : 배열 크기와 람다를 인자로 받아 람다를 호출해 각 배열 원소를 초기화 함</li>
</ol>
<h3 id="array-생성자-예제">Array 생성자 예제</h3>
<pre><code class="language-kotlin">fun main() {
    val letters = Array&lt;String&gt;(26) { i -&gt; (&#39;a&#39; + i).toString() }
    println(letters.joinToString(&quot;&quot;))
    // abcdefghijklmnopqrstuvwxyz
}</code></pre>
<ul>
<li>타입 인자를 생략해도 컴파일러가 알아서 원소 타입을 추론해줌</li>
</ul>
<h3 id="컬렉션을-배열로-변환-totypedarray-메서드">컬렉션을 배열로 변환 (toTypedArray 메서드)</h3>
<pre><code class="language-kotlin">fun main() {
    val strings = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
    println(&quot;%s/%s/%s&quot;.format(*strings.toTypedArray())) 
    // vararg 인자를 넘기기 위해 스프레드 연산자 사용
    // a/b/c
}</code></pre>
<h3 id="원시-타입의-배열">원시 타입의 배열</h3>
<ul>
<li>원시 타입의 배열을 표현하는 별도 클래스를 각 원시 타입마다 하나씩 제공<ul>
<li>IntArray, ByteArray, CharArray, BooleanArray 등…</li>
<li>자바 원시 타입 배열인 int[], byte[], char[] 등으로 컴파일 됨</li>
</ul>
</li>
</ul>
<h3 id="원시-타입-배열을-만드는-방법">원시 타입 배열을 만드는 방법</h3>
<ol>
<li><p>각 배영 타입의 생성자 : size를 인자로 받아 해당 원시 타입의 기본값으로 초기화된 size 크기의 배열을 반환</p>
<pre><code class="language-kotlin"> val fiveZeros = IntArray(5)</code></pre>
</li>
<li><p>팩토리 함수 : 여러 값을 가변 인자로 받아 그 값이 들어간 배열을 반환</p>
<pre><code class="language-kotlin"> val fiveZeros = intArrayOf(0, 0, 0, 0, 0)</code></pre>
</li>
<li><p>크기와 람다를 인자로 받는 생성자</p>
<pre><code class="language-kotlin"> fun main() {
         val squares = IntArray(5) { i -&gt; (i + 1) * (i + 1) }
         println(squares.joinToString())
         // 1, 4, 9, 16, 25
 }</code></pre>
</li>
</ol>
<h3 id="원시-타입-배열-과-컬렉션-확장-함수">원시 타입 배열 과 컬렉션 확장 함수</h3>
<ul>
<li>코틀린 표준 라이브러리 → 배열 기본 연산 + 컬렉션 확장 함수 제공<ul>
<li>filter, map 등을 배열에 써도 잘 작동함</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    args.forEachIndexed { index, element -&gt; 
        println(&quot;Argument $index is : $element&quot;)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[원시 타입과 기본 타입]]></title>
            <link>https://velog.io/@first_woosun/%EC%9B%90%EC%8B%9C-%ED%83%80%EC%9E%85%EA%B3%BC-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@first_woosun/%EC%9B%90%EC%8B%9C-%ED%83%80%EC%9E%85%EA%B3%BC-%EA%B8%B0%EB%B3%B8-%ED%83%80%EC%9E%85</guid>
            <pubDate>Thu, 12 Feb 2026 12:01:16 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<ul>
<li>코틀린 → 원시 타입과 래퍼 타입을 구분하지 않음<ul>
<li>그 이유와 작동 원리</li>
</ul>
</li>
<li>Object, void 등의 자바 타입과 코틀린 타입의 대응 관계</li>
</ul>
<hr>
<h2 id="int-float-char-boolean을-원시-타입으로-표현">Int, Float, Char, Boolean을 원시 타입으로 표현</h2>
<h3 id="자바-→-원시-타입과-참조-타입을-구분함">자바 → 원시 타입과 참조 타입을 구분함</h3>
<ul>
<li>원시 타임 → 값이 직접 들어감</li>
<li>참조 타입 → 메모리 상의 객체 위치가 들어감</li>
<li>원시 타입은 값에 대한 메서드를 호출할 수 없음</li>
<li>원시 타입을 래퍼 타입으로 감싸서 참조 타입으로 사용함<ul>
<li>Collection<Integer></li>
</ul>
</li>
</ul>
<h3 id="코틀린-→-원시-타입과-참조-타입을-구분하지-않음">코틀린 → 원시 타입과 참조 타입을 구분하지 않음</h3>
<ul>
<li><p>래퍼 타입을 따로 구분하지 않기 때문에 원시 타입의 값에 대해 메서드를 호출할 수 있음</p>
<pre><code class="language-kotlin">  fun showProgress(progress: Int) {
      val percent = progress.coerceIn(0, 100)
      println(&quot;We&#39;re $percent % done!&quot;)
  }
  fun main() {
      showProgress(146)
      // We&#39;re 100 % done!
  }</code></pre>
<ul>
<li>원시 타입과 참조 타입이 같다고 항상 객체로 표현하는 것은 아님</li>
<li>대부분의 경우(변수, 프로퍼티, 파라미터, 반환 타입 등) 코틀린의 Int는 자바의 int로 컴파일 됨<ul>
<li>컬렉션과 같은 제네릭 클래스는 다름</li>
<li>Int 타입을 타입 파라미터로 넘기면 java.lang.Integer 객체가 들어감</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="자바-원시-타입에-해당하는-코틀린-타입">자바 원시 타입에 해당하는 코틀린 타입</h3>
<ul>
<li>정수 타입 : Byte, Short, Int, Long</li>
<li>부동소수점 숫자 타입 : Float, Double</li>
<li>문자 타입 : Char</li>
<li>불리언 타입 : Boolean</li>
</ul>
<hr>
<h2 id="양수-표현을-위한-모든-비트-범위-사용--부호-없는-숫자-타입">양수 표현을 위한 모든 비트 범위 사용 : 부호 없는 숫자 타입</h2>
<p>코틀린은 JVM의 일반적인 원시 타입을 확장해 부호 없는 타입을 제공</p>
<table>
<thead>
<tr>
<th>타입</th>
<th>크기</th>
<th>값 범위</th>
</tr>
</thead>
<tbody><tr>
<td>UByte</td>
<td>8비트</td>
<td>0 ~ 255</td>
</tr>
<tr>
<td>UShort</td>
<td>16비트</td>
<td>0 ~ 65535</td>
</tr>
<tr>
<td>UInt</td>
<td>32비트</td>
<td>0 ~ 2^32  - 1</td>
</tr>
<tr>
<td>ULong</td>
<td>64비트</td>
<td>0 ~ 2^64 - 1</td>
</tr>
<tr>
<td>- 상응하는 부호 있는 타입의 범위를 시프트</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 같은 메모리 크기를 사용해 더 넓은 양수 범위를 표현</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- Int = -20억 ~ 20억</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- UInt = 0 ~ 40억</td>
<td></td>
<td></td>
</tr>
<tr>
<td>- 부호 없는 수도 필요할 때만 래핑됨</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="null이-될-수-있는-기본-타입">null이 될 수 있는 기본 타입</h2>
<h3 id="코틀린의-null이-될-수-있는-타입-→-자바-원시-타입으로-표현할-수-없음">코틀린의 null이 될 수 있는 타입 → 자바 원시 타입으로 표현할 수 없음</h3>
<ul>
<li>null 참조를 자바의 참조 타입에만 대입할 수 있음</li>
<li>null이 될 수 있는 원시 타입 → 자바의 래퍼 타입으로 컴파일</li>
</ul>
<pre><code class="language-kotlin">data class Person(
    val name: String,
    val age: Int? = null
) {
    fun isOlderThan(other: Person): Boolean? {
        if(age == null || other.age == null)
            return null
        return age &gt; other.age
    }
}

fun main() {
    println(Person(&quot;sam&quot;, 35).isOlderThan(Person(&quot;Amy&quot;, 42)))
    // false
    println(Person(&quot;sam&quot;, 35).isOlderThan(Person(&quot;Jane&quot;)))
    // null
}</code></pre>
<ul>
<li>Int? 타입의 두 값은 null이 될 가능성이 있으므로 두 값을 직접 비교할 수 없음<ul>
<li>두 값에 대한 null 검사를 진행해야 함</li>
</ul>
</li>
<li>Person 클래스에 선언된 age → java.lang.Integer로 저장됨<ul>
<li>자바에서 가져온 클래스를 다룰 때 필요함</li>
<li>코틀린에선 변수나 프로퍼티에 null이 들어갈 수 있는지만 고려하면 됨</li>
</ul>
</li>
</ul>
<h3 id="제네릭-클래스-→-래퍼-타입을-사용함">제네릭 클래스 → 래퍼 타입을 사용함</h3>
<ul>
<li>클래스의 타입 인자로 원시 타입을 넘기면 코틀린은 그 타입에 대한 박스 타입을 사용</li>
<li>val listOfInts = listOf(1, 2, 3)<ul>
<li>null 값이나 null이 될 수 있는 타입을 사용하지 않았지만 Integer 래퍼 타입으로 컴파일 됨</li>
</ul>
</li>
<li>JVM은 타입 인자로 원시 타입을 허용하지 않음</li>
<li>자바나 코틀린 모두 박스 타입을 사용해야 함</li>
</ul>
<hr>
<h2 id="원시-타입-수-변환">원시 타입 수 변환</h2>
<p>코틀린 → 자동 변환이 안됨</p>
<pre><code class="language-kotlin">val i = 1
val l: Long = i
// ERROR: type missmatch 오류 발생</code></pre>
<p>직접 변환 메서드를 호출해야 함</p>
<pre><code class="language-kotlin">val i = 1
val l: Long = i.toLong()</code></pre>
<ul>
<li>코틀린은 Boolean을 제외한 원시타입에 변환 함수를 제공함</li>
</ul>
<h3 id="두-박스-타입-비교">두 박스 타입 비교</h3>
<ul>
<li><p>두 박스 타입 간의 equals 메서드 → 박스 타입의 객체를 비교함</p>
<ul>
<li><p>타입을 명시적으로 변환하여 같은 타입끼리 비교해야 함</p>
<pre><code class="language-kotlin">fun main() {
  val x = 1
  val list = listOf(1L, 2L, 3L)

  println(x.toLong() in list)
  // true
}</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="숫자-리터럴">숫자 리터럴</h3>
<ul>
<li>숫자 리터럴에 대해선 따로 변환 작업을 하지 않아도 됨</li>
<li>컴파일러가 필요한 변환을 자동으로 넣어줌</li>
<li>산술 연산자는 적당한 타입의 값을 받도록 이미 오버라이드 되어 있음</li>
</ul>
<pre><code class="language-kotlin">fun printALong(l: Long) = println(l)

fun main() {
    val b: Byte = 1
    val l = b + 1L // +는 Byte와 Long을 인자로 받을 수 있음
    printALong(42) // 컴파일러는 42를 Long으로 해석함
}</code></pre>
<h3 id="오버플로우">오버플로우</h3>
<ul>
<li>코틀린에서도 산술연산에 대한 오버플로우가 발생할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun main() {
    println(Int.MAX_VALUE + 1)
    // -2147483648
    println(Int.MIN_VALUE - 1)
    // 2147483647
}</code></pre>
<h3 id="문자열-변환-toboolean">문자열 변환 (toBoolean())</h3>
<ul>
<li><p>인자로 받은 문자열이 null이 아니고 문자열의 내용이 true와 같으면 true를 반환</p>
<ul>
<li><p>문자열 내용은 대소문자를 구분하지 않음</p>
<pre><code class="language-kotlin">fun main() {
      println(&quot;trUE&quot;.toBoolean())
      // true
      println(&quot;7&quot;.toBoolean())
      // false
      println(null.toBoolean())
      // false
}
</code></pre>
</li>
</ul>
</li>
<li><p>toBooleanStrict()를 사용하면 true, false 이외에는 전부 false를 반환한다.</p>
</li>
</ul>
<hr>
<h2 id="any와-any--코틀린-타입-계층의-뿌리">Any와 Any? : 코틀린 타입 계층의 뿌리</h2>
<h3 id="자바와-코틀린의-최상위-타입">자바와 코틀린의 최상위 타입</h3>
<ul>
<li><p>자바 → object라는 최상위 클래스가 있음</p>
<ul>
<li>참조 타입은 object 클래스를 참조하지만 원시 타입은 object 클래스를 참조하지 않음</li>
<li>원시 타입에서 Object 타입 객체가 필요한 경우 래퍼 타입으로 감싸야 함</li>
</ul>
</li>
<li><p>코틀린 → Any라는 최상위 클래스가 있음</p>
<ul>
<li><p>Any가 원시 타입을 포함한 모든 타입의 부모 클래스임</p>
</li>
<li><p>원시 타입의 값을 Any 타입의 변수에 저장하면 자동으로 값을 객체로 감쌈(boxing)</p>
<pre><code class="language-kotlin">  val answer: Any = 42 // 42가 박싱됨</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="any-타입의-null-가능성">Any 타입의 null 가능성</h3>
<ul>
<li>Any는 기본적으로 null이 될 수 없는 타입</li>
<li>Any 타입에 null을 저장하려면 물음표( ? )를 붙여야 함</li>
</ul>
<h3 id="any-타입과-object-타입">Any 타입과 Object 타입</h3>
<ul>
<li>Any 타입은 java.lang.Object에 대응함<ul>
<li>자바에서 Object를 인자로 받거나 반환하면 코틀린에선 Any 타입으로 취급됨</li>
</ul>
</li>
<li>코틀린에서 Any를 사용하면 자바 바이트코드의 Object로 컴파일 됨</li>
</ul>
<h3 id="코틀린-클래스의-기본-함수">코틀린 클래스의 기본 함수</h3>
<ul>
<li>toString(), equals(), hashCode() 메서드는 Any 클래스에 정의된 메서드를 상속한 것</li>
<li>java.lang.Object에 정의된 다른 메서드는 Any 클래스에서 사용할 수 없음<ul>
<li>java.lang.Object에 정의된 메서드를 사용하려면 java.lang.Object타입으로 캐스트 해야 함</li>
</ul>
</li>
</ul>
<hr>
<h2 id="unit-타입-코틀린의-void">Unit 타입: 코틀린의 void</h2>
<ul>
<li><p>Unit타입 → 자바의 void와 같은 역할</p>
</li>
<li><p>아무 값도 반환하지 않는 함수에 사용할 수 있음</p>
<pre><code class="language-kotlin">  fun f(): Unit { /*...*/ }</code></pre>
</li>
<li><p>문법적으로 아무 반환 타입도 선언하지 않은 함수와 같음</p>
<pre><code class="language-kotlin">  fun f() { /*...*/ }</code></pre>
</li>
<li><p>코틀린 함수의 반환타입이 Unit이고 그 함수가 제네릭 함수를 오버라이드하지 않는다면 자바 void 함수로 컴파일 됨</p>
</li>
</ul>
<h3 id="unit과-void의-차이">Unit과 void의 차이</h3>
<ul>
<li>Unit → 모든 기능을 갖는 일반적인 타입</li>
<li>void와 달리 Unit을 타입 인자로 쓸 수 있음</li>
</ul>
<h3 id="제네릭-파라미터-반환-함수-오버라이드">제네릭 파라미터 반환 함수 오버라이드</h3>
<ul>
<li>Unit 타입에 속한 값은 단 하나이며 그 이름도 Unit임</li>
<li>Unit 타입의 함수는 Unit 값을 암시적으로 반환</li>
</ul>
<pre><code class="language-kotlin">interface Processor&lt;T&gt; {
    fun process(): T
}

class NoResultProcessor : Processor&lt;Unit&gt; {
    override fun process() { // Unit을 반환하지만 타입을 지정하지 않아도 됨
        // 처리 코드
    } // 명시적으로 return해 줄 필요 없음
}</code></pre>
<h3 id="자바에서의-값-없음">자바에서의 ‘값 없음’</h3>
<ul>
<li>자바에서는 값 없음에 대해 처리해줘야 한다는 점이 코틀린과의 차이점임</li>
</ul>
<h4 id="처리-방법">처리 방법</h4>
<ol>
<li>별도의 인터페이스를 통해 값을 반환하는 경우와 반환하지 않는 경우를 분리</li>
<li>타입 파라미터로 java.lang.Void 타입을 사용<ul>
<li>void 타입에 대응하는 null값을 반환하기 위해 return null을 명시해줘야 함</li>
<li>반환 타입이 void가 아니므로 명시적으로 처리</li>
</ul>
</li>
</ol>
<h3 id="코틀린에서-void가-아닌-unit이라는-이름을-채택한-이유">코틀린에서 void가 아닌 Unit이라는 이름을 채택한 이유</h3>
<ul>
<li>함수형 프로그래밍에서 Unit → 단 하나의 인스턴스만 갖는 타입<ul>
<li>이 인스턴스의 유무가 void 타입과의 가장 큰 차이점</li>
</ul>
</li>
</ul>
<hr>
<h2 id="nothing-타입">Nothing 타입</h2>
<ul>
<li>반환값 자체가 필요 없는 함수에서 사용</li>
</ul>
<pre><code class="language-kotlin">fun fail(message: String) : Nothing {
    throw IllegalStateException (message)
}

fun main() {
    fail(&quot;Error occurred&quot;)
    // java.lang.IllegalStateException: Error occurred
}</code></pre>
<ul>
<li><p>Nothing 타입 → 아무 값도 포함하지 않음</p>
<ul>
<li>함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있음</li>
</ul>
</li>
<li><p>Nothing 타입으로 변수를 선언해도 그 변수에 아무 값도 저장할 수 없음</p>
</li>
<li><p>엘비스 연산자의 오른쪽에 사용할 수 있음</p>
<pre><code class="language-kotlin">  val address = company.address ?: fail(&quot;No address&quot;)
  println(address.city)</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[null 가능성과 자바]]></title>
            <link>https://velog.io/@first_woosun/null-%EA%B0%80%EB%8A%A5%EC%84%B1%EA%B3%BC-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@first_woosun/null-%EA%B0%80%EB%8A%A5%EC%84%B1%EA%B3%BC-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 11 Feb 2026 14:20:52 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<ul>
<li>자바의 타입 시스템은 코틀린의 null 가능성을 지원하지 않음</li>
<li>모든 값을 쓸 때마다 null 검사를 해줘야 하는가?</li>
</ul>
<hr>
<h2 id="어노테이션을-통한-null-가능성">어노테이션을 통한 null 가능성</h2>
<ul>
<li>@Nullable 어노테이션이 붙은 코드 == 코틀린의 null이 될 수 있는 타입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/first_woosun/post/b2db22d1-e471-43d9-b94b-49911aff14d9/image.jpg" alt=""></p>
<p>널 가능성 어노테이션이 없는 경우 자바의 타입은 코틀린의 플랫폼 타입이 됨</p>
<hr>
<h2 id="플랫폼-타입">플랫폼 타입</h2>
<ul>
<li>코틀린의 null 관련 정보를 알 수 없는 타입<ul>
<li>null이 될 수 있는 타입으로 처리해도 되고</li>
<li>null이 될 수 없는 타입으로 처리해도 됨</li>
</ul>
</li>
<li>플랫폼 타입을 사용할 경우 null 가능성에 대한 처리를 사용자가 판단해야 함</li>
</ul>
<pre><code class="language-java">public class Person {
    private final String name;

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

    public String getName() {
        return this.name;
    }
}</code></pre>
<ul>
<li>코틀린 컴파일러는 getName에 대한 String null 가능성을 알 수 없음<ul>
<li>null 가능성을 직접 처리해야 함</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun yellAt(person: Person){
    println(person.name.uppercase()+ &quot;!!!&quot;)
}

fun main() {
    yellAt(Person(null))
    // java.lang.NullPointException: person.name must not be null
}</code></pre>
<ul>
<li>getName()의 반환 타입을 null이 될 수 있는 타입으로 해석해 null 안전성 연산을 활용할 수 있음</li>
</ul>
<pre><code class="language-kotlin">fun yellAt(person: Person){
    println((person.name ?: &quot;Anyone&quot;).uppercase()+ &quot;!!!&quot;)
}

fun main() {
    yellAt(Person(null))
    // ANYONE!!!
}</code></pre>
<ul>
<li>null 값에 대한 엘비스 연산 제공 → 예외가 발생하지 않음</li>
</ul>
<h3 id="대부분의-자바-라이브러리-→-null-관련-어노테이션을-쓰지-않음">대부분의 자바 라이브러리 → null 관련 어노테이션을 쓰지 않음</h3>
<ul>
<li>오든 타입을 null이 아닌 것처럼 다룰 수 있지만 오류가 발생할 수 있음</li>
</ul>
<blockquote>
</blockquote>
<h4 id="📌코틀린의-플랫폼-타입-도입-이유">📌코틀린의 플랫폼 타입 도입 이유</h4>
<ul>
<li>모든 자바 타입을 null이 될 수 있는 타입으로 다루면 불필요한 null 검사가 많아짐</li>
<li>리스트를 다룰 경우 각 원소에 접근할 때마다 null 검사 또는 안전한 캐스트를 수행해야 해 검사에 드는 비용이 너무 큼</li>
<li>모든 타입에 대한 null 검사를 작성하는건 비효율적임<blockquote>
</blockquote>
</li>
</ul>
<h3 id="코틀린에서-플랫폼-타입을-선언할-수-없음">코틀린에서 플랫폼 타입을 선언할 수 없음</h3>
<ul>
<li>자바 코드에서 가져온 타입만 플랫폼 타입이 됨</li>
<li>플랫폼 타입은 타입명 뒤에 느낌표 ( ! )가 표시됨<ul>
<li>String!</li>
</ul>
</li>
</ul>
<hr>
<h2 id="상속">상속</h2>
<ul>
<li>코틀린에서 자바 메서드를 오버라이드 할 때 그 메서드의 파라미터와 반환 타입의 null 가능성을 지정해줘야 함</li>
</ul>
<pre><code class="language-java">interface StringProcessor {
    void process(String value);
}</code></pre>
<p>위 자바 인터페이스에 대해 코틀린 컴파일러는 두 구현을 다 받아들임</p>
<pre><code class="language-java">class StringPrinter: StringProcessor {
    override fun process(value: String) {
        println(value)
    }
}

class NullableStringPrinter: StringProcessor {
    override fun process(value: String?) {
        if(value != null)
            println(value)
    }
}</code></pre>
<ul>
<li>null 가능성을 제대로 처리하는 것이 중요함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[null이 될 수 있는 타입 다루기 (2)]]></title>
            <link>https://velog.io/@first_woosun/null%EC%9D%B4-%EB%90%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%ED%83%80%EC%9E%85-%EB%8B%A4%EB%A3%A8%EA%B8%B0-2</link>
            <guid>https://velog.io/@first_woosun/null%EC%9D%B4-%EB%90%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%ED%83%80%EC%9E%85-%EB%8B%A4%EB%A3%A8%EA%B8%B0-2</guid>
            <pubDate>Wed, 11 Feb 2026 14:18:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-지연-초기화-프로퍼티">1. 지연 초기화 프로퍼티</h1>
<ul>
<li>객체를 일단 생성한 후 나중에 초기화하는 프레임워크들이 많음<ul>
<li>안드로이드, JUnit 등등…</li>
</ul>
</li>
<li>코틀린에는 클래스안의 null이 될 수 없는 프로퍼티를 생성자가 아닌 특별한 메서드에서 초기화할 방법이 없음</li>
<li>프로퍼티를 null이 될 수 있는 타입으로 선언하면 모든 프로퍼티 접근에 null 검사를 넣거나 단언문( !! )을 사용해야 함</li>
</ul>
<pre><code class="language-kotlin">class MyService() {
    fun performAction(): String = &quot;Action Done!&quot;
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {
    private var myService: MyService? = null // null 사용을 위해 null이 될 수 있는 타입으로 선언

    @BeforeAll fun setUp(){
        myService = MyService() // 메서드안에서 초기화
    }

    @Test fun testAction(){
        assertEquals(&quot;Action Done!&quot;, myService!!.performAction()) // null 가능성을 검증해야함
    }
}</code></pre>
<h2 id="지연-초기화--lateinit">지연 초기화 : lateinit</h2>
<p>lateinit 변경자를 통해 프로퍼티를 나중에 초기화할 수 있음</p>
<pre><code class="language-kotlin">class MyService() {
    fun performAction(): String = &quot;Action Done!&quot;
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {
    private lateinit var myService: MyService

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

    @Test fun testAction(){
        assertEquals(&quot;Action Done!&quot;, myService.performAction())
    }
}</code></pre>
<ul>
<li><p>val 프로퍼티로 선언하면 파이널 필드로 컴파일 되어 생성자 안에서 변경해줘야 함</p>
<ul>
<li>지연 초기화 프로퍼티는 항상 <strong>var</strong>로 선언 되어야 함</li>
</ul>
</li>
<li><p>지연 초기화 프로퍼티는 null이 될 수 없는 타입이라고 해도 생성자 안에서 초기화 할 필요가 없음</p>
</li>
<li><p>초기화 전에 지연 초기화 프로퍼티에 접근하면 다음과 같은 오류가 발생함</p>
<pre><code class="language-bash">  kotiln.UninitializedPropertyAccessException: 
          latainit property myService has not been initialized</code></pre>
<ul>
<li>단순히 NPE가 발생하는 것보다 문제를 파악하기 더 좋음</li>
</ul>
</li>
</ul>
<h2 id="자바-의존관계-주입-프레임워크와의-사용">자바 의존관계 주입 프레임워크와의 사용</h2>
<ul>
<li>자바 프레임워크와의 호환성을 위해 lateinit이 지정된 프로퍼티와 가시성이 똑같은 필드를 생성<ul>
<li>지연 초기화 프로퍼티가 public이라면 코틀린이 생성한 필드도 public</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<p>📌 지역 변수와 최상위 프로퍼티도 지연 초기화 가눙</p>
<blockquote>
</blockquote>
<hr>
<h1 id="2-null이-될-수-있는-타입에-대한-확장">2. null이 될 수 있는 타입에 대한 확장</h1>
<p>null이 될 수 있는 타입에 대한 확장 함수를 정의하면 null 값을 다루는 강력한 도구가 될 수 있음</p>
<ul>
<li><p>메서드 호출이 null을 수신 객체로 받고 내부에서 null을 처리할게 할 수 있음</p>
</li>
<li><p>String? 클래스의 isEmptyOrNull 이나 isBlankOrNull이 null이 될 수 있는 타입에 대한 확장임</p>
<pre><code class="language-kotlin">  fun verifyUserInput(input: String?) {
      if (input.isNullOrBlank()) // 안전한 호출이 필요 없음
          println(&quot;Please fill in the required fields&quot;)
  }

  fun main() {
      verifyUserInput(&quot; &quot;)
      // Please fill in the required fields
      verifyUserInput(null)
      // Please fill in the required fields
  }</code></pre>
<ul>
<li><p>안전한 호출이 없어도 확장 함수 호출이 가능함</p>
</li>
<li><p>isNullOrBlank → 입력값이 null인 경우 true를 반환하고 null이 아닐 경우 isBlank를 호출함</p>
<pre><code class="language-kotlin">  fun String?.isNullOrBlank(): Boolean =
          this == null || this.isBlank()</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="null이-될-수-있는-타입에-대한-확장을-정의하면-null이-될-수-있는-값에-대해-그-확장-함수를-호출할-수-있음">null이 될 수 있는 타입에 대한 확장을 정의하면 null이 될 수 있는 값에 대해 그 확장 함수를 호출할 수 있음</h3>
<ul>
<li>코틀린에서는 null이 될 수 있는 타입의 확장 함수 안에서 this가 null이 될 수 있음</li>
</ul>
<h3 id="let-함수도-null이-될-수-있는-타입의-값에-호출할-수-있지만-this가-null인지-검사하지-않음">let 함수도 null이 될 수 있는 타입의 값에 호출할 수 있지만 this가 null인지 검사하지 않음</h3>
<ul>
<li><p>안전한 호출을 사용하지 않고 let을 호출하면 람다의 인자는 null이 될 수 있는 값으로 추론됨</p>
<pre><code class="language-kotlin">  fun sendMailTo(email: String) {
      println(&quot;Sending email to $email&quot;)
  }

  fun main() {
      val recipient: String? = null
      recipient.let { sendMailTo(it) } // 안전한 호출을 사용하지 않으면 it은 null이 될 수 있는 타입으로 추론됨
  }

  //type mismatch: actual type is &#39;String?&#39;, but &#39;String&#39; was expected.</code></pre>
<ul>
<li>수신 객체가 null인지 검사하고 싶다면 안전한 호출을 사용해야 함</li>
</ul>
</li>
</ul>
<hr>
<h1 id="3-타입-파라미터의-null-가능성">3. 타입 파라미터의 null 가능성</h1>
<h3 id="코틀린에서-함수나-프로퍼티-→-기본적으로-null이-될-수-있음">코틀린에서 함수나 프로퍼티 → 기본적으로 null이 될 수 있음</h3>
<ul>
<li><p>null이 될 수 있는 타입을 포함한 모든 타입이 타입 파라미터를 대신할 수 있음</p>
</li>
<li><p>타입 파라미터를 타입 이름으로 사용하면 물음표가 없더라도 null이 될 수 있는 타입임</p>
<pre><code class="language-kotlin">  fun &lt;T&gt; printHashCode(t: T) {
      println(t?.hashCode())
  }

  fun main() {
      printHashCode(null)
      // null
  }</code></pre>
<ul>
<li>타입 파라미터 T에 대해 추론한 타입은 null이 될 수 있는 Any? 타입임</li>
<li>타입 파라미터에 물음표가 붙어있지 않지만 null을 받을 수 있음</li>
</ul>
</li>
</ul>
<h3 id="타입-파라미터가-null이-아님을-확실히-하려면-타입-상계upper-bound를-지정해줘야-함">타입 파라미터가 null이 아님을 확실히 하려면 타입 상계(upper bound)를 지정해줘야 함</h3>
<pre><code class="language-kotlin">fun &lt;T: Any&gt; printHashCode(t: T) {
    println(t.hashCode())
}

fun main() {
    printHashCode(null)
    printHashCode(42)
}</code></pre>
<ul>
<li>null이 될 수 없는 타입 상계를 지정하면 null이 될 수 있는 값을 거부함</li>
<li>위 코드는 컴파일 되지 않음<ul>
<li>null이 될 수 없는 타입의 파라미터에 null을 넘겼기 때문</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>