엔지니어 규의 IT 프로그래밍 다이어리

[Kotlin] 28. 변수의 고급 기술. 상수, lateinit, lazy 본문

프로그래밍 언어/코틀린

[Kotlin] 28. 변수의 고급 기술. 상수, lateinit, lazy

엔지니어 규 2022. 6. 3. 20:25
728x90

상수

 

우리는 var, val 을 이용하여 기초적인 변수를 선언하는 방법을 알고 있다.

 

var은 한번 할당한 객체가 있더라도, 다른 객체로 변경하여 할당할 수 있으며,

val은 한번 객체를 할당시 다시 할당된 객체를 바꿀 수 없다.

 

하지만 여기서 주의할점은 val은 할당된 객체를 바꿀 수 없을 뿐이지 객체 내부의 속성을 변경할수 없는것은 아니다.

 

그런데 절대 변경이 불가능한것이 있는데 '상수' 라는 것이다.

 

상수는 컴파일 시점에 결정되어 절대 바꿀수 없는 값이며 선언은 const val CONST_A = 1234 처럼 val 앞에 const를 붙인다.

 

상수로 선언될 수 있는 값은 기본자료형만 가능하며 런타임에 생성될 수 있는 일반적인 다른 클래스의 객체들은 담을 수 없다.

 

상수는 클래스의 속성이나, 지역변수로는 사용할 수 없으며, 반드시 companion object 안에 선언하여 객체의 생성과 상관없이

클래스와 관계된(Sample.CONST_A) 고정적인 값으로만 사용한다.

 

상수의 이름을 만들떄는 의례적으로 대문자와 언더바(_) 만 사용하며 이를 통해 변수가 아닌 상수라는 점을 알리게 된다.

이는 대부분의 언어에서 통용되는 방법이다.

 

아래 코드를 보자.

fun main(){

    val foodCourt = FoodCourt()
    foodCourt.searchPrice(FoodCourt.FOOD_CREAM_PASTA) // 크림파스타의 가격은 13000원 입니다.
    foodCourt.searchPrice(FoodCourt.FOOD_STEAK) // 스테이크의 가격은 25000원 입니다.
    foodCourt.searchPrice(FoodCourt.FOOD_PIZZA) // 피자의 가격은 15000원 입니다.

}

class FoodCourt{
    fun searchPrice(foodName : String){
        val price = when(foodName)
        {
            FOOD_CREAM_PASTA -> 13000
            FOOD_STEAK -> 25000
            FOOD_PIZZA -> 15000
            else -> 0
        }
        println("${foodName}의 가격은 ${price}원 입니다.")
    }
    companion object{
        const val FOOD_CREAM_PASTA = "크림파스타"
        const val FOOD_STEAK = "스테이크"
        const val FOOD_PIZZA = "피자"
    }
}

 

위의 코드만 보면 기능적으로 굳이 왜변수를 사용하지 않고 상수를 별도로 사용하는지 알수가 없다.

변수의 경우 런타임시 객체를 생성하는데 시간이 더 소요되어 성능의 하락이 있다.

따라서 늘 고정적으로 사용할 값은  상수를 통해 객체의 생성없이 메모리에 값을 고정하여 사용함으로써

성능을 향상시킬 수 있다는 장점이 있다.

 

 

늦은초기화 (lateinit)

 

코틀린에서는 변수를 선언할때 객체를 바로 할당하지 않는 경우에는 기본적으로 컴파일이 되지 않는다.

 

경우에 따라서는 변수에 객체를 할당하는 것을 선언과 동시에 할 수 없을 때도 있다.

 

이럴때는 lateinit var a : Int 처럼 var 앞에 lateinit을 사용하여 일단 변수만 선언하고 초기값의 할당은 나중에 할수 있도록 할 수 있다.

 

lateinit 으로 지정한 var 변수는 초기값 할당 전까지 변수를 사용할 수 없으며(에러발생), 기본자료형에는 사용할 수 없다.

(*String 클래스는 사용 가능하다.)

 

또한 lateinit 변수의 '초기화'를 하였는지 여부를 확인할때는 ::a.isinitialized 처럼 변수앞에 :: 를 붙이고 .isinitialized 라는 키워드로

초기화가 되었는지 확인 할 수 있으므로 오류를 막을 수 있다.

 

fun main(){

    val a= LateInitSample()

    println(a.getLateInitText())  //기본값
    a.text = "새로 할당한 값"
    println(a.getLateInitText()) //새로 할당한 값
    
}

class LateInitSample{
    lateinit var text : String

    fun getLateInitText(): String{
        if(::text.isInitialized){
            return text
        }
        else{
            return "기본값"
        }
    }
}

 실행해보면 변수의 초기화 여부에 따라 다른 값을 출력하는 것을 알 수 있다.

 

 

 

지연 대리자속성(lazy delegate properties)

 

lazy delegate 는 변수를 사용하는 시점까지 초기화를 자동으로 늦춰주며, 이는 lateinit 과 달리 val a : Int by lazy{7} 와 같이

 

val 변수에 by 라는 키워드를 사용하여 lazy 라는 람다 함수형태의 초기함수를 사용하는 형태로,

 

코드에서는 선언시 즉시 객체를 생성및 할당하여 변수를 초기화 하는 형태를 가지고 있지만 실제 실행시는

 

val 변수를 사용하는 시점에 초기화 과정을 진행함으로써 코드의 실행시간을 최적화 할 수 있는 코드 이이다.

 

람다함수로 초기화가 진행되므로 함수안에 여러 구문이 들어가 있을 수 있고 맨 마지막 구문의 결과가 변수에 할당이 된다.

 

fun main(){

    val number: Int by lazy {
        println("초기화를 합니다.")
        7
    }
    println("코드를 시작합니다.") //코드를 시작합니다.
    println(number)   //이 시점에서 number 가 7로 초기화된다.
    /*
    초기화를 합니다.
    7
    */

    println(number)
    /*
    7
     */

}

 

코드를 시작한뒤 number를 처음 출력할 때 lazy 함수를 통해 초기화가 진행 되었음을 알 수 있으며, 

두번째 number를 출력할때는 이미 초기화를 했기 때문에 다시 초기화 구문을 실행하지 않음을 알 수 있다.

 

상수, 늦은 초기화, 초기화의 지연은 상황에따라 변수를 사용하는 방법을 조금더 세세하게 조절할 수 있다는 장점이 있다.

 

728x90
Comments