명시적으로 새 하위객체를 만들지 않고 클래스의 일부만 수정한 객체를 만들고 싶을 때 사용함
표현식은 익명 클래스의 오브젝트를 만듬(class
선언 없이)
한번 사용하기에 유용한 클래스는 선언과 동시에 정의할 수 있고 현재 존재하는 클래스로 상속할 수 있다. interface implement도 가능. 익명 클래스의 인스턴스는 익명 오브젝트라고 불림. 이름이 아니라 표현식으로 정의 되었기 때문에~
중요한 슈퍼타입을 가진 object가 필요한게 아니라면 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"
}
object
다음에 :
으로 상속할 타입을 정의해주면 된다.
그 다음에 코드블럭에서 implement하거나 override하면 된다
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }
override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
// n개의 supertype 상속 예시 class랑 동일하쥬
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*...*/ }
val ab: A = object : A(1), B {
override val y = 15
}
익명 오브젝트가 인라인 (함수 혹은 property)선언이 아닌 로컬 혹은 private 타입으로 사용 될 때, 모든 멤버가 함수(혹은 property)를 통해 그 객체 멤버에 접근이 가능하다.
class C {
private fun getObject() = object {
val x: String = "x"
}
fun printX() {
println(getObject().x)
}
}
만약에 함수가 public 혹은 private 인라인이면 그것의 실제 타입은
Any
이 모든 케이스에서 추가된 멤버들은 접근 불가능하다. 실제 선언된 타입에 관련된 오버라이드 된 멤버는 접근 가능하다
interface A {
fun funFromA() {}
}
interface B
class C {
// return type: Any, x 접근 불가
fun getObject() = object {
val x: String = "x"
}
// return type: A; x 접근불가
fun getObjectA() = object: A {
override fun funFromA() {}
val x: String = "x"
}
// return type: B, funFromA() and x 접근 불가
fun getObjectB(): B = object: A, B { // (B같은)명시적인 return type 필요
override fun funFromA() {}
val x: String = "x"
}
}
오브젝트 표현식 코드는 enclosing scope 내에 변수들에는 접근이 가능함.
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 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) { ... }
}
데이터 오브젝트는 실험적으로 사용하는거라 언제 사라질지 모름~
// 일반 오브젝트 선언+해쉬값
object MyObject
fun main() {
println(MyObject) // MyObject@1f32e575
}
// 데이터 오브 젝트
data object MyDataObject {
val x: Int = 3
}
fun main() {
println(MyDataObject) // MyDataObject
}
데이터 오브잭트 생성 시 컴파일러가 생성하는 함수들(커스텀도 가능함)
데이터 객체에 대한 equals() 함수는 데이터 객체와 동일한 유형을 가진 모든 객체가 동일한 것으로 간주되도록 합니다. 대부분의 경우 런타임에는 데이터 객체의 인스턴스가 하나만 존재합니다(결국 데이터 객체는 싱글톤). 그러나 런타임에 동일한 유형의 다른 객체가 생성되는 경우(예: java.lang.reflect를 사용한 플랫폼 리플렉션 또는 이 API를 내부적으로 사용하는 JVM 직렬화 라이브러리 사용)에는 객체가 동일한 것으로 취급되도록 합니다.
다른 객체가 생성되는 경우를 이해를 못하겠군,,,
클래스 안에 오브젝트 선언할 때 companion 키워드로선언할 수 있음.
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
// 사용
val instance = MyClass.create()
class MyClass {
companion object { }
}
// 이름 없이 정의하면 Companion으로 사용할 수 있음.
val x = MyClass.Companion
클래스 멤버는 해당 컴패니언 객체의 비공개 멤버에 액세스할 수 있음
그 자체로 사용되는 클래스 이름은 클래스의 동반 객체에 대한 참조 역할을 합니다(이름 지정 여부에 관계없이)
컴패니언 오브젝트가 다른 언어 관점으로 보면 static 멤버로 보인다.
런타임에서 여전히 실제 오브젝트의 멤버이며, ㅇㅖ를 들면은 인터페이스를 구현할 수도 있음
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
@JvmStatic
를 사용하면 jvm에서는 진짜 static으로 생성함.