[Kotlin] Object 키워드 (with companion object)
이번 포스팅에서는 Kotlin에서 object 키워드가 무엇을 의미하고 class와 object는 어떤 차이가 있는지에 대해 알아보겠습니다.
object 키워드의 의미와 사용
코틀린에서 object 키워드는 클래스를 정의하는 동시에 객체를 생성하는 것이라고 볼 수 있습니다.
Kotlin에는 Java에서 쓰이는 static 키워드가 존재하지 않기 때문에 object를 사용해 static의 개념을 표현합니다.
object 키워드는 주로 다음과 같은 경우에 사용됩니다.
1. 싱글톤(Singleton) 클래스 정의
2. 동반객체(companion object) 생성
3. 익명 클래스 생성
각각의 경우에 대해서 아래에서 바로 알아보겠습니다.
1. 싱글톤(Singleton) 클래스 정의
싱글톤(Singleton)은 프로젝트에서 어떤 객체를 매번 생성하지 않고 하나의 객체만 생성해 하나의 객체를 재사용하는 방식으로 싱글톤 패턴이라고도 합니다.
싱글톤으로 생성된 객체는 전역에서 접근할 수 있고, 객체를 단 한 번만 생성하기 때문에 메모리를 효율적으로 사용할 수 있다는 장점이 있습니다.
object RetrofitInstance {
const val BASE_URL = "BASE_URL"
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
}
}
위 코드에서 RetrofitInstance는 class가 아닌 object로 생성되었기 때문에 싱글톤으로 동작하며 한 번의 생성으로 프로젝트 내의 모든 클래스에서 접근이 가능합니다.
2. 동반객체(companion object) 생성
코틀린에는 static 키워드를 제공하지 않지만 static처럼 표현하는 방법으로 companion object(동반 객체)라는 개념이 존재합니다.
companion object는 클래스 내부에 존재하며, 클래스 내부의 companion object는 해당 클래스가 공유하는 메소드나 프로퍼티를 정의할 수 있습니다.
companion object 또한 하나의 객체이므로 객체의 이름을 지정하거나 상속이 가능하고, 하나의 클래스는 최대 한 개의 companion object를 정의할 수 있습니다.
class Number {
companion object {
const val MAX_NUMBER: Int = 1000
fun bar() {
println("Companion object called")
}
}
}
fun main() {
println(Number.MAX_NUMBER) // 1000
A.bar() // "Companion object called"
}
또한, Companion obect를 통해 top-level function을 통해 같은 효과를 낼 수 있습니다. top-level function은 class 내부에 선언된 private property에는 접근할 수 없는 제한을 받지만, Companion object는 class 내부에 접근이 가능합니다.
3. 익명 클래스 생성
익명 클래스는 정의 그대로 이름이 없는 익명의 객체입니다.
한 번만 사용하고 재사용하지 않기 때문에 별도로 이름을 부여하지 않지만 싱글톤과 달리 매번 객체를 생성한다는 점에서 객체를 한 번만 생성하는 싱글톤과 차이가 있습니다.
productListAdapter.setItemClickListener(object : ProductListAdapter.OnItemClickListener {
override fun onClick(v: View, position: Int) {
// 클릭 시 실행
...
}
})
private lateinit var itemClickListener: OnItemClickListener
interface OnItemClickListener {
fun onClick(v: View, position: Int)
}
fun setItemClickListener(onItemClickListener: OnItemClickListener) {
this.itemClickListener = onItemClickListener
Object와 CompanionObject의 차이
1. Object
Object는 클래스 전체가 하나의 Singleton 객체로 선언됩니다.
앱의 여러 곳에서 사용될 수 있는 상수나 유틸리티 메서드가 필요할 때 사용합니다. 특히, 싱글턴 패턴이 필요할 경우에 적합합니다.
- Singleton 형태
- thread-safe하게 생성이 가능하다.
- lazy initialized : 실제로 사용될 때 초기화(initialized) 된다
- const val로 선언된 상수는 static 변수
- object 내부에 선언된 변수와 함수들은 java의 static이 아님
- 단, 아래 케이스들은 static
- const val 로 상수 선언한 것들.
- @JVMStatic 또는 @JVMField의 어노테이션이 붙은 변수 및 함수들
- 단, 아래 케이스들은 static
위와 같은 특징 중에서도 Object declaration의 장점은 thread-safe와 lazy initiallized라고 볼 수 있습니다.
- Thread-safe란? 멀티 스레드 환경에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램 실행에 문제가 없는 것
- lazy initialization란? Object 키워드가 선언된 클래스는 외부에서 객체가 사용되는 시점에 초기화가 이루어진다.
참고로 Object 키워드가 선언된 클래스는 주/부 생성자를 사용할 수 없습니다. 객체 생성과 동시에 생성자 호출 없이 바로 만들어지기 때문입니다.
또한 중첩 object 선언이 가능하며, 클래스나 인터페이스를 상속할 수 있습니다.
Object는 왜 Thread-Safe한걸까?
코틀린에서 obejct를 decompile해보면 인스턴스의 생성과 변수의 초기화는 static 초기화 블록에서 수행된다. 실제로 static 초기화 블록은 클래스가 메모리에 로딩될 때 자동으로 수행되는데, static block은 synchronized키워드 없이도 synchronized 블록 처럼 동작하기 때문에 thread-safe한 것이다.
2. CompanionObject
Companion object는 클래스가 메모리에 적재되면서 함께 생성되는 동반(companion)되는 객체이며, 클래스 내부의 객체 선언을 위한 object키워드입니다.
한마디로 클래스 내부에서 Singleton패턴을 구현하기 위해 사용하며, Object와 달리 일부분이 Singleton 객체로 선언되는 것입니다.
일반적으로 클래스에 연관된 유틸리티 함수나 팩토리 메소드를 정의할 때 사용됩니다.
- companion obejct 는 클래스 내부에 들어가는 블럭이므로 그 자체가 static 은 아님.
- 해당 클래스가 로드될 때 초기화(initialized) 된다.
- const val로 선언된 상수는 static 변수.
- companion object 내부에 선언된 변수와 함수들은 java의 static이 아님
- 단, 아래 케이스들은 static
- const val 로 상수 선언한 것들.
- @JVMStatic 또는 @JVMField의 어노테이션이 붙은 변수 및 함수들
- 단, 아래 케이스들은 static
CompanionObject는 Static처럼 동작하지만, 왜 Static이 아닐까?
CompanionObject는 디컴파일시에 Companion이라는 static 중첩 클래스의 인스턴스가 생성됩니다. 따라서, java의 static 변수, 함수와는 엄연히 다른 개념입니다.
Companion 클래스만 static으로 되어 있으며, Companion 클래스 내부의 변수, 메서드는 static 형태로 존재하지 않는다는 의미입니다.
만약 companion object의 변수 또는 메서드를 static으로 만들고 싶다면 @JvmField, @JvmStatic 어노테이션을 붙이면 static 키워드가 붙으므로, Java의 static 변수, 메서드와 같이 사용 할 수 있습니다.
즉, 정리하면 "Comanion Object를 선언하면, Companion 객체가 생성되는데, 이 객제 자체는 static이다.",
"하지만 내부 프로퍼티는 static이 아니다." 라고 정리해볼 수 있겠습니다.
Object와 CompanionObject는 각각 초기화가 언제될까?
위에서 언급하기를 Object는 실제로 사용될 때 초기화가 되고,
CompanionObject는 해당 클래스가 로드될 때 초기화 된다고 하였습니다.
이게 무슨말일까요?
이해를 돕기 위해 Object와 CompanionObject로 된 클래스를 각각 실행하는 예제를 통해 알아보겠습니다.
1. CompanionObject
class CompanionObjectTest{
companion object{
const val cObjectConstVal=0
val cObjectVal = 1
init {
println("companion object init!")
}
}
}
fun main(){
CompanionObjectTest
}
실제 위 코드를 실행해보면 아래와 같이 "companion object init!"이 찍히며, 클래스 로드 시점에 init이 되는것을 확인하실 수 있습니다. 즉, companion object를 실제 사용하지 않고, 이를 내재하고 있는 클래스만 main()함수에서 호출을 해도 init이 되는 것입니다.
2. Object
class CompanionObjectTest{
companion object{
const val cObjectConstVal=0
val cObjectVal = 1
init {
println("companion object init!")
}
}
object ObjectTest{
const val objectConstVal=0
val objectVal = 1
init {
println("object init!")
}
}
}
fun main(){
CompanionObjectTest
}
위에서 사용했던 클래스 안에 Object 클래스를 추가하였습니다.
main()함수를 실행해볼까요?
companion object의 init()만 호출된 것을 알 수 있는데요.
실제로 object 같은 경우, Object를 실제 사용을 해야 Init이 됩니다.
아래 코드를 확인해보시면 이해가 되실 겁니다.
fun main(){
CompanionObjectTest.ObjectTest
}
main함수에서 ObjectTest를 사용하도록 변경하고 실행해보았습니다.
Object의 init이 호출이 되는 것을 확인할 수 있습니다.
Java 코드에서는 Object와 CompanionObject가 어떻게 표현될까?
위에서 작성한 코드에 대해 디컴파일(Decompile)을 해보았습니다.
CompanionObject
public final class CompanionObjectTest {
public static final int cObjectConstVal = 0;
private static final int cObjectVal = 1;
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
static {
String var0 = "companion object init!";
System.out.println(var0);
}
public static final class Companion {
public final int getCObjectVal() {
return CompanionObjectTest.cObjectVal;
}
private Companion() {
}
}
}
코틀린에서 선언했던 const val, val 로 선언했던 변수들이 static으로 변환되어 상단에서 초기화 되는것을 확인할 수 있었습니다.
하지만, val로 선언했던 cObjectVal의 경우 java의 static은 아닙니다.
실제로 companion object 내에 선언된 일반 변수 또는 함수는 static class인 Companion을 통해 접근이 가능합니다. 즉, 변수 및 함수 자체는 static하지 않다는 얘기입니다.
java 파일에서 CompanionObjectTest 접근시 아래와 같습니다.
String s = CompanionObjectTest.cObjectConstVal
CompanionObjectTest.objectConstVal(); // ❌ error: objectConstVal() 는 static이 아님!!
CompanionObjectTest.Companion.getCObjectVal();
정리해보면, const val은 static 변수이기 때문에 컴파일 타임 (Java컴파일러가 바이트 코드 변환을 할 때)에 값이 이미 결정됩니다.
반면, val은 static 변수가 아니기 때문에 런타임에 값이 결정됩니다.
Object
자 그럼 Object도 Java클래스로 디컴파일을 해봐야겠지요?
public final class CompanionObjectTest {
..
..
public static final class ObjectTest {
public static final int objectConstVal = 0;
private static final int objectVal;
@NotNull
public static final ObjectTest INSTANCE;
public final int getObjectVal() {
return objectVal;
}
private ObjectTest() {
}
static {
ObjectTest var0 = new ObjectTest();
INSTANCE = var0;
objectVal = 1;
String var1 = "object init!";
System.out.println(var1);
}
}
}
companion object와 동일하게 object도 코틀린에서 선언했던 const val, val 로 선언했던 변수들이 static으로 변환이 되는 것을 확인할 수 있었습니다. 하지만 companion object에서 설명한 바와 같이 val로 선언한 부분은 Java의 static이 아닙니다.
따라서, val 또는 일반 함수로 선언된 애들은 static 필드가 아니여서 ISTANCE 객체로 접근이 가능합니다.
Java 파일에서 ObjectTest접근 시 아래와 같습니다.
String s3 = ObjectTest.cObjectConstVal
ObjectTest.objectConstVal(); // ❌ error: objectConstVal() 는 static이 아님!!
ObjectTest.INSTANCE.getCObjectVal();
즉, 다시 정리하면 const val과 val의 진정한 차이점은 "컴파일타임에 값이 결정 되냐" vs "런타임에 값이 결정 되냐" 차이입니다. const val은 실제로 Java컴파일러가 바이트 코드로 변환할 시점에 값이 이미 결정되어지는 것이고, val은 바이트 코드 변환 시점에는 값이 결정 되지 않습니다.
Top Level Property
const val topLevelConstVal:Int = 0
val topLevelVal: Int = 1
val dClass= Dclass(0,1)
fun main(){
}
Top Level Property에 대해서도 디컴파일을 해보겠습니다.
public final class JtmKt {
public static final int topLevelConstVal = 0;
private static final int topLevelVal = 1;
@NotNull
private static final Dclass dClass = new Dclass(0, 1);
}
클래스 이름은 최상위 함수가 들어있던 코틀린의 소스 파일의 이름으로 자동 생성됩니다.
TopLevelProperty는 companion obejct와 동일하게 클래스 로드 시점에 새로운 클래스가 생성되면서 값이 초기화 됩니다.
단, 앞서 설명한바와 같이 val은 런타임에, const val은 컴파일타임에 값이 결정됩니다.
끝.
참고자료
https://develop-oj.tistory.com/47
[안드로이드] 코틀린에서 object의 정의와 사용
이번 포스팅에서는 Kotlin에서 object 키워드가 무엇을 의미하고 class와 object는 어떤 차이가 있는지에 대해 알아보겠습니다. object 키워드의 의미와 사용 코틀린에서 object 키워드는 클래스를 정의하
develop-oj.tistory.com
https://tourspace.tistory.com/109
[Kotlin] 코틀린 object
이 글은 Kotlin In Action을 참고 하였습니다.더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다코틀린은 object란 키워드를 사용합니다.자바에는 이 키워드가 없죠.약간 생소할
tourspace.tistory.com
https://sikdroid.tistory.com/17
코틀린 Object와 Companion Object 차이? - kotlin
오늘은 비슷한거 같으면서 다른 Object declaration와 Companion object의 차이에 대해서 알아보려한다. 보통 Kotlin에서 Java의 static과 같은 정적 변수 및 메서드를 사용하기 위해 보통 object나 Companion object
sikdroid.tistory.com
https://jaeyeong951.medium.com/%EC%BD%94%ED%8B%80%EB%A6%B0-object-companion-object-3237ecea8df4
코틀린 object & companion object
object 키워드는 코틀린에서 singleton 객체를 thread-safe 하게 생성하는 가장 간편한 문법이며, companion object 키워드로 자바의 static 키워드와 동일한 경험을 할 수 있다.
jaeyeong951.medium.com
https://nuritech.tistory.com/18
[Kotlin] static, object, companion object 차이
목차 우리는 보통 kotlin에서 java의 static 변수 또는 메서드를 사용하기 위해 object 키워드 또는 companion object를 사용한다. 아래처럼 말이다. 아래와 같이 object 를 사용하는 것을 object declaration이라고
nuritech.tistory.com