Kotlin를 실무 흐름으로 이해하기
JetBrains가 만들고 Android 공식 언어로 채택된 Kotlin을 학습합니다. Null 안전성, 간결한 문법, 코루틴 기반 동시성까지 실무 수준으로 다룹니다. 이 가이드는 개념을 나열하기보다, 실제 프로젝트에서 판단해야 하는 순서대로 내용을 따라갈 수 있게 구성했습니다.
JetBrains가 만들고 Android 공식 언어로 채택된 Kotlin을 학습합니다. Null 안전성, 간결한 문법, 코루틴 기반 동시성까지 실무 수준으로 다룹니다.
JetBrains가 만들고 Android 공식 언어로 채택된 Kotlin을 학습합니다. Null 안전성, 간결한 문법, 코루틴 기반 동시성까지 실무 수준으로 다룹니다. 이 가이드는 개념을 나열하기보다, 실제 프로젝트에서 판단해야 하는 순서대로 내용을 따라갈 수 있게 구성했습니다.
화면을 그리는 법에서 멈추지 않고, 상태, 데이터 요청, 라우팅, 접근성, 배포 단위까지 함께 봅니다.
글로 읽은 내용을 머릿속에 오래 남기려면 먼저 흐름을 그림으로 잡는 편이 좋습니다. 아래 두 그림은 Kotlin를 학습할 때 계속 되돌아볼 수 있는 기준 지도입니다.
Kotlin를 처음 펼칠 때는 세부 명령보다 큰 그림이 먼저입니다. 이 섹션에서는 앞으로 배울 개념들이 어떤 문제를 풀기 위해 등장했는지부터 잡아봅니다.
# SDKMAN으로 JDK + Kotlin 설치 (macOS/Linux)
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 17.0.9-tem
sdk install kotlin
# 설치 확인
kotlin -version
# 컴파일 & 실행
kotlinc main.kt -include-runtime -d main.jar
java -jar main.jar여기서는 기본 문법 & Null 안전성을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
fun main() {
val name: String = "Kotlin" // non-null: null 대입 시 컴파일 에러
var nickname: String? = null // nullable: ?를 붙여야 null 허용
// 안전 호출(?.) + 엘비스 연산자(?:)
val len = nickname?.length ?: 0
println("이름: $name, 닉네임 길이: $len")
// let: null이 아닐 때만 블록 실행
nickname?.let { println("닉네임: $it") }
}여기서는 클래스 & data class을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
data class User(val id: Int, val name: String)
sealed class ApiResult
data class Success(val data: String) : ApiResult()
data class Failure(val error: String) : ApiResult()
object Loading : ApiResult()
fun handle(result: ApiResult) = when (result) {
is Success -> println("성공: ${result.data}")
is Failure -> println("실패: ${result.error}")
Loading -> println("로딩 중...")
// sealed class는 모든 하위 타입을 다루면 else 불필요
}여기서는 확장 함수 & Scope Function을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
// 확장 함수 — String 클래스를 건드리지 않고 새 메서드처럼 추가
fun String.isValidEmail(): Boolean =
this.matches(Regex("^[\w.-]+@[\w.-]+\.\w+$"))
if ("user@example.com".isValidEmail()) {
println("유효한 이메일")
}
// apply — 객체 자신(this)을 설정하고 그 객체를 반환 (초기화에 유용)
val user = User(id = 1, name = "").apply {
// 여기선 this가 User이므로 프로퍼티에 직접 접근 (val이면 생성자에서만 가능하니 var 가정)
}
// let — 인자(it)로 받고 람다의 결과를 반환 (null 체크와 자주 결합)
val length = nickname?.let { it.length } ?: 0
// also — 인자(it)로 받고 원본 객체를 그대로 반환 (부수 효과, 로깅에 유용)
val list = mutableListOf(1, 2, 3).also { println("생성됨: $it") }| 함수 | 수신 객체 참조 | 반환값 | 주로 쓰는 곳 |
|---|---|---|---|
| let | it | 람다의 결과 | null이 아닐 때만 실행, 값 변환 |
| run | this | 람다의 결과 | 객체 설정 + 계산 결과가 모두 필요할 때 |
| apply | this | 객체 자신 | 객체 초기화/설정 후 그대로 사용 |
| also | it | 객체 자신 | 로깅 등 원본 흐름에 끼워 넣는 부수 효과 |
여기서는 코루틴 (Coroutines)을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
import kotlinx.coroutines.*
suspend fun fetchUser(id: Int): String {
delay(100)
return "User-$id"
}
fun main() = runBlocking {
val d1 = async { fetchUser(1) }
val d2 = async { fetchUser(2) }
println("${d1.await()}, ${d2.await()}") // 병렬 실행
}여기서는 구조화된 동시성을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
class UserViewModel : ViewModel() {
fun loadUser(id: Int) {
// viewModelScope 안에서 실행된 코루틴은 ViewModel이 정리될 때 자동 취소됨
viewModelScope.launch {
val user = fetchUser(id) // 화면이 먼저 사라지면
updateUi(user) // 이 줄은 실행되지 않음 (안전하게 취소됨)
}
}
}Kotlin 실무 설계은 선택지가 갈리는 지점입니다. 표를 기준으로 각 방법의 쓰임새와 운영상의 차이를 비교해두면 이후 판단이 훨씬 쉬워집니다.
| 결정 지점 | 확인 질문 | 실무 기준 |
|---|---|---|
| 경계 | Kotlin 코드에서 바뀌기 쉬운 부분은 어디인가? | 입출력, 설정, 외부 연동, 핵심 규칙을 분리합니다. |
| 상태 | 상태가 어디서 생성되고 어디서 사라지는가? | 상태 소유자와 수명 주기를 코드로 드러냅니다. |
| 장애 | 실패했을 때 호출자는 무엇을 받는가? | timeout, fallback, error contract를 먼저 정합니다. |
이 섹션은 Kotlin 운영 기준을 실무 관점에서 정리합니다. 개념을 외우기보다, 어떤 상황에서 이 기준을 꺼내 쓸지에 초점을 맞춰보세요.
Kotlin 검증 전략은 선택지가 갈리는 지점입니다. 표를 기준으로 각 방법의 쓰임새와 운영상의 차이를 비교해두면 이후 판단이 훨씬 쉬워집니다.
| 품질 축 | 검증 방법 | 완료 기준 |
|---|---|---|
| 정확성 | 정상/실패 케이스를 자동화합니다. | 핵심 시나리오가 재현 가능하게 통과합니다. |
| 회귀 방지 | 버그 수정 시 동일 케이스를 테스트로 남깁니다. | 같은 장애가 다시 배포되지 않습니다. |
| 운영성 | 로그, 메트릭, 알림을 확인합니다. | 문제가 생겼을 때 원인 추적 경로가 있습니다. |