[Kotlin] static, object, companion object 차이

2021. 6. 27. 21:54Kotlin

반응형

목차

     

    우리는 보통 kotlin에서 java의 static 변수 또는 메서드를 사용하기 위해 object 키워드 또는 companion object를 사용한다.

    아래처럼 말이다. 아래와 같이 object 를 사용하는 것을 object declaration이라고 한다. 

    object ObjectTest {
    
        const val CONST_STRING = "1"
    
        fun test() {}
    }
    
    class CompanionObjectTest {
    
        companion object {
            const val CONST_TEST = 2
    
            fun test() { }
        }
    }
    

     

    kotlin에서는 static 이란 것이 없고 위와 같이 사용하기 때문에 이것이 static 이다 라고 오해할 수 있다. 나 역시 이번 공부를 통해 정확히 알기 전까지는 static과 동일한 것 아닐까? 하는 잘못된 생각을 가지고 있었다. 

     

    TL;DR:

    kotlin의 object declaration 과 companion object는 java의 static 과 동일하지 않아.

    ✅ object declaration :

    • Singleton 형태 
    • thread-safe
    • lazy initialized : 실제로 사용될 때 초기화(initialized) 된다 
    • const val로 선언된 상수는 static 변수.
    • object 내부에 선언된 변수와 함수들은 java 의 static 이 아님. 단, 아래 케이스들은 static
      • const val 로 상수 선언한 것들.
      • @JVMStatic 또는 @JVMField의 어노테이션이 붙은 변수 및 함수들.

     

    ✅ companion object : 

    • 해당 클래스(companion obejct 는 클래스 내부에 들어가는 블럭이므로) 자체가 static 이 아님. 
      즉, CompanionObjectTest() 로 생성할 때 마다 객체의 주소값은 다름.
    • 해당 클래스가 로드될 때 초기화 됨.
    • const val로 선언된 상수는 static 변수.
    • companion object 내부에 선언된 변수와 함수들은 java 의 static 이 아님. 단, 아래 케이스들은 static
      • const val 로 상수 선언한 것들.
      • @JVMStatic 또는 @JVMField의 어노테이션이 붙은 변수 및 함수들.


    ✅ object vs companion object 차이점  

    • 초기화 시점이 다름.
    • object declaration 초기화 시점 :
      실제로 사용될 때 initialized 된다. 실제로 내부 함수를 접근해야 init 블럭이 호출됨 
      • i.e. ObjectTest.nonStaticFun() 을 호출할 때, 초기화된다는 것.
    • companion object 초기화 시점 :
      해당 클래스가 로드될 때 initialized 된다. 실제로 해당 클래스 (CompanionObjectTest)를 생성하면 companion object 내부 init 블럭이 호출됨을 확인.
      • i.e. val test =  CompanionObjectTest() 시 초기화된다는 것.

     

     

    object declaration

    직접 java 로 변환된 코드를 통해 확인해보자. 

    ObjectTest.kt

    object ObjectTest {
    
        const val CONST_STRING = "1"
    
        val nonStaticField = "2"
        @JvmField
        val staticField = "2"
    
        fun nonStaticFun() {
            println("test nonStaticFun()")
        }
    
        @JvmStatic
        fun staticFun() {
            println("test staticFun()")
        }
    }

     

    ObjectTest.kt 가 java 로 변환된 코드 👇 (kotlin bytecode를 decompile 해서 얻은 ObjectTest.decompiled.java)

    public final class ObjectTest {
       @NotNull
       public static final String CONST_STRING = "1";
       @JvmField
       @NotNull
       public static final String staticField;
       @NotNull
       public static final ObjectTest INSTANCE;
       
       @NotNull
       private static final String nonStaticField;
    
       @NotNull
       public final String getNonStaticField() {
          return nonStaticField;
       }
    
       public final void nonStaticFun() {
          String var1 = "test nonStaticFun()";
          boolean var2 = false;
          System.out.println(var1);
       }
    
       @JvmStatic
       public static final void staticFun() {
          String var0 = "test staticFun()";
          boolean var1 = false;
          System.out.println(var0);
       }
    
       private ObjectTest() {
       }
    
       static {
          ObjectTest var0 = new ObjectTest();
          INSTANCE = var0;
          nonStaticField = "2";
          staticField = "2";
       }
    }

     

    정리

    • java 로 변환된 파일 내부에 INSTANCE라는 ObjectTest 타입의 static 객체가 생성됨.
      👉 그래서 ObjectTest는 Singleton 형태라는 것.
    • const로 선언한 변수들은 public static 필드로 변환됨.
    • val 또는 일반 함수로 선언된 애들은 static 필드가 아니어서 INSTANCE 객체로 접근 가능.
    • 단, 함수에 @JVMStatic 을 선언한 경우에는 static 함수로 변환됨. @JVMField로 선언된 경우 static 필드가 됨.

    따라서, java 파일에서 ObjectTest에 접근 시 아래와 같음.

    // 아래 3가지는 static 필드 처리된 것. 
    String s = ObjectTest.CONST_STRING;
    String s2 = ObjectTest.staticField;
    ObjectTest.staticFun();
    
    // 아래 2가지는 static 이 아니기 때문에, static 변수인 INSTANCE 로 접근해야 함.
    ObjectTest.INSTANCE.getNonStaticField();
    ObjectTest.INSTANCE.nonStaticFun();

     

     

    companion object 

    직접 java 로 변환된 코드를 통해 확인해보자. 

    CompanionObjectTest.kt

    class CompanionObjectTest {
    
        companion object {
            const val CONST_TEST = 2
    
            val valTest = 1
    
            fun nonStaticMethod() {
                println("test nonStaticMethod()")
            }
    
            @JvmStatic
            fun staticMethod() {
                println("test staticMethod()")
            }
        }
    }

     

    위 코드가 java 로 변환된 코드 (kotlin bytecode를 decompile 해서 얻은 CompanionObjectTest.decompiled.java)

    public final class CompanionObjectTest {
       @NotNull
       public static final String CONST_STRING = "1";
       @JvmField
       @NotNull
       public static final String staticField = "2";
       @NotNull
       public static final CompanionObjectTest.Companion Companion = new CompanionObjectTest.Companion((DefaultConstructorMarker)null);
    
       @NotNull
       private static final String nonStaticField = "2";
       
       @JvmStatic
       public static final void staticFun() {
          Companion.staticFun();
       }
    
       public static final class Companion {
          @NotNull
          public final String getNonStaticField() {
             return CompanionObjectTest.nonStaticField;
          }
    
          public final void nonStaticFun() {
             String var1 = "test nonStaticFun()";
             boolean var2 = false;
             System.out.println(var1);
          }
    
          @JvmStatic
          public final void staticFun() {
             String var1 = "test staticFun()";
             boolean var2 = false;
             System.out.println(var1);
          }
    
          private Companion() {
          }
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }

     

    정리

    • CompanionObjectTest 클래스 자체가 static 이 아님.
      👉 class 자체가 싱글톤은 아님. 
      👉 즉, CompanionObjectTest() 로 생성할 때마다 객체의 주소 값은 다름.
    • CONST_TEST와 같은 const val로 선언된 상수는 static 변수.
    • companion object 내에서 선언된 일반 변수 또는 함수는 static class 인 Companion을 통해 접근 가능. 즉, 변수 및 함수 자체는 static 하지 않음.
    • 단, companion object 내에서 @JVMStatic 또는 @JVMField 을 붙이면 static 

     

    따라서, java 파일에서 CompanionObjectTest 에 접근 시 아래와 같음.

    String s3 = CompanionObjectTest.CONST_STRING;
    String s4 = CompanionObjectTest.staticField;
    CompanionObjectTest.staticFun();
    CompanionObjectTest.nonStaticFun(); // ❌ error: nonStaticMethod() 는 static method 아님!!
    
    CompanionObjectTest.Companion.getNonStaticField();
    CompanionObjectTest.Companion.nonStaticFun();

     


    Reference

    반응형