[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

반응형