Object는 Kotlin에서 싱글톤 객체를 사용하기 위해 사용할 수 있으며, Companion object는 클래스 내에서 함수나 상수들을 정의해서 Java에서 Static 하게 사용 가능한 것으로 알고 있었고 둘의 차이에 대해서 깊게 생각해보지 않았던 것 같아서 정리해보려고 한다.
kotlin의 object 선언과 companion object는 java의 static과 동일하지 않다.
1) Object 선언
object ClassA {
fun testA(){
}
}
Object 키워드로 선언된 클래스는 주 / 부 생성자를 사용할 수 없다. 객체 생성과 동시에 생성자 호출 없이 바로 만들어지기 때문이다. 또한, 중첩 object 선언이 가능하며, 클래스나 인터페이스를 상속이 가능하다.
2) companion object
class ClassB{
companion object {
fun create()
}
}
companion object는 클래스 인스턴스 없이 어떤 클래스 내부에 접근하고 싶을 때, 선언한다. 클래스당 하나만 사용할 수 있고, Object 선언과 같이 생성자를 가질 수 없으며, static으로 선언되는 것이 아니라 런타임시에 실제 객체의 인스턴스로 실행된다.
Object 선언은 클래스 전체가 하나의 싱글톤 객체로 선언되지만,
companion object는 클래스 내에 일부분이 싱글톤 객체로 선언되는 것이다.
ex)
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()")
}
}
class CompanionObjectTest {
companion object {
const val CONST_TEST = 2
fun test() { }
}
}
1) Object 코드
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()")
}
}
java로 변환된 코드를 보자. (kotlin bytecode를 디컴파일해서 얻은 코드이다.)
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 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";
}
}
[정리]
// 아래 3가지는 static 필드 처리된 것.
String s = ObjectTest.CONST_STRING;
String s2 = ObjectTest.staticField;
ObjectTest.staticFun();
// 아래 2가지는 static 이 아니기 때문에, static 변수인 INSTANCE 로 접근해야 함.
ObjectTest.INSTANCE.getNonStaticField();
ObjectTest.INSTANCE.nonStaticFun();
2) companion object 코드
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를 디컴파일해서 얻은 코드이다.)
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 nonStaticMethod() {
String var1 = "test nonStaticMethod()";
boolean var2 = false;
System.out.println(var1);
}
@JvmStatic
public static final void staticMethod() {
String var1 = "test staticMethod()";
boolean var2 = false;
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
[정리]
String s3 = CompanionObjectTest.CONST_STRING;
String s4 = CompanionObjectTest.staticField;
CompanionObjectTest.staticFun();
CompanionObjectTest.nonStaticFun(); // ❌ error: nonStaticMethod() 는 static method 아님!!
CompanionObjectTest.Companion.getNonStaticField();
CompanionObjectTest.Companion.nonStaticFun();
[클래스 로드]
JVM이 클래스 파일을 읽어들이고, 메모리에 로딩하는 과정을 말한다. 클래스는 처음 사용될 때가 아니라 필요할 때마다 로드된다. 클래스 로드는 아래와 같은 경우에 발생할 수 있다.
[클래스 사용]
클래스 사용은 이미 로드된 클래스에서 메모리를 사용하는 것을 말한다. 클래스가 사용될 때는 해당 클래스의 인스턴스를 생성하거나, 정적 메소드나 변수를 호출하거나, 상속 구조에서 서브 클래스에서 상위 클래스를 참조하는 등의 경우가 있다.
즉, 클래스 로드는 메모리에 로딩하는 과정이고 클래스 사용은 메모리에 로드된 클래스를 실제로 사용하는 것이다. 클래스 로드는 클래스가 사용되기 전에 한 번만 발생하며, 클래스 사용은 필요에 따라 여러 번 발생할 수 있다.