출시·고도화 중
Kotlin 안내서 · 3/6
이 장에서는 Kotlin의 기본 타입과 컬렉션, 그리고 데이터를 담는 여러 종류의 클래스를 살펴봅니다. 특히 데이터 클래스와 sealed 클래스는 Kotlin 코드에서 아주 자주 보게 되는 도구입니다.
Kotlin에서는 숫자와 문자도 메서드를 가진 객체처럼 다룹니다. 그래도 성능 걱정은 하지 않아도 됩니다. 컴파일러가 가능한 곳에서는 JVM의 기본형(int, double 등)으로 바꿔 줍니다.
| 타입 | 크기 | 예 |
|---|---|---|
Int | 32비트 | 42 |
Long | 64비트 | 3_000_000_000L |
Double | 64비트 실수 | 0.75 |
Float | 32비트 실수 | 9.99f |
Boolean | 참 · 거짓 | true |
Char | 문자 하나 | 'K' |
String | 문자열 | "Kotlin" |
fun main() {
val count = 42 // Int
val big = 3_000_000_000L // Long: 밑줄로 자릿수를 나눌 수 있습니다
// val wide: Long = count // 컴파일 오류: 암시적 넓히기 변환이 없습니다
val wide: Long = count.toLong()
println(wide + big)
println("42".toInt() + 1) // 43
println("abc".toIntOrNull()) // null: 바꿀 수 없으면 예외 대신 null
}Java와 달리 Int를 Long 변수에 그냥 넣을 수 없습니다. toLong(), toDouble()처럼 명시적으로 변환해야 합니다. 이 규칙 덕분에 의도하지 않은 변환으로 생기는 버그가 줄어듭니다.
Kotlin의 컬렉션은 읽기 전용 인터페이스(List, Set, Map)와 변경 가능한 인터페이스(MutableList, MutableSet, MutableMap)로 나뉩니다. listOf로 만들면 원소를 추가할 수 없고, mutableListOf로 만들어야 add, remove를 쓸 수 있습니다.
fun main() {
val numbers = listOf(3, 1, 4, 1, 5) // List<Int>
val unique = setOf("a", "b", "a") // Set<String>: [a, b]
val ages = mapOf("민수" to 31, "지은" to 28) // Map<String, Int>
val todo = mutableListOf("장보기")
todo.add("운동")
todo.removeAt(0)
val scores = mutableMapOf<String, Int>()
scores["kim"] = 90
scores["lee"] = 85
println(numbers.filter { it > 2 }.map { it * 10 }) // [30, 40, 50]
println(numbers.sorted().distinct()) // [1, 3, 4, 5]
println(unique.size) // 2
println(ages.getOrDefault("철수", 0)) // 0
println(todo) // [운동]
}"민수" to 31의 to는 두 값을 Pair로 묶는 함수입니다. 읽기 전용이라는 말은 그 인터페이스로는 바꿀 수 없다는 뜻이지, 내부 객체가 절대 바뀌지 않는다는 보장은 아닙니다. 함수의 매개변수와 반환 타입에는 읽기 전용 타입을 쓰고, 변경은 그 컬렉션을 소유한 쪽에서만 하는 것이 좋습니다.
클래스 이름 옆의 괄호가 주 생성자입니다. 매개변수에 val이나 var를 붙이면 그대로 속성이 됩니다. 객체를 만들 때 new 키워드는 쓰지 않습니다. Kotlin의 클래스와 메서드는 기본적으로 final이라서, 상속을 허락하려면 open을 붙여야 합니다.
open class Animal(val name: String) {
open fun sound(): String = "..."
override fun toString() = "$name: ${sound()}"
}
class Dog(name: String, var age: Int) : Animal(name) {
init {
require(age >= 0) { "나이는 0 이상이어야 합니다" }
}
val isPuppy: Boolean
get() = age < 1 // 읽을 때마다 계산하는 속성
override fun sound() = "멍멍"
}
fun main() {
val dog = Dog("초코", 3)
dog.age += 1
println(dog) // 초코: 멍멍
println(dog.isPuppy) // false
}init 블록은 객체가 만들어질 때 실행됩니다. 속성에는 직접 getter와 setter를 정의할 수 있어서, Java처럼 getAge() 같은 메서드를 손으로 만들 필요가 없습니다.
값을 담는 것이 주 목적인 클래스는 data class로 선언합니다. 컴파일러가 equals, hashCode, toString, copy, 구조 분해용 componentN 함수를 자동으로 만들어 줍니다.
data class User(val id: Long, val name: String, val email: String? = null)
fun main() {
val a = User(1, "민수")
val b = User(1, "민수")
println(a == b) // true: 속성 값을 비교합니다
println(a) // User(id=1, name=민수, email=null)
val renamed = a.copy(name = "민준")
val (id, name) = renamed // 구조 분해
println("$id $name") // 1 민준
}==는 equals를 호출하고, 같은 객체인지 비교하려면 ===를 씁니다. String?의 물음표는 null을 담을 수 있다는 뜻으로, 6장에서 다룹니다.
정해진 몇 가지 값 중 하나만 가질 수 있다면 enum class를 씁니다. 각 경우가 서로 다른 데이터를 가져야 한다면 sealed 클래스나 인터페이스가 알맞습니다. sealed 타입의 하위 타입은 같은 모듈과 패키지 안에서만 정의할 수 있으므로, 컴파일러가 모든 경우를 알고 when에서 빠진 경우를 찾아 줍니다.
import kotlin.math.PI
enum class Color(val hex: String) {
RED("#FF0000"), GREEN("#00FF00"), BLUE("#0000FF");
fun label() = name.lowercase()
}
sealed interface Shape
data class Circle(val radius: Double) : Shape
data class Rect(val width: Double, val height: Double) : Shape
data object Empty : Shape
fun area(shape: Shape): Double = when (shape) { // else가 필요 없습니다
is Circle -> PI * shape.radius * shape.radius
is Rect -> shape.width * shape.height
Empty -> 0.0
}
fun main() {
println(Color.entries.map { it.label() }) // [red, green, blue]
println(area(Rect(2.0, 3.0))) // 6.0
}object는 클래스 선언과 동시에 단 하나의 인스턴스를 만드는 싱글턴입니다. Kotlin에는 static이 없으므로, 클래스에 딸린 팩토리 함수나 상수는 companion object 안에 둡니다.
object AppConfig {
var debug = false
fun summary() = "debug=$debug"
}
class Temperature private constructor(val celsius: Double) {
companion object {
val FREEZING = Temperature(0.0)
fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
}
}
fun main() {
AppConfig.debug = true
println(AppConfig.summary()) // debug=true
println(Temperature.fromFahrenheit(212.0).celsius) // 100.0
println(Temperature.FREEZING.celsius) // 0.0
}toLong() 같은 함수로 바꿉니다.listOf)과 변경 가능(mutableListOf)으로 나뉩니다.final이며, 주 생성자의 val/var 매개변수가 곧 속성입니다.equals, toString, copy, 구조 분해를 자동으로 만들어 줍니다.enum이나 sealed로 표현하고, object는 싱글턴, companion object는 static 대신 씁니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.