클래스를 정의하면서 동시에 인스턴스를 생성한다.!!

코틀린에는 ``java static``이 없고, 대신 이를 최상위 함수로 처리한다.

그러나 최상위 함수는 말 그대로 최상위에 존재하는 함수이기 때문에 어떤 클래스의 ``kt private``에는 당연히 접근할 수 없어 클래스 내부에 선언된 ``java static``이 클래스의 다른 멤버와 상호 작용하는 경우를 커버할 수 없다.
이렇게, ``java static``을 최상위 함수로 대체할 수 없을 때 ``kt object``를 고려한다.
* ``kt object``를 사용하면 java로 변환시 ``java static``으로 컴파일된다.

 

1. 객체 선언(object declaration) : 싱글턴

객체 선언은 클래스 선언과 그 클래스에 속한 단일 인스턴스의 생성을 동시에 처리해주기 때문에, singleton에 사용하기 적합하다.
  • 객체 접근 시점에 객체가 생성된다.
  • 주/부 생성자는 사용할 수 없다. 객체 선언과 동시에 생성자 호출 없이 바로 만들어지기 때문.
  • 객체 선언도 클래스나 인터페이스를 상속할 수 있다.
    인터페이스를 구현해야 하는데 그 구현 내부에 다른 상태가 필요하지 않은 경우 사용하면 좋다.
  • 객체 선언 안에도 프로퍼티, 메소드, ``kt init``이 들어갈 수 있다.
  • 싱글턴과 마찬가지로 객체 선언도 대규모 소프트웨어 시스템에서는 적합하지 않을 수 있다.
  • 자바에서 코틀린 객체 선언으로 생성된 인스턴스에 접근하려면 ``java INSTANCE``를 사용한다.
  • 클래스 안에서 객체 선언을 사용하더라도 객체 선언의 인스턴스는 단 하나만 생성된다.

```kt

data class Person(val name: String) {

    object NameComparator : Comparator<Person> {

        override fun compare(p1: Person, p2: Person): Int =

                p1.name.compareTo(p2.name)

    }

}

```

```kt

>>> val persons = listOf(Person("B"), Person("C"), Person("A"))

>>> println(persons.sortedWith(Person.NameComparator))

[Person(name=A), Person(name=B), Person(name=C)]

```

 

Q1 최상위 함수로 정의해도 되는거 아님?

이런 경우 ``kt Person``에 대해서만 비교하는 것이기 때문에 최상위 함수 보다는 클래스 내부에 정의하는 것이 알맞다.

 

Q2 Person이 Comparator을 직접 상속 받으면 되지 왜 안에 object를 쓰고 거기서 상속받는가?

  • ``kt Comparable``이 아니라 ``kt Comparator``이니까.
  • ``kt sortedWith()``는 ~를 기준으로 정렬. 이라는 의미이므로 ``kt Comparator``를 받아야 한다.
  • 즉 ``kt Comparable``을 이용해 1:1 비교 메소드를 정의하는 것과는 다름.
  • 만약 ``kt Person``이 직접 Comparator를 상속받았다면 다음과 같이 써야해서 매우 부자연스럽다.

```kt

>>> val persons = listOf(Person("B"), Person("C"), Person("A"))

>>> println(persons.sortedWith(persons[0]))

[Person(name=A), Person(name=B), Person(name=C)]

```

 

2. 동반 객체 companion

  • 객체 선언 처럼 클래스가 로드되는 시점에 인스턴스가 단 하나만 생성된다.
  • 동반 객체 선언도 일반 객체 선언처럼 상속이나 함수, 프로퍼티를 가질 수 있다.
  • 객체 선언은 한 클래스 내에 여러개 존재할 수 있지만, 동반 객체는 단 하나만 존재할 수 있다.
    • 따라서 여러개의 객체 선언을 사용하는게 더 적합할 때도 있다.
  • 동반 객체를 포함한 클래스를 확장해야하는 경우에는 동반 객체 멤버를 하위 클래스에서 오버라이드할 수 없으므로 부 생성자를 사용하는 편이 더 낫다.
  • 동반 객체에 이름을 지정하지 않는 경우 자동으로 ``kt Companion``이 이름이 된다.
  • 이름을 붙이건 안붙이건 동반 객체 내부의 메소드는 그냥 호출할 수 있다.

