반응형
- 경우에 따라 새 하위 클래스를 명시적으로 선언하지 않고 일부 클래스를 약간 수정한 개체를 만들어야 한다. 코틀린은 객체 표현식과 객체 선언으로 이를 처리할 수 있다.
Object expressions
- 객체 표현식은 익명 클래스, 즉 클래스가 명시적으로 선언되지 않은 클래스의 객체를 만든다. 이러한 클래스는 일회용으로 사용하기 유용하다.
- 처음부터 정의하거나 기존 클래스에서 상속하거나 인터페이스를 구현할 수 있다.
- 익명 클래스의 인스턴스는 이름이 아닌 식에 의해 정의되므로 익명 객체라고도 한다.
Creating anonymous objects from scratch
- 객체 표현식은 object 키워드를 사용한다.
val helloWorld = object {
val hello = "Hello"
val world = "World"
// object expressions extend Any, so `override` is required on `toString()`
override fun toString() = "$hello $world"
}
// result
// Hello World
Inheriting anonymous objects from supertypes
- 어떤 유형을 상속받는 익명 클래스의 객체를 만들려면 object와 콜론(:) 뒤에 유형을 선언한다.
- 그런 다음 이 클래스 구성원을 상속받는 것처럼 구현하거나 재정의한다.
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }
override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
- 슈퍼타입에 생성자가 있는 경우 적절한 생성자 매개변수를 전달한다.
- 콜론 뒤에 여러 개의 슈퍼타입을 쉼표로 구분하여 목록으로 지정할 수 있다.
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*...*/ }
val ab: A = object : A(1), B {
override val y = 15
}
Using anonymous objects as return and value types
- 익명 객체가 인라인 선언이 아닌 로컬 또는 private 유형의 객체로 사용될 때, 모든 구성원은 이 함수 또는 프로퍼티를 통해 액세스 할 수 있다.
class C {
private fun getObject() = object {
val x: String = "x"
}
fun printX() {
println(getObject().x)
}
}
- 만약 이러한 함수 혹은 프로퍼티가 public or private inline으로 선언되었다면, 실제 타입은 다음 상황에 따라 결정된다.
- 만약 선언된 슈퍼타입이 없다면 익명 객체의 타입은 Any이다.
- 하나의 슈퍼타입으로 선언되었다면 타입으로 선언된 슈퍼타입과 동일한 타입을 갖는다.
- 둘 이상이라면 명시적으로 선언된 타입을 갖는다.
- 이러한 경우 익명 객체에 추가된 구성원에 접근할 수 없다. 재정의된 구성원은 함수 또는 프로퍼티의 실제 유형으로 선언된 경우 접근할 수 있다.
interface A {
fun funFromA() {}
}
interface B
class C {
// The return type is Any. x is not accessible
fun getObject() = object {
val x: String = "x"
}
// The return type is A; x is not accessible
fun getObjectA() = object: A {
override fun funFromA() {}
val x: String = "x"
}
// The return type is B; funFromA() and x are not accessible
fun getObjectB(): B = object: A, B { // explicit return type is required
override fun funFromA() {}
val x: String = "x"
}
}
Accessing variables from anonymous objects
- 객체 표현식 안에 있는 코드는 일정 범위 안에 있는 변수에 접근할 수 있다.
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ...
}
Object declarations
- Singleton 패턴은 매우 유용한데, 코틀린은 object 키워드를 사용하여 이를 쉽게 만들 수 있다.
- 객체 선언은 표현식이 아니므로 할당문 오른쪽에 선언할 수 없다.
- 객체 선언의 초기화는 스레드에 안전하며 첫 번째 액세스에서 수행된다.
- 객체 선언은 로컬에서 선언될 수 없지만 (즉, 함수 내에 직접 중첩 불가.) 다른 객체 선언이나 내부 클래스가 아닌 클래스로 중첩될 수 있다.
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
- 객체를 참조하려면 직접 참조한다.
DataProviderManager.registerDataProvider(...)
- 슈퍼타입이 존재할 수 있다.
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
}
- Kotlin에서 일반 객체 선언을 인쇄할 때 문자열 표현에 객체의 이름과 해시가 모두 포함되어 있다.
object MyObject
fun main() {
println(MyObject) // MyObject@1f32e575
}
- data class처럼 data object로 선언하면 toString 함수에 대한 구현을 수동으로 구현하지 않고도 적절한 형식의 문자열 표현을 얻을 수 있다. (Kotlin 1.9 버전에서 사용 가능하지만 실험적인 기능.)
data object MyObject
fun main() {
println(MyObject) // MyObject
}
- data object 기능은 sealed class와 함께 사용하기 유용하다.
sealed class ReadResult {
data class Number(val value: Int): ReadResult()
data class Text(val value: String): ReadResult()
data object EndOfFile: ReadResult()
}
fun main() {
println(ReadResult.Number(1)) // Number(value=1)
println(ReadResult.Text("Foo")) // Text(value=Foo)
println(ReadResult.EndOfFile) // EndOfFile
}
Companion objects
- companion 키워드를 사용하여 멤버를 전역으로 사용할 수 있고 쉽게 접근할 수 있다.
- 해당 클래스가 로드될 때 초기화된다.
- const val로 선언된 상수는 static 변수라고 할 수 있다. (혹은, @JvmStatic or @JvmField 어노테이션이 붙은 함수 및 프로퍼티)
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
- 다른 언어에서의 static 멤버와 비슷하지만 런타임에서는 실제 객체의 인스턴스 멤버이기 때문에 인터페이스 구현이 가능하다.
- 그러나 만약 @JvmStatic 어노테이션을 사용하였다면 JVM에서 실제 정적 메서드 및 필드로 생성된 객체의 멤버를 가질 수 있다. Java와의 상호 운용성에 대한 이해가 필요하다.
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
Semantic difference between object expressions and declarations
- 객체 표현식은 사용되는 곳에서 즉시 실행(초기화)된다.
- 객체 선언은 처음 액세스되었을 때 lazy 초기화된다.
- 객체 선언은 Java로 변환된 파일 내부에 object file name 타입의 static INSTANCE 객체가 생성된다. (Singleton)
- companion object는 해당 클래스가 로드될 때 초기화된다.
- companion object 내에서 선언된 함수 및 프로퍼티는 Java로 변환된 파일 내부에 생성되는 static class Companion을 통해 접근이 가능하다. 즉, 함수 및 프로퍼티 자체가 static은 아니다.
Kotlin Docs 번역
참고자료 :
https://kotlinlang.org/docs/object-declarations.html
반응형
'Programming Language > Kotlin' 카테고리의 다른 글
[Kotlin] Sealed Class (0) | 2023.02.19 |
---|---|
[Kotlin] Interfaces (0) | 2023.02.12 |
[Kotlin] forEach에서 break & continue (0) | 2022.12.05 |
[Kotlin] 진수 변환. 알고리즘. (5) | 2021.08.25 |
[Kotlin] 비동기 프로그래밍 기술 (0) | 2021.06.19 |