코틀린 문법 정리
Java에서 코틀린으로 넘어오는 서비스들이 많죠. 코틀린을 보면 가장 먼저 느낀 건 간결함이었어요. 자바에 비해 같은 로직을 절반도 안 되는 코드로 표현할 수 있다는 게 꽤 신선했거든요. 이 글에서는 실무에서 자주 쓰는 코틀린 문법을 정리해볼게요. Null Safety부터 코루틴까지, 핵심만 다뤄봐요. 변수와 타입 추론 코틀린의 변수 선언은 두 가지예요. val은 불변, var는 가변이에요. val name: String = "홍길동" val age = 30 // 타입 추론으로 Int var count: Int = 0 count = 1 const val MAX_SIZE = 100 // 컴파일 타임 상수 Java에서 final을 매번 붙이던 것과 달리, 코틀린에서는 val이 기본이라 자연스럽게 불변을 지향하게 돼요. 문자열 처리도 훨씬 깔끔해요. 문자열 템플릿과 raw string을 쓰면 StringBuilder를 꺼낼 일이 거의 없어요. val json = """ { "name": "$name", "age": $age } """.trimIndent() Null Safety 코틀린을 쓰는 가장 큰 이유 중 하나예요. 모든 타입이 기본적으로 non-null이기 때문에 NullPointerException을 컴파일 타임에 잡아줘요. var nickname: String? = null // ?를 붙여야 null 허용 nickname?.length // Safe call: null이면 null 반환 nickname!!.length // Non-null assertion: null이면 크래시 val len = nickname?.length ?: 0 // Elvis 연산자: null일 때 기본값 실무에서는 let과 Elvis 연산자를 조합하는 패턴을 가장 많이 써요. nickname?.let { println("닉네임: $it") } fun process(name: String?) { val n = name ?: return // null이면 조기 반환 println(n.uppercase()) } !! 연산자는 테스트 코드 외에는 쓰지 않는 게 좋아요. 결국 NPE를 다시 만들어내는 것과 같거든요. 함수와 확장함수 기본값과 명명 인자 덕분에 Java에서 오버로딩으로 해결하던 문제가 함수 하나로 끝나요. fun greet(name: String = "Guest", age: Int = 0) = "Hello, $name ($age세)" greet(age = 30, name = "홍길동") // 순서 무시 가능 확장함수는 기존 클래스를 수정하지 않고 메서드를 추가하는 기능이에요. 유틸리티 함수를 만들 때 특히 유용해요. fun String.toSlug() = this.lowercase().replace(" ", "-").replace(Regex("[^a-z0-9-]"), "") "Hello World".toSlug() // "hello-world" 클래스: Data Class와 Sealed Class Data Class DTO나 모델 객체를 만들 때 equals(), hashCode(), toString(), copy()를 자동 생성해줘요. Java의 boilerplate를 완전히 없앨 수 있어요. data class User( val id: Long, val name: String, val email: String ) val user2 = user1.copy(name = "김철수") // 일부 필드만 변경한 복사본 Sealed Class 상태 관리에 특화된 클래스예요. when 표현식과 함께 쓰면 모든 경우를 컴파일러가 체크해주기 때문에 else 분기가 필요 없어요. sealed class Result&x3C;out T> { data class Success&x3C;T>(val data: T) : Result&x3C;T>() data class Error(val message: String) : Result&x3C;Nothing>() object Loading : Result&x3C;Nothing>() } fun handleResult(result: Result&x3C;String>) = when (result) { is Result.Success -> "성공: ${result.data}" is Result.Error -> "에러: ${result.message}" Result.Loading -> "로딩 중..." } 새로운 상태를 추가하면 when을 쓰는 모든 곳에서 컴파일 에러가 발생하므로, 누락 없이 처리할 수 있어요. 제어문은 표현식 코틀린에서는 if와 when 모두 값을 반환하는 표현식이에요. val max = if (a > b) a else b val message = when (status) { "PENDING" -> "대기중" "CONFIRMED" -> "확정" in listOf("CANCELLED", "FAILED") -> "실패" else -> "알수없음" } Java의 삼항 연산자(? :)가 없는 대신 if 자체가 그 역할을 해요. when은 switch보다 훨씬 유연해서, 범위 검사나 타입 검사도 가능하고요. 컬렉션 함수형 처리 코틀린의 컬렉션 API는 Java의 Stream보다 간결해요. val numbers = listOf(1, 2, 3, 4, 5) numbers.filter { it > 2 } // [3, 4, 5] numbers.map { it * 2 } // [2, 4, 6, 8, 10] numbers.groupBy { it % 2 == 0 } // {false=[1,3,5], true=[2,4]} numbers.chunked(2) // [[1,2], [3,4], [5]] .stream().collect(Collectors.toList()) 같은 코드를 안 써도 돼요. 스코프 함수 다섯 가지 스코프 함수는 처음엔 헷갈리지만, 핵심은 참조 방식(it vs this)과 반환값(람다 결과 vs 객체 자체)의 조합이에요. 함수 참조 반환 용도 let it 람다 결과 null 체크, 변환 run this 람다 결과 설정 후 계산 with this 람다 결과 여러 메서드 호출 apply this 객체 자체 객체 초기화 also it 객체 자체 로깅, 부가 작업 실무에서 가장 많이 쓰는 조합은 apply + also예요. val user = User().apply { name = "홍길동" age = 30 }.also { log("유저 생성: $it") } apply로 객체를 초기화하고, also로 사이드 이펙트(로깅 등)를 처리하는 패턴이에요. 코루틴 코틀린의 비동기 처리는 코루틴이 담당해요. RxJava보다 학습 곡선이 낮고, 코드가 동기식처럼 읽혀요. 기본 API 호출 viewModelScope.launch { _loading.value = true _user.value = repository.getUser(id) _loading.value = false } 병렬 호출 순차 실행이면 3초 걸리는 작업을, async로 병렬 처리하면 1초로 줄일 수 있어요. coroutineScope { val user = async { fetchUser() } val posts = async { fetchPosts() } val comments = async { fetchComments() } Triple(user.await(), posts.await(), comments.await()) } Dispatcher로 스레드 전환 viewModelScope.launch { showLoading() // Main 스레드 val user = withContext(Dispatchers.IO) { api.getUser(id) // IO 스레드 } showUser(user) // 다시 Main 스레드 } withContext로 스레드를 전환해도 콜백 지옥 없이 위에서 아래로 읽을 수 있어요. Java 상호운용 기존 Java 코드와의 호환이 필요하다면 @JvmStatic, @JvmField 같은 어노테이션을 활용하면 돼요. class Utils { companion object { @JvmStatic fun helper() { } } } // Java에서 Utils.helper()로 호출 가능 정리 코틀린은 결국 안전하고 간결한 코드를 지향하는 언어예요. 이 글에서 다룬 내용 중 실무에서 가장 체감이 큰 건 다음 세 가지였어요. Null Safety - 런타임 크래시를 컴파일 타임으로 끌어올려줘요 Sealed Class + when - 상태 누락 없는 분기 처리가 가능해요 코루틴 - 콜백 없이 비동기 코드를 작성할 수 있어요 더 깊이 파고 싶다면 Kotlin Koans로 연습해보세요. We learn what we have said from those who listen to our speaking.— Kenneth Patton