코틀린에서 클래스 안의 클래스는 기본적으로 ``kt static``으로 컴파일 되는 중첩 클래스이며, ``kt inner``와 ``kt object``를 함께 사용할 수 없기 때문에 클래스 안에 들어있는 ``kt object``객체 선언일지라도 바깥쪽 클래스의 멤버에 그냥 접근할 수는 없다.

단, 객체를 생성하면서 접근하는 것은 가능한데 이 때문에 ``kt private`` 생성자를 ``kt object`` 내에서 호출할 수 있어 팩토리 메소드 패턴을 구현하기 적합하다.

이런 식으로 클래스 안에 중첩 클래스로 ``kt object``가 들어가는 경우, ``kt companion``을 붙여주면 ``kt object``의 이름을 명시하지 않고 바깥쪽 클래스의 이름으로 바로 접근할 수 있기 때문에 팩토리 패턴을 구현하는 경우 이를 붙여주는 것이 좋다.

 

#0 class 내부의 static 변수를 companion에 둘 수 있음

  • 인스턴스가 단 하나만 생성되니까. 여기에 변수를 두면 static처럼 쓸 수 있다.
 

#1 Factory method pattern

```kt

class User private constructor(val nickname: String) {

    companion object {

        fun newSubscribingUser(email: String) =

                User(email.substringBefore('@'))

        fun newFacebookUser(accountId: Int) =

                User(getFacebookName(accountId))

    }

}

```

```kt

>>> val u = User.newSubscribingUser("umbum@n.c")

>>> println(u.nickname)

umbum

>>> val u2 = User.Companion.newSubscribingUser("umbum@n.c")

```

 

#2 클래스 내부에 뭔가를 상속/구현해야 할 때 동반 객체를 사용할 수 있다.

어떤 시스템에서는 모든 객체를 JSON 역직렬화를 통해 만들어야 하기 때문에, 모든 타입의 객체를 JSON 역직렬화하는 일반적인 방법이 필요하다. 이런 경우 JSON 역직렬화 구현을 제공하는 인터페이스를 사용하게 되는데, 이런 메서드는 static으로 ``kt Person.fromJSON()`` 형태로 호출하는게 자연럽다. 그래서 1. 객체 선언과 마찬가지로 클래스 자체가 이 인터페이스를 상속하게 하는 것 보다는 객체 선언을 이용해 클래스 내부에 싱글턴 객체가 이를 상속하도록 한다.
1. 객체 선언에서는 ``kt Person.NameComparator.compareTo()``로 내부 객체를 명시해 주어야 하지만, 동반 객체는 그냥 ``kt Person.fromJSON()``으로 사용했다는 점에서 차이가 있다.
```kt
interface JSONFactory<T> {
    fun fromJSON(jsonText: String): T
}
 
class Person(val name: String) {
    companion object : JSONFactory<Person> {
        override fun fromJSON(jsonText: String): Person {
            TODO("not implemented")
        }
    }
}
 
fun loadFromJSON<T>(factory: JSONFactory<T>): T {
    . . .
}
```
```kt
>>> val p = Person.fromJSON(json)
>>> loadfromJSON(p)
```
 

#3 동반 객체 확장, 모듈 분리

동반 객체의 메소드도 확장 함수로 정의할 수 있기 때문에 함수의 정의를 바깥쪽 클래스와 분리할 수 있다.
``kt Person``은 비즈니스 로직 모듈이므로, JSON 역직렬화 함수는 서버/클라이언트 통신 모듈에 두고싶다면
```kt
// 비지니스 로직 모듈
class Person(val name: String) {
    companion object {}  // 빈 동반 객체
}
```
```kt
// 서버/클라이언트 통신 모듈
fun Person.Companion.fromJSON(json: String): Person {
    TODO()
}
```
```kt
>>> val p = Person.fromJSON(json)
```
* 이렇게 분리하는건 장단이 있는 듯. ``kt Person``만 확인했을 때 동반 객체 안에 뭐가 있는지 바로 확인이 안되니까.
 

3. 객체 식 : 무명 내부 클래스, 무명 객체, 무명 클래스

이름 없이 쓰면 객체 식이다.

