[Kotlin] const val과 val (Feat. Companion Object, Object)

송규빈·2022년 10월 1일
0

const val과 val

먼저 const val과 val의 차이를 말해보자면 아래와 같습니다.

const val

  1. const val은 값이 컴파일 시 할당이 됩니다.
  2. String, Int 등 primitive type만 가능합니다.(참조 타입 X)

이 두 개의 특징에 대해 세부적으로 생각을 해보자면

  • 값 할당
    컴파일 시 할당이 된다는 건 즉, 상수처럼 런타임 전에 값을 넣어줘야한다는 것입니다.
const val NUMBER :Int = max(1,0)

이렇게 코드를 작성하게 되면 컴파일 에러가 나옵니다. 왜냐하면 const val은 컴파일 단계에서 값이 할당이 되어야 하는데, max()라는 메서드는 런타임 시 동작이 되기 때문입니다.

const val NUMBER :Int = 10000

그래서 이렇게 값을 넣어줘야합니다.

  • 참조 타입이 불가

위에서 말했듯이 const val 은 참조 타입으로 선언이 불가합니다.

const val NUMBER :MutableList<Int> = mutableListOf()

위처럼 선언하게 되면 또 컴파일 에러가 나옵니다. 왜 그런걸까요??
값 할당에서의 이유를 생각해보면 됩니다. (또 다른 이유가 있을지는 모르겠지만...제 생각은 이렇습니다)

const val은 분명 컴파일 시에 값이 할당된다고 했습니다. 근데 참조 타입이 가능하다면? 참조 타입은 런타임 시 객체가 할당이 되기 때문입니다.

  • 접근
    또한, 위에서 말했던 것을 토대로 생각해보면 const val은 컴파일 시에 데이터가 메모리에 존재한다는 뜻입니다. 그렇기에 사용 시에 객체를 따로 생성해서 접근하는 것이 아니라 Class.NUMBER 형태를 사용해서 직접 접근이 가능합니다.

val

  1. 런타임 시 값을 할당할 수 있습니다.
  2. primitive type과 reference type 모두 선언 가능합니다.

val 은 const val과 다르게 런타임 시에 값을 할당할 수 있습니다.
즉, 아래와 같은 형태가 가능하다는 것입니다.

val number = max(1,0)

또한, val은 const val과는 다르게 런타임에서 값이 할당될 수 있으므로 primitive type과 reference type 모두 가능합니다.

object와 Companion Object

object와 companion object에 대한 내용을 넣은 것은 object안에서도 val과 const val을 사용할 수 있고 companion object 안에서도 사용할 수 있는데 이와 관련해서 궁금해서 넣었습니다.

겉으로 보이는 차이는 object는 class 키워드 대신 object My{ } 이런식으로 쓰이고 companion object는 다른 클래스 내부에서 블럭 형태로 쓰이는 차이가 있습니다.
다음은 내부적(?)으로 차이입니다.

companion object

companion object는 클래스 내부에 들어가는 블럭이고, 해당 클래스가 로드될 때 초기화가 됩니다.
-> 이유를 생각해보면 클래스 내부에 있으니 클래스 정보를 읽고 JVM의 클래스 로더가 클래스를 로드해야하기 때문인 것 같습니다.
즉, 클래스가 메모리에 적재되면서 함께 생성되는 객체라고 할 수 있을 것 같습니다.

  • 초기화 시점
    init 블럭에 로그를 찍어 확인해봤습니다.
class Test {
 companion object {
        init {
            println()
            println("init companion object")
        }
     }
     
private fun main() {
    val test = Test()

}

이를 실행해보니 아래와 같이 클래스가 로드될 때 초기화가 된다는 것을 알 수 있었습니다.

  • 접근
    companion object에는 이름도 붙일 수 있는데 접근을 할 때는 Class.Companion 이렇게 사용이 가능합니다. (이름을 붙이지 않을 경우에는 자동적으로 Companion이라는 이름으로 할당이 됩니다.)

  • const val, val
    그렇다면 이 안에 있는 멤버들은 어떻게 선언이 되는 걸까요?

class Test {
    init {
        println("init Test Class")
    }
    companion object TestCompanion{
        init {
            println()
            println("init companion object")
        }

        val testCompanionVal = 0
        const val testCompanionConstVal = 0

        fun testCompanionFunction(){
            println("testCompanionFunction")
        }
    }

}

우선 코드는 이렇게 작성했습니다. 여기서 testCompanionVal와 testCompanionConstVal를 주목하시면 될 것 같습니다.

이와 같이 val과 const val로 이뤄진 변수를 디컴파일 해봤습니다.

둘 다 static final로 선언이 되지만 val로 선언이 되면 private으로 되어 있는 것을 확인할 수 있습니다.
또 다른 차이는 두 개 모두 변수에 0을 할당하는 것으로 설정했지만 const val로 선언된 변수에만 값이 들어가있는 것을 볼 수 있는데요.
이는 위에 const val과 val에서 말했던 값 할당 시점 때문에 그런 것 같습니다.

근데 그렇다면 val로 선언이 된 변수는 private으로 되어 있으니 companion object 내부에서만 접근이 가능할까요? 그렇지 않습니다.

위처럼 둘 다 똑같은 방법으로 접근이 가능한데 그 이유는 바로 companion object가 생성이 되는 코드를 보면 알 수 있습니다.

Companion object는 따로 Test 클래스 내부에 public static final class로 생성이 됩니다.
이 안에 Test.testCompanionVal을 리턴하는 getter가 있는 것을 확인할 수 있습니다.
그렇기에 const val과 똑같은 방법으로 접근이 가능한 것이죠. (val로 선언된 변수에 @JVMField 어노테이션을 붙인다면 const val과 똑같이 public으로 선언이 되어집니다.)

그렇다면 const val은 public으로 선언이 되어졌고 val은 private으로 선언되고 getter를 통해 접근하게끔 만든 것일까요??

official한 정보를 제공하면 좋겠지만 검색을 해봐도 안 나와서 제 지식을 기반으로 말씀드리겠습니다.
(맞을 거에요 아마두,,아니라면 꼭 댓글로 알려주세요😅)

일단 접근은 캡슐화의 관점에서 바라보았습니다.
const val은 컴파일 시에 값이 할당되며 그렇기에 값을 선언과 동시에 할당해야합니다. 하지만 val은 불변이기는 하지만 런타임 시에 값이 할당이 되기 때문에 외부에서의 수정을 막은 듯 합니다 :)

추가적으로 val이면 어차피 수정이 불가하니까 상관없는 것 아닌가? 라고 0.1초 생각이 들었었는데 자바에서는 애초에 '불변'이라는 val 키워드가 없고 코틀린의 동작방식이 결국엔 자바로 디컴파일되어 jvm에서 돌아가는 것으로 알고 있기 때문에 앞서 말한 이유가 되는 것 아닌가 싶습니다.

object

object는 이 키워드로 클래스를 정의하여 싱글톤을 구현할 때도 사용이 되고, 익명 객체로도 쓰입니다.

아래는 object 키워드로 클래스를 정의했을 때의 특징입니다.

  • 초기화 시점
    object는 실제 사용 시 초기화됩니다. 즉, 멤버에 접근할 때 초기화 된다는 뜻입니다.

  • 접근
    Class.멤버 형태로 접근이 가능합니다.

Test라는 이름으로 object 클래스를 하나 만들었습니다.

object Test{
    val testVal = 0
    const val testConstVal = 0
    init {
        println("init object Test")
    }
}
private fun main() {

    println(Test.testVal)
    println(myTestConstVal)

}

그러면 또 얘네들은 어떻게 동작하는 지를 봐야겠죠?

companion object와 동일한 구조를 보이고 있습니다. 차이가 있다면 companion object는 클래스 내부에 선언되기 때문에 중첩 클래스로 나타내지고 object로 선언되면 바로 해당 클래스로 생성이 됩니다.

profile
🚀 상상을 좋아하는 개발자

0개의 댓글