/ KOTLIN

코틀린에는 static이 없다? - companion object

코틀린에는 정적(static) 변수 혹은 메소드가 없고, 대신 패키지 내에 함수를 선언하여 사용할 수 있습니다.

예를 들어 자바에서 다음과 같이 사용했다면,

[Foo.java]

package foo.bar;

class Foo {

    public static final String BAR = "bar";

    public static void baz() {
        // Do something
    }
}

코틀린에서는 다음과 같이 함수를 정의하여 사용할 수 있습니다.

[Foo.kt]

package foo.bar

const val BAR = "bar"

fun baz() {
    // Do something
}

프로젝트 전체를 코틀린으로 작성하는 경우라면 위와 같이 함수를 정의하여 사용해도 큰 문제가 없습니다.

하지만, 프로젝트의 일부에만 코틀린을 사용한 경우 자바에서 코틀린에서 만든 함수를 사용하려면 다소 깔끔하지 않은 방법을 사용해야 합니다.

위와 같이 Foo.kt 파일에 정의한 속성 및 함수는 자바에서 각각 FooKt.BARFooKt.baz()로 접근할 수 있습니다. 파일 이름과 동일한 이름의 클래스 선언 없이 선언된 필드나 메서드를 자바에서 접근할 수 있도록 하는 과정에서 파일 이름 뒤에 kt라는 접미사를 추가하기 때문입니다.

다음은 자바에서 위에서 정의한 필드 및 메서드를 사용하는 예를 보여줍니다.

public void doSomething(Bundle args) {

    // args 내에 "bar" 키로 정의되어 있는 데이터 추출
    int bar = args.getInt(FooKt.BAR);

    // baz() 메서드 수행
    FooKt.bar();
}

물론, 이러한 규칙을 무시하고 자신이 원하는 이름으로 생성되도록 하려면 파일의 맨 첫 줄에 @file:JvmName(name: String)을 사용하면 됩니다. 다음과 같이 사용하면 FooKt 대신 Foo를 사용하여 필드 및 메서드에 접근할 수 있습니다.

@file:JvmName("Foo") // 'Foo' 라는 이름으로 자바에서 접근할 수 있도록 함

package foo.bar

const val BAR = "bar"

fun baz() {
    // Do something
}

object를 사용하면 기존에 자바에서 사용하던 방식과 매우 유사한 형태로 구현이 가능합니다.

package foo.bar

object Foo {

    const val BAR = "bar"

    fun baz() {
        // Do something
    }
}

그런데, 이를 안드로이드에 적용하려고 보니…

안드로이드에서 정적 변수 및 메서드를 사용하는 경우는 크게 다음과 같습니다.

  • 액티비티/프래그먼트의 인텐트 Extra로 사용하는 키
  • 로그 출력을 위한 태그(Tag) 이름 정의
  • 뷰 내부에서 사용하는 고정된 길이 값 (너비, 높이 등)
  • 각종 유틸리티 클래스 내 메서드

위 경우들은 모두 선언된 클래스와 밀접하게 관련된 경우로, 클래스 외부에 별도로 선언하기엔 모호한 녀석들입니다.

물론, 다음과 같이 파일 내에 함수/필드 + 클래스를 정의할 수도 있습니다.

[Foo.kt]

package foo.bar

const val BAR = "bar"

fun baz() {
    // Do something
}

// Foo 클래스 선언
class Foo {
    ...
}

하지만, 이렇게 할 경우 처음과 마찬가지로 자바에서 위에 정의된 함수 및 필드에 접근하려면 Foo.BAR, Foo.baz() 대신 FooKt.BAR, FooKt.baz()를 사용해야 합니다.

object를 사용하면 해결할 수 있을 것 같지만, 클래스 및 object 이름은 중복될 수 없으므로 위와 같이 외부에 선언하는 것은 불가합니다. 그렇다면 이 문제는 어떻게 해결할 수 있을까요?

자바와 동일한 방식으로 사용하기: companion object

companion object를 사용하면 위에서 나열했던 문제 없이 자바에서 정적 변수/메서드를 사용했던 것과 동일하게 사용할 수 있습니다. 다음은 사용 예를 보여줍니다.

class Foo {

    companion object {

        const val BAR = "bar"

        fun baz() {
            // Do something
        }
    }
}

companion object 내에 선언된 속성과 함수는 {클래스 이름}.{필드/함수 이름} 형태로 바로 호출할 수 있습니다. 즉, 위의 Foo클래스 내 companion object에 선언된 baz() 함수는 다음과 같이 호출 가능합니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // ... Something ...

    // Foo.baz() 함수 호출
    Foo.baz()
}

자바에선 저렇게 못 쓰는데요? - @JvmStatic

companion object를 사용하여 위와 같이 구성한 코드를 자바에서 사용하려면 속성 및 함수가 자바의 필드/메서드로 해석되도록 알려주어야 합니다.

const 선언이 되어 있는 속성은 별도 처리가 필요 없이 자바에서도 동일하게 사용 가능하며, 함수는 @JvmStatic 어노테이션을 사용하여 자바에서 정적 메서드로 사용할 수 있게 합니다.

class Foo {

    companion object {

        // 자바에서도 동일하게 Foo.BAR 로 접근 가능
        const val BAR = "bar"

        // 자바에서 정적 메서드(static method)처럼 사용할 수 있도록 함
        @JvmStatic fun baz() {
            // Do something
        }
    }
}

위와 같이 처리해 두면, 자바의 정적 필드 및 메서드를 사용하는 것과 동일하게 사용할 수 있습니다.

public void doSomething(Bundle args) {

    // args 내에 "bar" 키로 정의되어 있는 데이터 추출
    int bar = args.getInt(Foo.BAR);

    // baz() 메서드 수행
    Foo.bar();
}

Primitive type이나 String이 아닌 타입은 어떻게 처리하나요? - @JvmField

const 키워드는 Primitive type과 String 에만 사용 가능합니다. 따라서 이에 해당하지 않는 타입의 객체를 자바에서 정적 필드처럼 사용하려면 @JvmField 어노테이션을 사용해야 합니다.

다음은 Bar 타입의 속성 BAR를 자바의 정적 필드처럼 사용할 수 있도록 @JvmField 어노테이션을 사용한 예를 보여줍니다.

class Foo {

    companion object {
        @JvmField BAR = Bar()
    }
}

class Bar {

}

위와 같이 처리하면 다음과 같이 자바 코드에서 BAR 속성을 정적 필드처럼 사용할 수 있습니다.

public void doSomething(Bundle args) {

    // Foo.BAR 필드 접근
    Bar myBar = Foo.BAR;
}

추가 참고자료:

kunny

커니

안드로이드와 오픈소스, 코틀린(Kotlin)에 관심이 많습니다. 전 한국 GDG 안드로이드 운영자 및 GDE 안드로이드로 활동했으며, 현재 구글에서 애드몹 기술 지원을 담당하고 있습니다.

Read More