Note ) 당연하겠지만 이건 싱글턴이 아니다. 객체 식이 쓰일 때 마다 새로운 인스턴스가 생성된다.

객체 식은 여러 메소드를 오버라이드해야 하는 경우나, 추상 클래스를 구현해야 하는 경우 사용하도록 한다.

SAM(Single Abstract Method), 즉 단일 추상 메소드만 가지고 있는 함수형 인터페이스인 경우에는 무명 객체 대신 람다를 사용하는 편이 좋다.

2017/12/05 - [Coding/Kotlin Java] - [Kotlin] 함수형 인터페이스(SAM)에 람다 사용하기

```kt

fun countClicks(window: Window) {

    var clickCount = 0

    window.addMouseListener(

        object : MouseAdapter() {

            override fun mouseClicked(e: MouseEvent) {

                clickCount++    // 둘러싼 함수의 변수에 접근할 수 있다.

// 자바는 final만 접근할 수 있지만 코틀린은 final이 아니어도 접근 가능하다.

            }

            override fun mouseEntered(e: MouseEvent) { ... }

        }

    )

}

```

딱 한 번만 사용되는 객체라서 클래스를 정의하기가 부담스러운 경우 객체 식을 변수에 넣어 사용하면 깔끔하게 처리할 수 있다.

```kt

interface Shape {

    fun onDraw()

}

 

val triangle = object: Shape {

    override fun onDraw() { ... }

}

```

 

### 객체 선언과 동반 객체의 차이

내부적인 구현에 약간 차이가 있기는 하나, 둘 다 ``java static``으로 컴파일되며 시스템 전역에 하나만 존재하는 인스턴스라는 점은 같다.
```kt
class NCtest (private val name: String) {
    object NotCompanion{
//        val _name = name    이게 가능하려면 inner를 붙여야 하는데 object에는 inner를 붙일 수 없다.
        fun testMethod(c: NCtest) = c.name
    }
}
 
class Ctest (private val name: String) {
    companion object {
//        val _name = name    역시 위와 같은 이유로 안됨.
        fun testMethod(c: Ctest) = c.name
    }
}
```
 

```kt

public final class Ctest {

   private final String name;

   public static final Ctest.Companion Companion = 

         new Ctest.Companion((DefaultConstructorMarker)null);

 

   public Ctest(@NotNull String name) {

      Intrinsics.checkParameterIsNotNull(name, "name");

      super();

      this.name = name;

   }

 

   public static final class Companion {

 

      @NotNull

      public final String testMethod(@NotNull Ctest c) {

         Intrinsics.checkParameterIsNotNull(c, "c");

         return c.name;

      }

 

      private Companion() {

      }

 

      // $FF: synthetic method

      public Companion(DefaultConstructorMarker $constructor_marker) {

         this();

      }

   }

}

```

```kt

public final class NCtest {

   private final String name;

 

 

   public NCtest(@NotNull String name) {

      Intrinsics.checkParameterIsNotNull(name, "name");

      super();

      this.name = name;

   }

 

   public static final class NotCompanion {

      public static final NCtest.NotCompanion INSTANCE;

 

      @NotNull

      public final String testMethod(@NotNull NCtest c) {

         Intrinsics.checkParameterIsNotNull(c, "c");

         return c.name;

      }

 

      static {

         NCtest.NotCompanion var0 = new NCtest.NotCompanion();

         INSTANCE = var0;

      }

   }

}

```

 

### 생성자 파라미터를 사용해야 하는 경우 적용할 수 있는 싱글턴

```kt

class DBHandler private constructor(context: Context)

    : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {

    companion object {

        // null check 해서 객체 생성해야 하니까 lateinit쓰면 안된다.

        @Volatile private var INSTANCE: DBHandler? = null

        

        // 중복 처럼 보이지만 sync는 꽤 비싸니까 퍼포먼스를 위해.

        // 근데 java로 변환해보면 synchronized block은 비어있고 코드가 밖으로 나와있음. 뭔지.

        fun getInstance(context: Context): DBHandler =

                INSTANCE ?: synchronized(this) {

                    INSTANCE ?: DBHandler(context.applicationContext).also{ INSTANCE = it }

                }

    }

 

    override fun onCreate(db: SQLiteDatabase?) {

    }

}

```