개요
코틀린(Kotlin)의 Companion object는 단순히 자바(Java)의 static 키워드를 대체하기 위해서 탄생했을까요? 이 갑작스러운 질문은 코틀린에서 왜 static을 안 쓰게 되었는지 이해하는 데 큰 도움이 될 수 있습니다.
자바의 static 키워드는 클래스 멤버(member)임을 지정하기 위해 사용합니다. static이 붙은 변수와 메소드를 각각 클래스 변수, 클래스 메소드라 부릅니다. 반면, static이 붙지 않은 클래스 내의 변수와 메소드는 각각 인스턴스 변수, 인스턴스 메소드라 합니다. static이 붙은 멤버는 클래스가 메모리에 적재될 때 자동으로 함께 생성되므로 인스턴스 생성 없이도 클래스명 다음에 점(.)을 쓰면 바로 참조할 수 있습니다.
public final class MyClass{ static public final String TEST = "test"; //클래스 변수 static public method(int i):int{ //클래스 메소드 return i + 10 } } System.out.println(MyClass.TEST); //test System.out.println(MyClass.method(1)); //11
자바(Java) 개발 경험이 있는 사람들에게는 코틀린(Kotlin)에 static이 없다는 사실에 당황할 수 있습니다. 대신 코틀린은 companion object라는 키워드와 함께 블록({}) 안에 멤버를 구성합니다.
class MyClass{ companion object{ val TEST = "test" fun method(i:Int) = i + 10 } } fun main(args: Array<String>){ println(MyClass.TEST); //test println(MyClass.method(1)); //11 }
여기까지만 보면 단순히 static 키워드 대신 companion object 블록으로 대체한 느낌만 듭니다. 그러면 아래처럼 static 키워드를 써도 문제없다고 판단할지도 모르겠습니다.
class MyClass{ static val TEST = "test" static fun method(i:int) = i + 10 } fun main(args: Array<String>){ println(MyClass.TEST); //test println(MyClass.method(1)); //11 }
코틀린은 static을 버리고 companion object를 도입했으며 더욱 명쾌하고 멋진 방법으로 문제를 해결하는 데 도움을 주도록 했습니다. 이 글은 companion object와 static의 차이점을 이해하고 static의 한계를 파악하는 동시에 companion object 학습에 도움을 주는 데 목적이 있습니다.
코틀린의 class 키워드 기초
companion object를 다루기 전에 먼저 코틀린의 class와 object를 먼저 이해해야 할 것 같습니다. 사실 이 주제만으로도 엄청난 분량이지만 여기서는 간단하게 사용법만 소개합니다.
class WhoAmI(private val name:String){ fun myNameIs() = "나의 이름은 ${name}입니다." } fun main(args: Array<String>){ val m1 = WhoAmI("영수") val m2 = WhoAmI("미라") println(m1.myNameIs()) //나의 이름은 영수입니다. println(m2.myNameIs()) //나의 이름은 미라입니다. }
클래스 WhoAmI를 정의하면서 생성자의 인자로 val name:String을 받습니다. 자바에 익숙하나 코틀린을 처음 접한 분에게는 이게 무슨 생성자인가 생각이 들 수 있습니다. WhoAmI 클래스를 자바 개발자가 이해하기 쉽게 풀어 쓰면 다음과 같습니다.
class WhoAmI{ private val name:String constructor(name:String){ this.name = name } fun myNameIs() = "나의 이름은 ${name}입니다." }
위처럼 클래스 생성자는 constructor 키워드를 써서 정의합니다. 그리고 생성자에서 받은 인자 name:String의 값을 속성인 this.name을 통해 private val name:String에 할당하고 있습니다. 이 코드는 자세히 보면 너무 중복이 많습니다. 그래서 코틀린 언어 설계자는 이를 단순화했습니다. 즉, 클래스를 정의하자마자 바로 생성자 및 속성까지 정의한 것입니다. 덕분에 코틀린으로 코드를 짜면 자바보다 훨씬 짧아집니다(이것 때문만은 아니지만 코틀린으로 코딩하면 경험상 코딩량이 최소 50% 이상 줄어드는 것 같습니다. 아니면 그보다 더….).
각설하고, 여기서 중요한 것은 클래스로부터 객체를 생성하기 위해 val m1 = WhoAmI(“영수”)처럼 한다는 점입니다. 자바는 객체 생성을 위해 new 키워드를 쓰지만 코틀린에서는 new 키워드 없이 클래스 명 뒤에 괄호()를 붙인다는 점을 기억하세요(코틀린에서 new를 쓰지 않는다는 점이 과연 무슨 의미인지 생각해보는 것도 나쁘지 않을 것 같습니다). 마치 함수 호출처럼요.
코틀린의 object 키워드 기초
코틀린에는 자바에 없는 독특한 싱글턴(singletion; 인스턴스가 하나만 있는 클래스) 선언 방법이 있습니다. 아래처럼 class 키워드 대신 object 키워드를 사용하면 됩니다.
object MySingleton{ val prop = "나는 MySingleton의 속성이다." fun method() = "나는 MySingleton의 메소드다." } fun main(args: Array<String>){ println(MySingleton.prop); //나는 MySingleton의 속성이다. println(MySingleton.method()); //나는 MySingleton의 메소드다. }
object는 특정 클래스나 인터페이스를 확장(var obj = object:MyClass(){} 또는 var obj = object:MyInterface{})해 만들 수 있으며 위처럼 선언문이 아닌 표현식(var obj = object{})으로 생성할 수 있습니다. 싱글톤이기 때문에 시스템 전체에서 쓸 기능(메소드로 정의)을 수행하는 데는 큰 도움이 될 수 있지만, 전역 상태를 유지하는 데 쓰면 스레드 경합 등으로 위험할 수 있으니 주의해서 사용해야 합니다.
언어 수준에서 안전한 싱글턴을 만들어 준다는 점에서 object는 매우 유용합니다.
이 글은 object가 아닌 companion object를 다룰 것이므로 이 정도만 소개하는 것으로 마무리 짓겠습니다. 앞서 미리 말씀드린 companion object는 클래스 내부에 정의되는 object의 특수한 형태입니다. 이제 companion object를 자세히 알아봅시다.
Companion object는 static이 아닙니다.
사실 코틀린 companion object는 static이 아니며 사용하는 입장에서 static으로 동작하는 것처럼 보일 뿐입니다. 다음 코드를 보세요.
class MyClass2{ companion object{ val prop = "나는 Companion object의 속성이다." fun method() = "나는 Companion object의 메소드다." } } fun main(args: Array<String>) { //사실은 MyClass2.맴버는 MyClass2.Companion.맴버의 축약표현이다. println(MyClass2.Companion.prop) println(MyClass2.Companion.method()) }
MyClass2 클래스에 companion object를 만들어 2개의 멤버를 정의했습니다. 이를 사용하는 main() 함수를 보면 이 멤버에 접근하기 위해 클래스명.Companion형태로 쓴 것을 확인할 수 있습니다. 이로써 유추할 수 있는 것은 companion object{}는 MyClass2클래스가 메모리에 적재되면서 함께 생성되는 동반(companion)되는 객체이고 이 동반 객체는 클래스명.Companion으로 접근할 수 있다는 점입니다(클래스와 동반자라고 하면 정감이 가려나요?).
fun main(args: Array<String>) { //사실은 MyClass2.맴버는 MyClass2.Companion.맴버의 축약표현이다. println(MyClass2.prop) println(MyClass2.method()) }
위 코드에서 MyClass2.prop와 MyClass2.method()는 MyClass2.Companion.prop과 MyClass2.Companion.method() 대신 쓰는 축약 표현일 뿐이라는 점을 이해해야 합니다. 언어적으로 지원하는 축약 표현 때문에 companion object가 static으로 착각이 드는 것입니다.
Companion object는 객체입니다.
Companion object에서 기억해야 할 중요한 점은 객체라는 것입니다. 그래서 다음과 같은 코딩이 가능해 집니다.
class MyClass2{ companion object{ val prop = "나는 Companion object의 속성이다." fun method() = "나는 Companion object의 메소드다." } } fun main(args: Array<String>) { println(MyClass2.Companion.prop) println(MyClass2.Companion.method()) val comp1 = MyClass2.Companion //--(1) println(comp1.prop) println(comp1.method()) val comp2 = MyClass2 //--(2) println(comp2.prop) println(comp2.method()) }
위 코드에서 주석 (1)을 확인해 주세요. companion object는 객체이므로 변수에 할당할 수 있습니다. 그리고 할당한 변수에서 점(.)으로 MyClass2 정의된 companion object의 맴버에 접근할 수 있습니다. 이렇게 변수에 할당하는 것은 자바의 클래스에서 static 키워드로 정의된 멤버로는 불가능한 방법입니다.
위 코드에서 주석 (2)는 .Companion을 빼고 직접 MyClass2로 할당한 것입니다. 이것도 또한 MyClass2에 정의된 companion object입니다. 위에서 MyClass2.Companion.prop 대신 MyClass2.prop로 해도 같다는 점을 생각해보면 쉽게 가능함을 유추할 수 있습니다. 꼭 기억해야 할 것은 클래스 내 정의된 companion object는 클래스 이름만으로도 참조 접근이 가능합니다. 이 특징은 코틀린 설계자의 신의 한 수인지도 모릅니다(왜일까요? ^^).
여기서 알 수 있듯이 static키워드만으로는 클래스 멤버를 companion object처럼 하나의 독립된 객체로 여겨질 수 없겠죠? 이것도 또한 static과 큰 차이점이기도 합니다.
Companion object에 이름을 지을 수 있습니다.
companion object의 기본 이름은 Companion입니다. 앞서 MyClass2.Companion.prop처럼 사용할 수 있음을 기억해 보시면 이해가 됩니다. 이 이름은 바꿀 수 있습니다.
class MyClass3{ companion object MyCompanion{ // -- (1) val prop = "나는 Companion object의 속성이다." fun method() = "나는 Companion object의 메소드다." } } fun main(args: Array<String>) { println(MyClass3.MyCompanion.prop) // -- (2) println(MyClass3.MyCompanion.method()) val comp1 = MyClass3.MyCompanion // -- (3) println(comp1.prop) println(comp1.method()) val comp2 = MyClass3 // -- (4) println(comp2.prop) println(comp2.method()) val comp3 = MyClass3.Companion // -- (5) 에러발생!!! println(comp3.prop) println(comp3.method()) }
위 코드에서 주석 (1)을 확인해 주세요. companion object 이름을 MyCompanion으로 지었음을 확인하세요. 주석 (2), (3)을 보시면 이제 기본 이름인 Companion 대신 MyCompanion을 사용할 수 있습니다.
그러나 여전히 주석 (4)를 보시면 생략할 수 있습니다.
하지만 주석 (5) 처럼 기존 이름인 Companion을 쓰면 Unresolved reference: Companion 에러가 납니다.
val comp4:MyClass3.MyCompanion = MyClass3 println(comp4.prop) println(comp4.method())
위 코드처럼 타입 추론(참고로 코틀린은 타입 추론 때문에 코드량이 급격히 줄어듭니다)을 사용하지 않고 명시적으로 타입을 정하면 MyClass3의 결과는 MyClass3.MyCompanion이기 때문에 이렇게 사용해도 컴파일 에러 없이 정상 동작할 수 있음을 유추할 수 있습니다.
클래스내 Companion object는 딱 하나만 쓸 수 있습니다.
클래스 내에 2개 이상 companion object를 쓰는 것은 안 됩니다. 지금까지 학습한 것을 유추해 보면 당연한 건데 코틀린은 클래스 명만으로 companion object 객체를 참조할 수 있기 때문에 한 번에 2개를 참조하는 것은 애초부터 불가능한 것이지요.
class MyClass5{ companion object{ val prop1 = "나는 Companion object의 속성이다." fun method1() = "나는 Companion object의 메소드다." } companion object{ // -- 에러발생!! Only one companion object is allowed per class val prop2 = "나는 Companion object의 속성이다." fun method2() = "나는 Companion object의 메소드다." } }
위처럼 만들면 Only one companion object is allowed per class 에러가 발생할 것입니다.
class MyClass5{ companion object MyCompanion1{ val prop1 = "나는 Companion object의 속성이다." fun method1() = "나는 Companion object의 메소드다." } companion object MyCompanion2{ // -- 에러발생!! Only one companion object is allowed per class val prop2 = "나는 Companion object의 속성이다." fun method2() = "나는 Companion object의 메소드다." } }
위 코드처럼 companion object 이름을 별도로 부여해도 마찬가지입니다.
덕분에 자바에서 static 멤버를 클래스에 아무 데나 쓰는 게 제약이 없었다면 코틀린에서는 자동으로 한곳에 모이게 됩니다. 물론 이 목적으로 companion object를 한 개만 있도록 코틀린이 설계된 것은 아닙니다.
인터페이스 내에도 Companion object를 정의할 수 있습니다.
코틀린 인터페이스 내에 companion object를 정의할 수 있습니다. 덕분에 인터페이스 수준에서 상수항을 정의할 수 있고, 관련된 중요 로직을 이곳에 기술할 수 있습니다. 이 특징을 잘 활용하면 설계하는 데 도움이 될 것입니다.
interface MyInterface{ companion object{ val prop = "나는 인터페이스 내의 Companion object의 속성이다." fun method() = "나는 인터페이스 내의 Companion object의 메소드다." } } fun main(args: Array<String>) { println(MyInterface.prop) println(MyInterface.method()) val comp1 = MyInterface.Companion println(comp1.prop) println(comp1.method()) val comp2 = MyInterface println(comp2.prop) println(comp2.method()) }
이제 위 코드가 이해되시죠? 완벽히 이해가 안 되면 다시 위에서부터 차근차근 읽으세요.
상속 관계에서 Companion object 멤버는 같은 이름일 경우 가려집니다(섀도잉, shadowing).
부모 클래스를 상속한 자식 클래스에 모두 companion object를 만들고 같은 이름의 멤버를 정의했다고 가정합니다. 이때, 자식 클래스에서 이 멤버를 참조하면 부모의 멤버는 가려지고 자식 자신의 멤버만 참조할 수 있습니다. 말이 어려우니 코드로 이해해 보겠습니다.
open class Parent{ companion object{ val parentProp = "나는 부모값" } fun method0() = parentProp } class Child:Parent(){ companion object{ val childProp = "나는 자식값" } fun method1() = childProp fun method2() = parentProp } fun main(args: Array<String>) { val child = Child() println(child.method0()) //나는 부모값 println(child.method1()) //나는 자식값 println(child.method2()) //나는 부모값 }
위 코드처럼 부모/자식의 companion object의 멤버가 다른 이름이라면 자식이 부모의 companion object 멤버를 직접 참조할 수 있습니다. 하지만 같은 이름이면 어떻게 될까요? 다음 코드를 봅시다.
open class Parent{ companion object{ val prop = "나는 부모" } fun method0() = prop //Companion.prop과 동일 } class Child:Parent(){ companion object{ val prop = "나는 자식" } fun method1() = prop //Companion.prop 과 동일 } fun main(args: Array<String>) { println(Parent().method0()) //나는 부모 println(Child().method0()) //나는 부모 println(Child().method1()) //나는 자식 println(Parent.prop) //나는 부모 println(Child.prop) //나는 자식 println(Parent.Companion.prop) //나는 부모 println(Child.Companion.prop) //나는 자식 }
위 코드에서 부모/자식 모두 자신의 companion object의 prop의 값을 반환하고 있습니다. 같은 이름(prop)을 사용했다는 것을 확인하세요. main() 함수의 결과를 보면 자식클래스의 companion object 속성인 prop이 부모에서 정의 되어 있지만 가려져서 무시되는 것을 볼 수 있습니다.
여기서 조금 헷갈릴 수 있는 부분은 Child().method0()의 결과일 것입니다. method0() 메소드는 Parent클래스 것입니다. 그래서 부모의 companion object의 prop값인 “나는 부모”가 출력되었습니다.
이제 위 코드를 조금 변경해 보겠습니다. 자식클래스의 companion object에 이름을 부여합니다.
open class Parent{ companion object{ val prop = "나는 부모" } fun method0() = prop } class Child:Parent(){ companion object ChildCompanion{ // -- (1) ChildCompanion로 이름을 부여했어요. val prop = "나는 자식" } fun method1() = prop fun method2() = ChildCompanion.prop fun method3() = Companion.prop } fun main(args: Array<String>) { val child = Child() println(child.method0()) //나는 부모 println(child.method1()) //나는 자식 println(child.method2()) //나는 자식 println(child.method3()) // -- (2) }
위 코드에서 주석 (1)에 자식 클래스의 companion object에 ChildCompanion로 이름을 부여했습니다. 그리고 자식 클래스에 3개의 메소드를 정의했습니다. child.method0()은 부모의 method이므로 어렵지 않게 “나는 부모”가 출력된 것을 예상할 수 있습니다. 그리고 child.method1()과 child.method2()도 역시 자식의 companion object의 속성을 가리킨다는 것을 알 수 있습니다.
이제 진짜 문제인데, 주석(2)는 어떤 결과가 나올까요? 답부터 말씀드리면 “나는 부모”입니다. 자식의 companion object를 뛰어넘어서 부모의 companion object에 직접 접근이 가능하게 되었습니다. fun method3() = Companion.prop에서 Companion.prop를 하면 이때 Companion은 자식 것이 아닙니다. 왜냐하면 자식은 ChildCompanion로 이름을 바꿨으니깐요. 그래서 여기서 Companion은 부모가 됩니다.
아래 코드에서는 부모의 companion object마저 이름을 붙여봅니다.
open class Parent{ companion object ParentCompanion{ // -- (1) ParentCompanion로 이름을 부여했어요. val prop = "나는 부모" } fun method0() = prop } class Child:Parent(){ companion object ChildCompanion{ val prop = "나는 자식" } fun method1() = prop fun method2() = ChildCompanion.prop fun method3() = Companion.prop // -- (2) Unresolved reference: Companion 에러!! }
위 코드에서 주석(1)을 보시면 부모 companion object의 이름을 ParentCompanion로 했습니다. 그러자마자 주석(2) 부분은 컴파일 에러가 납니다. 에러가 안 날려면 fun method3() = Companion.prop이 아닌 fun method3() = ParentCompanion.prop 바꿔야 합니다.
이 실험을 통해 부모/자식의 companion object에 정의된 멤버는 자식 입장에서 접근할 수 있지만, 같은 이름을 쓰면 섀도잉(shadowing) 되어 감춰진다는 점을 알 수 있었습니다.
다형성 문제
companion object 문제가 아닌 코드를 보겠습니다. 이 글의 보너스 파트 정도로 봐주세요.
open class Parent{ companion object{ val prop = "나는 부모" } open fun method() = prop //Companion.prop과 동일 } class Child:Parent(){ companion object{ val prop = "나는 자식" } override fun method() = prop //Companion.prop 과 동일 } fun main(args: Array<String>) { println(Parent().method()) //나는 부모 println(Child().method()) //나는 자식 val p:Parent = Child() println(p.method()) // -- (1) }
클래스 정의를 먼저 자세히 보면 Parent의 method() 메소드가 open이고 Child에서 이를 override했습니다. main() 함수에서 첫 번째와 두 번째 실행 결과는 쉽게 예측이 됩니다. 하지만 주석(1)의 결과는 무엇일까요? 분명 Child 클래스의 객체를 생성했으나 Parent 부모로 형 변환 했습니다. 그럼 이때 호출한 method() 결과는 대체 무엇인가요? 부모 형이니 “나는 부모”라고 출력할까요? 아니면 태생이 자식이니 “나는 자식”일까요?
이 문제는 compaion object문제가 아닙니다. 게다가 코틀린 문제도 아닙니다. 사실 이는 객체지향 언어에서 약속한 다형성(Polymorphism)에 대한 질문입니다. 다형성은 두 가지를 만족해야 합니다.
- 대체 가능성(substitution) – 어떤 형을 요구한다면 그 형의 자식형으로 그 자리를 대신할 수 있다.
- 내적 동질성(internal identity) – 객체는 그 객체를 참조하는 방식에 따라 변화하지 않는다. 즉 업다운캐스팅해도 여전히 최초 생성한 그 객체라는 것입니다.
이 두 가지 조건을 만족하면 다형성을 만족한다고 볼 수 있고 거꾸로 다형성을 만족하면 객체지향 언어라고 볼 수 있습니다. 다형성의 1번 조건인 대체 가능성 때문에 var c:Parent = Child()를 할 수 있었던 것입니다. 그리고 2번 조건인 내적 동질성으로 인해 아무리 자식을 부모 형으로 대체해도 자식은 자식이죠. 태어날 때의 본질은 어디 가지 않습니다! 그래서 p.method()의 결과는 Child().method()결과와 같습니다. 그러므로 결과는 “나는 자식” 입니다.
정리하며
조금 코틀린 companion object에 대해서 이해가 되셨나요? 자바의 static과는 다르며 더 많은 일을 할 수 있다는 것도 느껴지시나요? 하지만 이제 맛보기만 했을 뿐입니다. companion object에 대해서 더 학습이 필요하지만, 글이 많이 길어지는 관계로 이 정도로 마무리 짓고 다음에 2번째 글을 올리도록 하겠습니다. 긴 글 읽어주셔서 감사하며, 부족한 점 있거나 잘못된 내용이 있다면 댓글 부탁드립니다.
다음글 : [kotlin] Companion Object (2) – 중첩클래스(Nested Class)와 내부클래스(Inner Class)
안녕하세요. 글 정말 잘 보았습니다! 혹시 글에 하얀색 배경색에 회색 글자를 넣으신 건 강조를 위하신 걸까요? 글이 잘 보이지 않아 매번 글을 드래그 하면서 보아야 해서요 ㅠ
아, 그건 아니고, 홈페이지 개편을 하면서 기존 테마가 흰색바탕이어서 그 시절 제작된 컨텐츠가 지금 다크테마에는 어울리지 않게 된 것입니다.
하나하나 컨텐츠를 수정할건 아니고, 앞으로 새 글을 많이 써서 희석(?)할 계획입니다 ^^
안녕하세요.
본문의 내용 중 ‘코틀린의 class 키워드 기초’ (개요 다음에 있는 내용) 부분의 내용 중에 java 클래스 생성자에 대해 설명할 때
constructor(name:String){
this.name = name
}
이렇게 코드를 넣으셨던데, java에서 생성자는 클래스 네임과 똑같이 지정되는 것 아닌가요? 혹시 몰라 검색을 좀 해봤는데 찾을 수 없어 질문합니다.
감사합니다.
문의해주신 코드는 글 본문에서도 설명드렸듯이 자바개발자 입장에서 보기 편한 코틀린 코드로 작성한 겁니다(자바코드 아닙니다).
그 코드도 코틀린에서 정상적인 코드이지만 코틀린에서는 더욱 짧게 표현이 가능합니다.
class WhoAmI(private val name:String)
이렇게요.
해서.. 생성자 관련되어 코틀린이 자바보다 훨씬 짧게 코딩할 수 있다는 점을 설명드린 겁니다.
궁금한 점이 해소되셨나요? ^^
와 정말 잘읽고갑니다. static처럼 쓰일수있구나로만 알고있었지 덕분에 자세한 차이점을 깨달았습니다 감사합니당
잘 봐주셔서 감사합니다. 2번째 편도 있으니 꼭 보세요~ ^^
정말 감사합니다
감사합니다 ^^
와.. 진짜 자세히 알려주셔서 감사합니다.. 그저 빛!
도움 되셨다니 다행입니다! ^^
“companion 는 object 다 (java 의 static 이 아니라)” 라는 소리를 듣고 이게 무슨소린가 싶었는데,
요 글로 이해할 수 있었습니다 !! 좋은글 감사합니다 🙂
네 처음에는 쌩뚱맞죠? 이게 객체다라는 것을 받아들이는 순간 새로운 세계가 펼쳐짐에 놀라워요~ ^^
좋은글 감사합니다 !
감사합니다! ^^
kotlin을 처음 공부하고 있는데, 공부하면서 궁금했던 부분이 딱 있어서 좋네요 ㅎ
감사합니다.
도움이 되셨길 바랍니다. 시리즈 3,4도 있는데.. 게을러서 글을 못쓰고 있네요. ^^;;;
코틀린을 새로 공부하면서 새롭게 마주하는 개념들이 많아서 찾아보다가 들어오게 되었는데 companion object에 대해 아주 잘 설명해주셔서 도움이 많이 됐습니다. 감사합니다
코틀린 처음 접해보는데 이해가 논리정연하게 설명해주셔서 너무 잘 읽었습니다.
하루종일 Java의 static 과 Kotlin의 Object, Companion object 는 어떤차이가 있을까 에 대해 고민을 했었고, 결론이 나지 않았었는데 운좋게도 저녁에 설명 잘해주신 블로그를 찾아 다행이라는 생각이듭니다. 감사합니다. 자주 찾아올께요 ㅎㅎ
Companion Object를 왜 사용하는지, 정확한 개념이 잡히지 않았는데 완벽하게 이해됐습니다. 정말 감사합니다. 추가로 다형성까지 설명해주시니 정말 도움이 많이 됐습니다. 감사합니다.
작성해주신 MyClass2를 kotlin에서 java로 변환해 보았습니다.
— kotlin —
class MyClass {
companion object {
val prop = “나는 Companion object의 속성이다.”
fun method() = “나는 Companion object의 메서드다.”
}
}
— java —
public final class MyClass {
@NotNull
private static final String prop = “나는 Companion object의 속성이다.”;
@NotNull
public static final MyClass.Companion Companion = new MyClass.Companion((DefaultConstructorMarker)null);
(중략)
public static final class Companion {
@NotNull
public final String getProp() {
return MyClass.prop;
}
@NotNull
public final String method() {
return "나는 Companion object의 메서드다.";
}
private Companion() {
}
}
}
kotlin에서의 prop와 method()는 java에서 각각 static final과 final로 변환되는 모습을 볼 수 있습니다.
위와 같은 상태로 java에서 method()를 호출하려면 Companion instance를 생성해야 되기 때문에 설명해주신 대로 static method가 아닌 거 같은데,
prop는 static final로 명시되어서 “companion object는 static이 아니다.” 라고 명확하게 답하기 어렵지 않나요?
혹은 단순히 kotlin을 java로 표현하기 위해 생기는 해프닝 정도로 여겨야 하나요?
저도 동일한 의문을 가지고 여기까지 왔습니다.
자바로 디컴파일해 보면 static키워드가 붙는데, ‘static이 아니다’란 말이 모호해서 명확한 근거를 찾고 있습니다.
와 글을 읽으면서 소름이 돋았네요;; 자세한 설명 감사합니다!!
기초에 대해 지식을 쌓을 수 있는 좋은 경험이었습니다 감사합니다