[Kotlin] 싱글턴 object(static) : 객체 선언, 동반 객체, 객체 식 / 익명, 무명
클래스를 정의하면서 동시에 인스턴스를 생성한다.!!
코틀린에는 ``java static``이 없고, 대신 이를 최상위 함수로 처리한다.
1. 객체 선언(object declaration) : 싱글턴
- 객체 접근 시점에 객체가 생성된다.
- 주/부 생성자는 사용할 수 없다. 객체 선언과 동시에 생성자 호출 없이 바로 만들어지기 때문.
- 객체 선언도 클래스나 인터페이스를 상속할 수 있다.
인터페이스를 구현해야 하는데 그 구현 내부에 다른 상태가 필요하지 않은 경우 사용하면 좋다. - 객체 선언 안에도 프로퍼티, 메소드, ``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 클래스 내부에 뭔가를 상속/구현해야 할 때 동반 객체를 사용할 수 있다.
#3 동반 객체 확장, 모듈 분리
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() { ... }
}
```
### 객체 선언과 동반 객체의 차이
```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?) {
}
}
```
'Java Stack > Kotlin' 카테고리의 다른 글
[Kotlin] stream API / 시퀀스(Sequence) (0) | 2017.12.04 |
---|---|
[Kotlin] 람다(lambda), 변수 포획과 클로저, 멤버 참조 (0) | 2017.12.04 |
[Kotlin] delegate 키워드 : by (0) | 2017.12.02 |
[Kotlin/Java] data class (0) | 2017.12.02 |
[Kotlin] 프로퍼티, 커스텀 접근자, 지연 초기화 (0) | 2017.12.02 |