코틀린 시작하기 — 자바 개발자를 위한 첫걸음
자바와 100% 호환되면서 세미콜론도 없고, getter/setter도 없고, null 처리까지 언어 차원에서 해주는 언어가 있다면 어떨까요?
코틀린 은 JetBrains가 만든 JVM 기반 언어로, 자바의 장점을 유지하면서 간결한 문법과 타입 안전성 을 추가한 언어입니다.
val과 var — 변수 선언의 기본
코틀린에서 변수를 선언하는 방법은 딱 두 가지입니다.
- val — 재할당 불가 (자바의
final과 동일) - var — 재할당 가능
val name = "코틀린" // 재할당 불가
var count = 0 // 재할당 가능
// name = "자바" // 컴파일 에러!
count = 1 // OK
val ≠ 불변
val은 ** 참조의 재할당을 막을 뿐 **, 객체 내부 상태까지 보장하지 않습니다.
val list = mutableListOf(1, 2, 3)
list.add(4) // OK — 리스트 내부 상태 변경
// list = mutableListOf() // 컴파일 에러 — 재할당 불가
val은 자바의
final변수와 동일한 개념입니다. 진짜 불변을 원한다면listOf()처럼 불변 컬렉션을 사용해야 합니다.
타입 추론 — 타입을 안 써도 되는 이유
코틀린 컴파일러는 초기화 값을 보고 타입을 알아서 결정합니다. 이것을 ** 타입 추론(Type Inference)**이라고 합니다.
val message = "Hello" // String으로 추론
val number = 42 // Int로 추론
val pi = 3.14 // Double로 추론
명시적으로 타입을 적어도 됩니다.
val message: String = "Hello"
val number: Int = 42
타입 추론이 안 되는 경우
초기화 없이 선언만 하면 타입을 반드시 명시해야 합니다.
val name: String // 나중에 초기화할 예정
// val name // 컴파일 에러 — 타입을 알 수 없음
타입을 안 쓴다고 동적 타이핑이 아닙니다. 코틀린은 ** 정적 타이핑** 언어이며, 컴파일 타임에 타입이 확정됩니다.
Nullable 타입 — 물음표 하나의 차이
코틀린에서 모든 타입은 기본적으로 null을 허용하지 않습니다. 타입 뒤에 ?를 붙이면 Nullable 타입이 됩니다.
var name: String = "코틀린"
// name = null // 컴파일 에러!
var nullableName: String? = "코틀린"
nullableName = null // OK
이 규칙 하나로 NPE(NullPointerException)를 컴파일 타임에 잡아냅니다.
fun getLength(text: String?): Int {
// return text.length // 컴파일 에러 — null일 수 있음
return text?.length ?: 0 // 안전 호출 + 엘비스 연산자
}
Nullable에 대한 자세한 내용은 별도 글에서 다룹니다.
String Template — 문자열 조합
자바에서 문자열을 조합하려면 + 연산이나 String.format()을 써야 했습니다. 코틀린은 $ 하나로 해결합니다.
val name = "코틀린"
val version = 2.0
println("언어: $name") // 언어: 코틀린
println("버전: ${version + 0.1}") // 버전: 2.1
여러 줄 문자열도 지원합니다.
val json = """
{
"name": "$name",
"version": $version
}
""".trimIndent()
자바와 비교하면 차이가 확연합니다.
// 자바
String result = "이름: " + name + ", 버전: " + version;
// 코틀린
val result = "이름: $name, 버전: $version"
when 표현식 — switch의 상위 호환
코틀린의 when은 자바 switch의 모든 기능을 포함하면서 훨씬 강력합니다.
val result = when (grade) {
'A' -> "우수"
'B' -> "보통"
'C' -> "미흡"
else -> "알 수 없음"
}
범위 검사, 타입 검사, 여러 값 매칭, 인자 없는 형태까지 지원합니다.
// 범위 검사
when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
else -> "F"
}
// 타입 검사 (스마트 캐스트 적용)
when (obj) {
is String -> println("길이: ${obj.length}")
is Int -> println("정수: $obj")
else -> println("기타")
}
| 특징 | Java switch | Kotlin when |
|---|---|---|
| Fall-through | 있음 (break 필요) | 없음 |
| 표현식 사용 | Java 14부터 | 처음부터 |
| 타입 검사 | Java 17부터 | 처음부터 |
| 범위 검사 | 불가 | in 연산자 |
| 인자 없는 형태 | 불가 | 가능 |
자바와의 100% 호환
코틀린은 같은 JVM 위에서 돌아가므로 자바와 완벽하게 호환됩니다.
// 코틀린에서 자바 라이브러리를 그대로 사용
import java.time.LocalDateTime
val now = LocalDateTime.now()
// 코틀린 파일: Utils.kt
fun greet(name: String): String = "안녕, $name!"
// 자바에서 호출
String result = UtilsKt.greet("자바");
코틀린 최상위 함수는 파일명Kt 클래스의 정적 메서드로 컴파일됩니다. @JvmStatic, @JvmOverloads 같은 어노테이션으로 자바 호출을 더 자연스럽게 만들 수 있습니다.
점진적 마이그레이션이 가능하다
기존 자바 프로젝트
├── src/main/java/ ← 기존 자바 코드 유지
│ └── UserService.java
├── src/main/kotlin/ ← 새 코드는 코틀린으로
│ └── OrderService.kt
한 번에 전부 바꾸는 게 아니라, 새로운 코드부터 코틀린으로 작성하고 기존 자바 코드와 공존시키는 방식입니다.
주의할 점
자바에서 코틀린으로 넘어올 때 자주 실수하는 부분입니다.
checked exception이 없다
코틀린은 checked exception을 강제하지 않습니다. 자바에서 throws IOException을 선언해야 했던 메서드도, 코틀린에서는 선언 없이 호출할 수 있습니다. 편리하지만, ** 예외 처리를 잊기 쉽다 **는 함정이 있습니다.
fun readFile(path: String): String {
return File(path).readText() // throws 선언 불필요
}
기본 가시성이 다르다
자바는 package-private가 기본이지만, 코틀린은 public이 기본 입니다. 의도치 않게 API가 노출될 수 있으니, internal이나 private을 적극 활용해야 합니다.
val이 불변을 보장하지 않는다
앞에서 다뤘듯이 val은 참조 재할당만 막습니다. val list = mutableListOf()처럼 선언하면 내부 상태가 변합니다. 진짜 불변이 필요하면 불변 컬렉션(listOf)을 사용해야 합니다.
정리
| 항목 | 설명 |
|---|---|
| val/var | val은 재할당 불가(≠ 불변), var는 재할당 가능 |
| 타입 추론 | 컴파일 타임에 타입이 결정되는 정적 타이핑 |
| Nullable | ? 하나로 NPE를 컴파일 타임에 방지 |
| String Template | $와 ${}로 문자열 조합 |
| when | fall-through 없는 강력한 패턴 매칭 |
| 자바 호환 | 같은 프로젝트에서 자바와 코틀린 공존 가능 |
| 기본 가시성 | 코틀린은 public, 자바는 package-private |