val과 const val의 차이

Seogi·2024년 2월 24일

var 과 val

두 키워드를 통해 클래스 내부에 프로퍼티를 선언한 경우 멤버 필드의 가시성은 보통 private이다.

대신, 클래스는 접근자 메서드를 제공한다.

  • 클래스 밖에 선언한 경우에도 마찬가지이다. 다만 차이가 있다면 기존의 클래스명+kt라는 임의의 클래스가 생성되고 그 안에 들어가게 된다.
var valTest1 = "고양이"

class Test {
    var valTest2 = "강아지"
}

--------------------------------Decompile to Java-----------------------------------

//코드 생략

public final class Test {
   @NotNull
   private String valTest2 = "강아지";

   @NotNull
   public final String getValTest2() {
      return this.valTest2;
   }

   public final void setValTest2(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.valTest2 = var1;
   }
}

//코드 생략

public final class TestKt {
   @NotNull
   private static String valTest1 = "고양이";

   @NotNull
   public static final String getValTest1() {
      return valTest1;
   }

   public static final void setValTest1(@NotNull String var0) {
      Intrinsics.checkNotNullParameter(var0, "<set-?>");
      valTest1 = var0;
   }
}

기본적으로 가시성이 private으로 변환되는 것은 나의 생각이지만 클래스가 가진 목적 중 하나는 캡슐화이기에 직접 필드에 접근하지 못하게 한 것 같다.

val과 const val

둘의 차이는 간단하게 이렇게 말할 수 있다.

  • const
    • 컴파일 시점에 값을 할당
      • 컴파일: 소스 코드를 프로세서가 이해할 수 있는 기계어로 번환하는 작업
    • 객체나 동반객체 선언의 최상위 속성 또는 멤버여야 한다
      • 별도의 인스턴스를 생성하지 않고도 액세스할 수 있어야 하기 때문이라 생각된다
    • 문자열 또는 기본 타입만을 가질 수 있다
      • compile-time inlining한 특성을 가지고 있기 때문이다.
  • val
    • 런타임 시점에 값을 할당
      • 런타임: 컴파일 과정을 마친 컴퓨터 프로그램이 실행되고 있는 환경 또는 동작되는 동안의 시간

위 말을 이해했다면 아래 코드에서 왜 에러가 발생하는지도 쉽게 알 수 있다.

fun getKeyword():String{
    return "커피"
}

class Test {
    companion object{
        val valTest = getKeyword()
        const val constValTest = getKeyword() //ERROR

    }
}

그렇다면 위 에러를 고치는 방법은 무엇일까?

그냥 const 변경자를 제거하면 된다. 그러나 여기에 ‘trade-off가 존재한다.

앞서 말했듯이 val키워드는 내부적으로 getter가 생성된다. 상수가 getter를 가진다는 것은 자연스럽지 못하다.

⇒ getter(or setter)가 생성되지 않게 하는 방법으로는 @JvmField 어노테이션이 있다.

  • @JvmField: Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field.
class Test {
    companion object{
        val valTest = getKeyword()
        @JvmField
        val constValTest = getKeyword()

    }
}

--------------------------------Decompile to Java-----------------------------------

public final class Test {
   @NotNull
   private static final String valTest = TestKt.getKeyword();
   @JvmField
   @NotNull
   public static final String constValTest = TestKt.getKeyword();\

	//코드 생략

public static final class Companion {
      @NotNull
      public final String getValTest() {
         return Test.valTest;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }

}

디컴파일된 코드를 보게 되면 valTest의 경우 Companion클래스 내부에 getter가 존재하는 것을 볼 수 있지만 constValTest의 경우 존재하지 않는다.

대신 constValTest의 가시성이 private에서 public으로 변경되었다. 이는 접근자메서드를 생성하지 않는 대신 필드로서 직접적으로 드러내는 것을 의미한다.

(위에 작성해놓은 JvmField의 정의 중 ’expose it as a field’ 가 그런 의미이다.)

그렇다면 우리는 @JvmField가 private을 public으로 바꿔주는 역할을 하는 것을 알 수 있다.

fun getKeyword():String{
    return "커피"
}

class Test {
    companion object{
        @JvmField //ERROR
        private val constValTest = getKeyword()
    }
}

당연히 private가 붙어있는 경우 @JvmField를 사용할 수 없다.

참고

서적: Kotlin IN ACTION

What can const val do which @JvmField val cannot?

JvmField - Kotlin Programming Language

0개의 댓글