Kotlin - companion object로 static 메소드, 객체 정의하기

자바는 static 키워드를 제공하며 클래스 내부에 static(정적) 변수 또는 메소드를 정의할 수 있습니다. 일반적으로 아래와 같은 코드로 static 변수 및 메소드를 정의할 수 있습니다.

public class MyClass {
    static String TAG = "MyClass";
    static void createFiles() {
    }
}

외부에서 접근은 이렇게 할 수 있습니다.

MyClass.TAG
MyClass.createFiles()

반면에 kotlin에는 static 키워드를 제공하지 않습니다. 대신 Companion 기능을 제공합니다. 아래 코드는 위와 정확히 같지는 않지만 비슷합니다. 정적(static) 변수인 TAG와 메소드 createFiles을 정의하였습니다.

class MyClass {
    companion object {
        val TAG = "MyClass"
        fun createFiles() {
        }
    }
}

외부에서 접근은 이렇게 할 수 있습니다.

MyClass.TAG
MyClass.Companion.TAG
MyClass.createFiles()
MyClass.Companion.createFiles()

눈치채셨겠지만 MyClass.TAGMyClass.Companion.TAG의 축약형입니다. companion은 MyClass 내부에 Companion이라는 정적(static) 객체를 생성하고 그 객체 안에 TAG 변수가 있습니다. 이를 짧게 MyClass.TAG로 사용할 수 있습니다.

코틀린 코드를 자바로 변환하여 확인하기

위에서 추측한 내용을 코드로 확인해보려면 어떻게해야 할까요? 코틀린 코드가 빌드된 바이트코드를 보고 확인할 수 있겠지만, 바이트 코드는 잘 모르기 때문에 자바코드로 다시 변환해서 확인해보았습니다.

코드를 빌드하면 확장자가 class인 ***.class 파일들이 생성됩니다. 이 파일을 Jadx라는 툴로 java로 변환하였습니다. (Jadx 툴 사용법은 'Android 앱(apk)을 decompile하는 방법'을 참고해주세요)

먼저 코틀린 코드입니다.

package main.kotlin
class MyClass {
    companion object {
        val TAG = "MyClass"
        fun createFiles() {
        }
    }
}

fun main(args: Array<String>) {
    val myClass = MyClass()
    println("TAG: " + MyClass.TAG)
    println("TAG: " + MyClass.Companion.TAG)
    MyClass.createFiles()
    MyClass.Companion.createFiles()
}

이 코드를 빌드하면 3개의 class 파일이 생성됩니다.

  • KotlinKt.class: main이 있는 클래스입니다.
  • MyClass.class: MyClass 클래스입니다.
  • MyClass$Companion.class: MyClass$Companion 클래스입니다.

아래 코드는 KotlinKt.class를 자바로 변환한 코드입니다. MyClass.TAGMyClass.Companion.getTAG()로 변경되었습니다. 이것으로 위에서 말한 Companion 객체의 존재에 대해서 알 수 있습니다.

public final class Kotlin4Kt {
    public static final void main(@NotNull String[] args) {
        Intrinsics.checkParameterIsNotNull(args, "args");
        MyClass myClass = new MyClass();
        System.out.println("TAG: " + MyClass.Companion.getTAG());
        System.out.println("TAG: " + MyClass.Companion.getTAG());
        MyClass.Companion.createFiles();
        MyClass.Companion.createFiles();
    }
}

아래 코드는 MyClass.class를 자바로 변경한 코드입니다. 클래스 내부에 Companion 객체를 static으로 생성하였습니다.

public final class MyClass {
    public static final Companion Companion = new Companion(null);
    @NotNull
    private static final String TAG = TAG;
}

아래 코드는 MyClass$Companion.class를 자바로 변경한 코드입니다. 내부에 변수 TAG를 리턴하는 getTAG()와 createFiles() 메소드가 정의되어 있습니다.

public final class MyClass$Companion {
    ...
    @NotNull
    public final String getTAG() {
        return MyClass.access$getTAG$cp();
    }

    public final void createFiles() {
    }
}

자바에서 코틀린의 Companion 호출하기

코틀린에서 생성된 파일을 자바에서 호출할 때, Companion으로 정의된 부분은 아래처럼 호출할 수 있습니다.

public class Test {
    void callKotlinStatic() {
        System.out.println(MyClass.Companion.getTAG());
        MyClass.Companion.createFiles();
    }
}

하지만 Companion이 붙어 매우 어색합니다. 자바에서 사용하는 스타일대로 쓰려면 어떻게 해야할까요? 코틀린의 파일을 생성해줄 때 다음처럼 변수에는 const를 메소드에는 @JvmStatic를 붙여주세요.

아래 코드처럼 코틀린의 클래스를 정의해주면

class MyClass {
    companion object {
        const val TAG = "MyClass"
        @JvmStatic fun createFiles() {
        }
    }
}

자바에서 아래처럼 익숙한 스타일대로 사용할 수 있습니다. 메소드인 경우 두가지 모두 사용이 가능합니다.

public class Test {
    void callKotlinStatic() {
        System.out.println(MyClass.TAG);
        MyClass.createFiles();
        //System.out.println(MyClass.Companion.getTAG()); // 사용이 안됩니다.
        MyClass.Companion.createFiles();
    }
}

위의 차이점을 보기 위해 코틀린 빌드 산출물인 class파일을 자바로 변환해보았습니다. 코드를 보면 Companinon과 public인 TAG, createFiles가 모두 static으로 존재합니다.

public final class MyClass {
    public static final Companion Companion = new Companion(null);
    @NotNull
    public static final String TAG = "MyClass";

    @JvmStatic
    public static final void createFiles() {
        Companion.createFiles();
    }
}

하지만 MyClass$Companion를 자바로 변환한 코드에는 createFiles()만 존재하고 getTAG가 존재하지 않네요.

public final class MyClass$Companion {
    ....
    @JvmStatic
    public final void createFiles() {
    }
}

@JvmStatic

위에서 사용한 어노테이션 @JvmStatic를 메소드에 붙이면 아래 두개의 위치에 객체를 모두 생성합니다.

  • 클래스의 바로 밑에 정적 메소드 생성
  • Companion 내부에도 객체를 생성

이 때문에, 자바에서 MyClass.createFilesMyClass.Companion.createFiles 모두 사용 가능했습니다.

const 또는 @JvmField

위에서 TAG 앞에 const를 붙였지만 @JvmField를 붙여도 동일하게 처리됩니다. companion의 필드에 이 어노테이션이 붙이면, 클래스 바로 밑에 정적 객체가 정의됩니다.

@JvmStatic@JvmField의 자세한 내용은 static methods를 참고해주세요.

정리

코틀린에서 클래스의 static 메소드 및 변수를 제공하는 Companion에 대해서 알아보았습니다. 자바의 static 키워드와 다르게, 코틀린은 클래스 바로 아래에 static 변수 또는 메소드가 존재하지 않습니다. Companion이라는 static 객체가 존재하고 이 객체 안에 정의한 변수 또는 메소드들이 존재합니다.

자바에서 코틀린에서 작성된 코드를 호출하는 방법에 대해서 알아보았습니다. Companion 없이 사용하기 위해 필요한 annotation에 대해서도 알아보았습니다.

참고

Loading script...
codechachaCopyright ©2019 codechacha