자바는 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.TAG
는 MyClass.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.TAG
가 MyClass.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.createFiles
와 MyClass.Companion.createFiles
모두 사용 가능했습니다.
const 또는 @JvmField
위에서 TAG 앞에 const
를 붙였지만 @JvmField
를 붙여도 동일하게 처리됩니다.
companion의 필드에 이 어노테이션이 붙이면, 클래스 바로 밑에 정적 객체가 정의됩니다.
@JvmStatic
과 @JvmField
의 자세한 내용은
static methods를 참고해주세요.
정리
코틀린에서 클래스의 static 메소드 및 변수를 제공하는 Companion에 대해서 알아보았습니다. 자바의 static 키워드와 다르게, 코틀린은 클래스 바로 아래에 static 변수 또는 메소드가 존재하지 않습니다. Companion이라는 static 객체가 존재하고 이 객체 안에 정의한 변수 또는 메소드들이 존재합니다.
자바에서 코틀린에서 작성된 코드를 호출하는 방법에 대해서 알아보았습니다. Companion 없이 사용하기 위해 필요한 annotation에 대해서도 알아보았습니다.
참고
Related Posts
- Kotlin - 배열에서 최소 값, 최대 값 찾기
- Kotlin - 2차원 배열 선언, 초기화 방법
- Kotlin - 배열 선언, 초기화 방법
- Kotlin - 리스트, 배열 길이 가져오기
- Kotlin - 리스트에서 최대, 최소 값 찾기
- Kotlin - for 반복문, 배열/리스트 순회
- Kotlin - Timer, 주기적으로 함수 실행
- Kotlin - sleep, 쓰레드 몇 초 지연
- Kotlin - Thread 생성 및 실행
- Kotlin에서 정규표현식 사용하기
- Kotlin - 문자열 길이 계산
- Kotlin - 문자열 비교 방법(equals, ==, compareTo)
- Kotlin - 2개의 배열 하나로 합치기
- Kotlin - 2개의 List 하나로 합치기
- Kotlin - 디렉토리의 모든 파일 리스트 출력
- Kotlin - 리스트 정렬 방법 (sort, sortBy, sortWith)
- Kotlin - 문자열 뒤집기 (Reverse String)
- Kotlin - 랜덤 숫자 생성 (Random, SecureRandom)
- Kotlin - Range, 숫자 범위 표현
- Kotlin - 음수를 양수로 변환, math.abs()
- Kotlin - List를 Set로 변환
- Kotlin - Set를 List로 변환
- Kotlin - 문자열에서 숫자(int)만 추출하는 방법
- Kotlin - Map을 List로 변환하는 방법
- Kotlin - File, Directory가 존재하는지 확인
- Kotlin - List를 Map으로 변환
- Kotlin - List의 중복 요소 제거
- Kotlin - List를 Array로 변환
- Kotlin - 엘비스 연산자 (Elvis Operation)
- Kotlin - Array를 List로 변환
- Kotlin - String을 Float으로 변환
- Kotlin - String을 Double으로 변환
- Kotlin - String을 Int로 변환
- Kotlin - String을 Long으로 변환
- Kotlin - String Null 또는 Empty 체크