먼저 const val과 val의 차이를 말해보자면 아래와 같습니다.
이 두 개의 특징에 대해 세부적으로 생각을 해보자면
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은 분명 컴파일 시에 값이 할당된다고 했습니다. 근데 참조 타입이 가능하다면? 참조 타입은 런타임 시 객체가 할당이 되기 때문입니다.
val 은 const val과 다르게 런타임 시에 값을 할당할 수 있습니다.
즉, 아래와 같은 형태가 가능하다는 것입니다.
val number = max(1,0)
또한, val은 const val과는 다르게 런타임에서 값이 할당될 수 있으므로 primitive type과 reference type 모두 가능합니다.
object와 companion object에 대한 내용을 넣은 것은 object안에서도 val과 const val을 사용할 수 있고 companion object 안에서도 사용할 수 있는데 이와 관련해서 궁금해서 넣었습니다.
겉으로 보이는 차이는 object는 class 키워드 대신 object My{ } 이런식으로 쓰이고 companion object는 다른 클래스 내부에서 블럭 형태로 쓰이는 차이가 있습니다.
다음은 내부적(?)으로 차이입니다.
companion object는 클래스 내부에 들어가는 블럭이고, 해당 클래스가 로드될 때 초기화가 됩니다.
-> 이유를 생각해보면 클래스 내부에 있으니 클래스 정보를 읽고 JVM의 클래스 로더가 클래스를 로드해야하기 때문인 것 같습니다.
즉, 클래스가 메모리에 적재되면서 함께 생성되는 객체라고 할 수 있을 것 같습니다.
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는 실제 사용 시 초기화됩니다. 즉, 멤버에 접근할 때 초기화 된다는 뜻입니다.
접근
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로 선언되면 바로 해당 클래스로 생성이 됩니다.