코틀린에는 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.BAR
및 FooKt.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;
}
추가 참고자료: