목차
코틀린을 시작할 때 알아둘 것
코틀린(Kotlin)은 객체지향과 함수형 프로그래밍의 도구를 함께 제공하는 정적 타입 언어다. 변수와 식의 타입을 컴파일러가 확인하므로 서로 맞지 않는 값을 넘기는 실수를 실행 전에 발견할 수 있다. 타입을 매번 적을 필요는 없다. 컴파일러가 오른쪽 값에서 타입을 추론할 수 있기 때문이다.
val name = "Kotlin" // String으로 추론
val version: Int = 2 // 타입을 직접 명시
// val count: Int = "two" // 타입이 맞지 않아 컴파일 오류
정적 타입의 실용적인 장점은 잘못된 호출을 빨리 찾고, IDE가 변수의 가능한 동작을 알려주며, 리팩터링 때 영향을 받는 코드를 추적하기 쉽다는 것이다. 정적 타입이라는 이유만으로 모든 호출이 반드시 빨라지는 것은 아니다. 실제 성능은 생성된 코드와 실행 환경에 따라 달라진다.
val과 var
val은 한 번 정한 참조를 다시 대입할 수 없고, var는 다시 대입할 수 있다. 바뀔 필요가 없는 값은 val로 선언하면 코드의 상태 변화를 줄일 수 있다.
val greeting = "안녕하세요"
// greeting = "반갑습니다" // 재대입할 수 없음
var retryCount = 0
retryCount += 1
여기서 val이 참조 대상까지 항상 불변으로 만드는 것은 아니다. val items = mutableListOf("A")라면 items에 다른 목록을 대입할 수는 없지만, items.add("B")로 목록의 내용은 바꿀 수 있다. 목록 자체를 바꾸지 않을 계획이라면 읽기 전용 인터페이스와 새 값을 반환하는 연산을 우선한다.
null을 타입에 표시하기
기본적인 String은 null을 담을 수 없다. 값이 없을 가능성이 있다면 String?로 표시하고, 사용할 때 그 경우를 처리해야 한다. 이렇게 하면 값의 부재를 호출 지점에서 놓치기 어렵다.
fun displayName(nickname: String?): String {
return nickname?.trim()?.takeIf { it.isNotEmpty() } ?: "익명"
}
println(displayName(" 민수 ")) // 민수
println(displayName(null)) // 익명
?.는 왼쪽 값이 null이면 뒤의 호출을 건너뛰고 null을 돌려준다. ?:는 그때 대신 사용할 값을 정한다. !!는 값이 null이면 예외를 일으키므로, 값이 반드시 있다는 근거가 있을 때만 사용한다.
flowchart LR
A[입력 값] --> B{null인가?}
B -->|아니오| C[값을 사용]
B -->|예| D[기본값·오류 처리]
함수와 컬렉션
함수는 fun으로 정의하고 반환 타입을 매개변수 뒤에 적는다. 한 식으로 계산되는 함수는 = 형태로 줄일 수 있다. 컬렉션에는 filter, map처럼 원본을 직접 바꾸지 않고 결과를 만드는 연산이 있다.
fun double(value: Int): Int = value * 2
val numbers = listOf(1, 2, 3, 4)
val evenDoubles = numbers.filter { it % 2 == 0 }.map(::double)
println(numbers) // [1, 2, 3, 4]
println(evenDoubles) // [4, 8]
처음에는 val과 명확한 타입, null 처리부터 익숙해지는 편이 좋다. 이후 클래스, 확장 함수, 코루틴 등으로 넓혀 가더라도 이 세 가지가 읽기 쉬운 코틀린 코드의 바탕이 된다.
참고: Kotlin 기본 문법, Null 안전성