본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로프로그래밍 › Kotlin › 기초

1.변수(Variable)

목차

1. 변수(Variable)

Kotlin은 val과 var로 이름을 선언한다. 둘의 차이는 그 이름에 새 값을 다시 할당할 수 있는지다. val은 한 번 할당한 뒤 재할당할 수 없고, var는 다시 할당할 수 있다. 둘 다 객체 내부가 자동으로 불변이 되는 것은 아니다.

fun main() {
    val name = "Kim"          // String으로 타입 추론
    var count: Int = 1        // 타입을 명시
    count += 1
    println("$name: $count")  // Kim: 2
    // name = "Lee"          // 컴파일 오류: val 재할당
}

val name = "Kim"에서 타입을 생략할 수 있는 것은 초기값으로부터 String을 추론하기 때문이다. 선언만 먼저 하고 나중에 값을 넣으려면 타입이 필요하다. 지역 val도 모든 사용 경로에서 한 번 할당되어야 한다.

val result: Int
result = 30
println(result)

val이 참조하는 컬렉션이 변경 가능한 종류라면 원소는 바뀔 수 있다. 완전히 바뀌지 않는 목록이 필요하다면 공개 API에 변경 메서드가 없는 List를 사용하고, 외부에서 들고 있는 변경 가능한 원본을 공유하는지도 확인한다.

val numbers = mutableListOf(1, 2)
numbers.add(3)           // 가능: 객체 내용 변경
// numbers = mutableListOf(4) // 불가능: val 재할당

기본 타입과 문자열 템플릿

val age: Int = 20
val large: Long = 20L
val ratio: Double = 0.5
val small: Float = 0.5f
val letter: Char = '가'
val enabled: Boolean = true
println("나이: $age, 두 배: ${age * 2}")

$age는 단순 변수, ${age * 2}는 식의 값을 문자열에 넣는다. 큰따옴표는 String, 작은따옴표는 Char다. Int 변수에 Long 값을 자동 대입하는 암묵적 형변환은 하지 않으므로 필요한 경우 범위와 오버플로를 확인해 toInt() 등을 사용한다. 여러 타입을 한 변수로 받아야 하면 Any를 쓸 수 있지만 타입별 사용 전에는 스마트 캐스트나 명시적 검사가 필요하다. 함수가 의미 있는 결과를 반환하지 않을 때 쓰는 Unit은 Java의 void와 비슷한 역할을 한다.

초기화를 미룰 때: lateinit과 lazy

클래스가 만들어질 때 값을 준비할 수 없다면 초기화를 미룰 수 있다. lateinit var는 나중에 직접 할당하겠다는 약속이다. 초기화 전에 읽으면 UninitializedPropertyAccessException이 난다. 일반적인 지역 변수·속성에서는 가능한 한 생성자에서 값을 받는 편이 누락을 줄인다.

class Controller {
    lateinit var message: String
    fun show() {
        check(::message.isInitialized) { "message를 먼저 설정하세요" }
        println(message)
    }
}

val controller = Controller()
controller.message = "Hello"
controller.show()

lateinit은 var 속성에 사용하며 Int, Boolean 같은 비 nullable 기본 타입에는 쓸 수 없다. ::message.isInitialized 검사는 해당 속성에 접근 가능한 범위에서 사용한다. 의존성 주입으로 나중에 값을 제공받는 경우에 볼 수 있지만 초기화 순서를 지키지 못하면 런타임 실패가 된다.

lazy는 처음 읽는 순간 초기화 식을 실행한다. 값을 처음 사용하기 전까지 계산을 미루면서 이후에는 같은 값을 재사용한다.

val configText: String by lazy {
    println("설정 계산")
    "mode=prod"
}
println(configText) // 계산 메시지를 출력한 뒤 mode=prod
println(configText) // 이미 계산된 mode=prod만 출력

lazy는 보통 val 속성에 위임한다. 기본 모드는 동기화되어 여러 스레드가 동시에 처음 읽어도 한 번만 초기화한다. 동기화 비용이 불필요하다는 근거가 있을 때에만 다른 LazyThreadSafetyMode를 선택한다. lazy 초기화 식이 외부 상태를 읽는다면 최초 접근 시점의 상태가 고정된다는 점도 확인해야 한다.

요구선택주의할 점
즉시 준비된 값, 재할당 불필요val참조 객체가 가변일 수 있음
계산 중 여러 번 갱신var변경 범위를 좁힐 것
외부 생명주기에서 나중에 주입lateinit var읽기 전 초기화 검사
첫 사용 때 한 번 계산val ... by lazy최초 접근 시점과 스레드 안전성

표에서 같은 “나중에 값이 생김”이라도 lateinit은 호출자가 값을 설정하고, lazy는 코드에 적은 식이 값을 만든다. 이 차이로 선택한다.

참고: Kotlin 기본 문법, 속성 문서, 위임 속성 문서

같은 카테고리의 글