- 코틀린 클래스 안에는 정적인 멤버가 없다. 자바 static 키워드를 지원하지 않는다.
- 그 대신 코틀린에서는 패키지 수준의 최상위 함수와 객체 선언을 활용한다.
- 대부분의 경우 최상위 함수를 활용하는 편을 더 권장한다.
- 그러나 최상위 함수는 특정 클래스의 private 멤버에 접근할 수 없기 때문에, 클래스 내부에 정의된 static 멤버가 다른 클래스 멤버와 상호작용해야 하는 경우에는 적합하지 않다.
- static 을 최상위 함수로 대체할 수 없는 경우에는 object 를 고려한다.
객체 선언(Object Declaration) : 싱글톤
- 클래스 선언과 그 클래스에 속한 단일 인스턴스의 생성을 동시에 처리해주기 때문에, 싱글톤에 사용하기 적합하다.
- 클래스 전체가 하나의 싱글톤 객체로 선언되고, 처음 사용될 때까지 초기화가 지연된다.
- 클래스와 마찬가지로 객체 선언 안에도 프로퍼티, 메소드, 초기화 블록 등이 들어갈 수 있다. 하지만 생성자는(주 생성자와 부 생성자 모두) 객체 선언에 쓸 수 없다.
- 일반 클래스 인스턴스와 달리, 싱글톤 객체는 객체 선언문이 있는 위치에서 생성자 호출 없이 즉시 만들어진다.
- 객체 선언도 클래스나 인터페이스를 상속할 수 있다.
- 인터페이스를 구현해야 하는데, 그 구현 내부에 다른 상태가 필요하지 않은 경우에 유용하다.
- 클래스 안에서 객체 선언을 사용하더라도 객체 선언의 인스턴스는 단 하나만 생성된다.
- 대규모 소프트웨어 시스템에서는 객체 생성을 제어할 방법이 없고 생성자 파라미터를 지정할 수 없어 객체 선언이 항상 적합하지는 않다.
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int =
p1.name.compareTo(p2.name)
}
}
val persons = listOf(Person("B"), Person("C"), Person("A"))
println(persons.sortedWith(Person.NameComparator)) // [Person(name=A), Person(name=B), Person(name=C)]
- 함수가 여러 객체와 관계를 맺지 않고 하나의 특정 객체(Person)와만 관계를 맺는다면, 최상위 함수보다는 클래스 내부에 정의하는 것이 좋다. (캡슐화)
data class Person(val name: String) : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int {
return p1.name.compareTo(p2.name)
}
}
val persons = listOf(Person("B"), Person("C"), Person("A"))
println(persons.sortedWith(Person("A"))) // [Person(name='A'), Person(name='B'), Person(name='C')]
- Comparator 를 직접 상속받으면 sortedWith(Person("A"))와 같이 사용해야 하는데, 이는 매우 부자연스럽다.
동반 객체(Companion Object)
- 객체 선언처럼 클래스가 로드되는 시점에 인스턴스가 단 하나만 생성된다.
- 클래스 내에 일부분이 싱글톤 객체로 선언되고, 클래스의 인스턴스가 로드될 때 즉시 초기화 된다.
- 동반 객체 선언도 일반 객체 선언처럼 상속이나 함수, 프로퍼티를 가질 수 있다.
- 객체 선언은 한 클래스 내에 여러개 존재할 수 있지만, 동반 객체는 단 하나만 존재할 수 있다.
- 동반 객체를 포함하는 클래스를 확장해야하는 경우에는 동반 객체 멤버를 하위 클래스에서 오버라이드할 수 없으므로 부 생성자를 사용하는 편이 더 낫다.
- 동반 객체에 이름을 지정하지 않는 경우 자동으로 Companion 이 이름이 된다.
- 이름을 붙이건 안붙이건 동반 객체 내부의 메소드는 그냥 호출할 수 있다.
- 객체 선언에서는 Person.NameComparator.compareTo()로 내부 객체를 명시해 주어야 하지만, 동반 객체는 그냥 Person.fromJSON()으로 사용했다는 점에서 차이가 있다.
- 동반 객체는 자신을 둘러싼 클래스의 모든 private 멤버에 접근할 수 있다. 따라서 동반 객체는 바깥쪽 클래스의 private 생성자도 호출할 수 있다. (팩토리 메서드 패턴)
class User private constructor(val nickname: String) { // 주 생성자를 비공개로 만든다.
companion object {
fun newSubscribingUser(email: String) =
User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) =
User(getFacebookName(accountId))
}
}
val subscribingUser = User.newSubscribingUser("bob@gmail.com")
val facebookUser = User.newFacebookUser(4)
println(subscribingUser.nickname) // bob
- 동반 객체는 클래스 안에 정의된 일반 객체로서 인터페이스를 상속하거나, 동반 객체 안에 확장 함수와 프로퍼티를 정의할 수 있다.
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 {
. . .
}
val p = Person.fromJSON(json)
loadfromJSON(p)
- 동반 객체의 메소드도 확장 함수로 정의할 수 있기 때문에 함수의 정의를 바깥쪽 클래스와 분리할 수 있다.
// Person 은 비즈니스 로직 모듈이므로, JSON 역직렬화 함수는 서버/클라이언트 통신 모듈에 두고싶다면
class Person(val name: String) {
companion object {} // 빈 동반 객체
}
fun Person.Companion.fromJSON(json: String): Person {
TODO()
}
val p = Person.fromJSON(json)
'안드로이드 > Kotlin' 카테고리의 다른 글
[Kotlin] Enum class, Sealed class (0) | 2024.12.10 |
---|---|
[Kotlin] 범위 지정 함수(Scope function) (0) | 2024.12.09 |
[Kotlin] 타입 연산자(is, as), 널 처리 연산자 (0) | 2024.12.08 |