자바의 static 키워드는 클래스 멤버임을 지정하기 위해 사용한다. static이 붙은 멤버는 클래스가 메모리에 적재될 때 자동으로 함께 생성되므로 인스턴스 생성 없이도 클래스명 다음에 점(.)을 쓰면 바로 참조할 수 있습니다.
하지만 코틀린에는 static이 없고, 대신에 companion object라는 키워드와 함께 블록안에 멤버를 구성한다.
public class test {
int iv; // 인스턴스 변수
static int cv; // 클래스 변수
void method() {
int lv; // 지역 변수
}
}
코틀린에는 자바에 없는 독특한 싱글턴(singleton: 인스턴스가 하나만 생성되는 클래스) 선언 방법이 있습니다. class 대신 object 키워드를 사용하면 됩니다.
object MySingleton{
val prop = "나는 MySingleton의 속성이다."
fun method() = "나는 MySingleton의 메소드다."
}
fun main(args: Array<String>){
println(MySingleton.prop); //나는 MySingleton의 속성이다.
println(MySingleton.method()); //나는 MySingleton의 메소드다.
}
사실 코틀린 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())
}
companion object{}는 MyClass2 클래스가 메모리에 적재되면서 함께 생성되는 동반되는 객체이고 이 동반 객체는 클래스명.Companion으로 접근할 수 있다는 점이다. MyClass2.Companion.prop 과 MyClass2.prop 는 동일한 결과를 보입니다. 축약 표현일 뿐이다. 언어적으로 지원하는 축약하는 표현 때문에 companion object가 static으로 착각이 드는 것이다.
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())
}
companion object는 객체이므로 변수에 할당할 수 있다. 그리고 할당한 변수에서 점으로 MyClass2에 정의된 companion object의 멤버에 접근할 수 있다. 이렇게 변수에 할당하는 것은 자바의 클래스에서 static 키워드로 정의된 멤버로는 불가능한 방법이다.
위 코드에서 .Companion을 빼고 직접 MyClass2로 할당했다. 이것도 또한 MyClass2에 정의된 companion object이다.
static 키워드만으로는 클래스 멤버를 companion object처럼 하나의 독립된 객체로 여겨질 수 없다.
companion object의 기본 이름은 companion인데, 바꿀 수 있다.
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())
}
하지만, 이름을 지었을 경우 더이상 .Companion으로 접근할 수 없다.
클래스 명만으로 companion object 객체를 참조할 수 있기 때문에 한 번에 2개를 참조하는 것은 불가능하다.
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의 메소드다."
}
}
이름을 별도로 부여해도 마찬가지로 불가능하다.
덕분에 자바에서 static 멤버를 클래스에 아무데나 쓰는게 제약이 없었다면 코틀린에서는 자동으로 한곳에 모이게 된다.
인터페이스 수준에서 상수항을 정의할 수 있고, 관련된 중요 로직을 이곳에 기술할 수 있다.
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()) //나는 부모값
}
다른 이름이라면 문제가 없다. 하지만 같은 이름이면 어떻게 될까?
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) //나는 자식
}
prop이 부모에서 정의 되어 있지만 가려져서 무시되는 것을 볼 수 있다.
Child().method0() 메소드는 Parent 클래스의 것이기 때문에 부모의 companion object의 prop 값인 "나는 부모"가 출력된다.
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)
}
주석 2는 "나는 부모"가 출력된다. 자식의 이름을 바꿨기 때문에 Companion은 부모가 된다.
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 에러!!
}
주석이 에러가 난다. ParentCompanion.prop 로 바꿔야 한다.
부모/자식의 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)
}
다형성은 두 가지를 만족해야 한다.
이 두 가지 조건을 만족하면 다형성을 만족한다고 볼 수 있고, 다형성을 만족한다면 객체지향 언어라고 볼 수 있다. 따라서 p.method()의 결과는 Child().method() 결과와 같습니다. 그러므로 결과는 "나는 자식"입니다.
companion object는 클래스 안에 존재하므로 private 선언된 생성자에 접근 가능하다
class User {
private val name = "User1"
companion object {
val name2 = User().name
}
}
fun main() {
println(User.name2)
}