Kotlin 변수와 타입 | val, var, 기본 타입 완벽 정리

Kotlin 변수와 타입 | val, var, 기본 타입 완벽 정리

이 글의 핵심

Kotlin 변수와 타입에 대해 정리한 개발 블로그 글입니다. var score = 90 score = 95

들어가며

val·var널 안전성(?, !!, ?.)으로 불변을 기본에 두고, 널 가능성을 타입에 적습니다. 컴파일러가 흐름 분석으로 불필요한 null 검사를 줄여 줍니다.


1. 변수 선언

val (불변)

val name = "홍길동"
val age = 25

// name = "김철수"  // 컴파일 에러!

var (가변)

var score = 90
score = 95  // OK

var count = 0
count++  // OK

선택 기준

// 기본적으로 val 사용 (권장)
val pi = 3.14
val maxSize = 100

// 변경이 필요한 경우만 var
var currentPage = 1
var isLoggedIn = false

2. 기본 타입

숫자 타입

// 정수
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2147483647
val long: Long = 9223372036854775807L

// 실수
val float: Float = 3.14f
val double: Double = 3.14159

// 타입 추론
val number = 10        // Int
val bigNumber = 10L    // Long
val decimal = 3.14     // Double
val precise = 3.14f    // Float

문자와 문자열

// 문자
val char: Char = 'A'
val koreanChar = '가'

// 문자열
val text: String = "Hello"
val multiLine = """
    여러 줄
    문자열
""".trimIndent()

// 문자열 템플릿
val name = "홍길동"
val greeting = "안녕하세요, $name님!"
val info = "나이: ${age + 1}세"

불리언

val isActive: Boolean = true
val isCompleted = false

// 논리 연산
val result = isActive && !isCompleted

3. Nullable 타입

Non-null vs Nullable

// Non-null (기본)
var name: String = "홍길동"
// name = null  // 컴파일 에러!

// Nullable
var nullableName: String? = "홍길동"
nullableName = null  // OK

Safe Call (?.)

Nullable 타입을 안전하게 다루는 핵심 연산자입니다:

val name: String? = null

// Safe Call: null이면 null 반환, 아니면 프로퍼티/메서드 호출
val length = name?.length  
// name이 null이면 length도 null
// name이 "홍길동"이면 length는 3
// Java의 if (name != null) name.length() 를 간결하게 표현

// 체이닝 가능 - 중간에 null이 있으면 전체가 null
val city = user?.address?.city
// user가 null이면 → null
// user.address가 null이면 → null
// 둘 다 있으면 → user.address.city
// Java라면 여러 줄의 null 체크가 필요

Elvis 연산자 (?:)

null일 때 기본값을 제공하는 연산자입니다:

val name: String? = null

// Elvis 연산자: null이면 오른쪽 값 사용
val length = name?.length ?: 0  
// name?.length가 null이면 0 반환
// name?.length가 3이면 3 반환

// 기본값 제공
val displayName = name ?: "Guest"
// name이 null이면 "Guest"
// name이 "홍길동"이면 "홍길동"

// 실전 예시: 사용자 이름 표시
fun greet(name: String?) {
    val greeting = "안녕하세요, ${name ?: "방문자"}님!"
    println(greeting)
}
greet(null)      // 안녕하세요, 방문자님!
greet("홍길동")  // 안녕하세요, 홍길동님!

Not-null 단언 (!!)

“이 값은 절대 null이 아니다”라고 컴파일러에게 단언합니다:

val name: String? = "홍길동"

// !! : null이 아님을 단언
val length = name!!.length  
// name이 null이면 KotlinNullPointerException 발생
// name이 "홍길동"이면 3 반환

// 주의: 확실할 때만 사용!
// 가능하면 ?. 또는 ?: 사용 권장

// 나쁜 예
val user: User? = getUser()
val name = user!!.name  // null이면 크래시!

// 좋은 예
val name = user?.name ?: "Unknown"  // null 안전

연산자 비교:

연산자동작null일 때
?.Safe Callnull 반환
?:Elvis기본값 반환
!!Not-null 단언예외 발생

Safe Cast (as?)

val obj: Any = "Hello"
val str: String? = obj as? String  // 성공
val num: Int? = obj as? Int        // null 반환

4. 타입 변환

명시적 변환

val int: Int = 10
val long: Long = int.toLong()
val double: Double = int.toDouble()
val string: String = int.toString()

// 모든 변환 메서드
val byte = int.toByte()
val short = int.toShort()
val float = int.toFloat()

문자열 변환

// 문자열 → 숫자
val str = "123"
val num = str.toInt()
val decimal = str.toDouble()

// 안전한 변환
val num = str.toIntOrNull() ?: 0

5. 타입 체크와 스마트 캐스트

is 연산자

val obj: Any = "Hello"

if (obj is String) {
    // 자동으로 String으로 캐스트
    println(obj.length)
}

// not is
if (obj !is String) {
    println("문자열이 아님")
}

when과 스마트 캐스트

fun describe(obj: Any): String {
    return when (obj) {
        is String -> "문자열, 길이: ${obj.length}"
        is Int -> "정수: $obj"
        is List<*> -> "리스트, 크기: ${obj.size}"
        else -> "알 수 없음"
    }
}

6. 상수

const val

// 컴파일 타임 상수
const val MAX_SIZE = 100
const val API_KEY = "your-api-key"

// 클래스 밖에서만 선언 가능
class Config {
    companion object {
        const val TIMEOUT = 5000
    }
}

lateinit

// 나중에 초기화
class MyClass {
    lateinit var name: String
    
    fun init() {
        name = "홍길동"
    }
    
    fun isInitialized() = ::name.isInitialized
}

lazy

// 처음 사용 시 초기화
val heavyObject: HeavyObject by lazy {
    println("초기화 중...")
    HeavyObject()
}

// 처음 접근 시에만 초기화됨
println(heavyObject.data)

7. 실전 예제

예제 1: 사용자 입력 처리

fun main() {
    print("이름을 입력하세요: ")
    val name = readLine() ?: "Guest"
    
    print("나이를 입력하세요: ")
    val ageInput = readLine()
    val age = ageInput?.toIntOrNull() ?: 0
    
    println("안녕하세요, $name님! (${age}세)")
}

예제 2: 안전한 계산

fun divide(a: Int, b: Int): Double? {
    return if (b != 0) {
        a.toDouble() / b
    } else {
        null
    }
}

fun main() {
    val result = divide(10, 0)
    println("결과: ${result ?: "0으로 나눌 수 없음"}")
}

예제 3: 타입 변환 유틸리티

fun parseNumber(input: String): Int {
    return input.toIntOrNull() ?: run {
        println("잘못된 입력: $input")
        0
    }
}

fun main() {
    println(parseNumber("123"))    // 123
    println(parseNumber("abc"))    // 0
}

정리

핵심 요약

  1. val: 불변 (권장)
  2. var: 가변
  3. Nullable: String?, ?., ?:, !!
  4. 타입 변환: toInt(), toString()
  5. 스마트 캐스트: is 연산자
  6. 지연 초기화: lateinit, lazy

다음 단계

  • Kotlin 함수
  • Kotlin 클래스
  • Kotlin 컬렉션

관련 글

  • Java 변수와 타입 | 기본 타입, 참조 타입, 형변환
  • JavaScript 변수와 데이터 타입 | let, const, var 완벽 정리
  • Swift 변수와 타입 | var, let, 옵셔널
  • C++ numeric_limits |
  • C++ 템플릿 |