Object
Object
키워드를 다양한 상황에서 사용할 수 있다. 그 상황들의 공통점은, 클래스를 정의하면서 동시에 인스턴스(객체)를 생성한다는 점이다.
코틀린에서 object
키워드는 다음 두가지 형태로 사용 가능하다.
- object declarations(선언식)
- object expressions(표현식)
1. Object declaration(선언식): Singleton 만들기
object를 선언식으로 사용하게 된다면, 무엇보다도 Singleton패턴
형태로 사용하는 것이다.
객체지향 프로그램을 설계하다 보면, 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많아진다.
JAVA에서는 아래와 같이 보통 클래스의 생성자를 private로 선언하고, static 변수에 클래스 객체를 저장하는 방식으로 싱글턴 패턴을 구현한다.
public class Singleton {
private static Singleton INSTANCE;
private Singleton() { }
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
반면, 코틀린은 객체 선언(object) 기능을 통해 싱글톤을 언어 자체에서 기본 지원해준다.
코틀린에서 object를 선언식으로 사용하는 문법은 아래와 같다.
object Singleton{
...
}
// 사용 방법
val singleton = Singleton
선언된 싱글톤은 객체명만으로 사용이 가능하다.
Object 내에 class와 같이 멤버 변수/ 메서드를 가질 수 있으며, class와 interface의 상속이 가능하다.
그렇기 때문에, object를 선언식으로 사용하게 되면, 객체를 생성하여 사용할 수 있다.
object Singleton: MyClass(), MyInterface { //⭐ class, interface 상속 가능
private val name: String = "Molrhmbo" // ⭐ 멤버 변수를 가질 수 있다.
override fun MyClassFunc(){
...
}
}
2. Object expressions(표현식): Non-Singleton
선언식과 달리 object를 표현식으로 사용하면 Singleton형태가 아닌, 아래와 같은 익명 객체(anonymous object)로서 사용가능하다.
흔히 접할 수 있는 익명 객체는 익명 클래스로 구현하는 이벤트 리스너들을 떠올리면 된다.
- 익명 클래스의 객체를 바로 생성하고자 하는 경우
// 📢 익명 클래스의 객체를 바로 생성하고자 하는 경우 fun main() { val user = object{ val name = "Molrhmbo" val age = 28 } println(user.name) println(user.age) }
- 추상 클래스, 인터페이스의 구현체를 익명 클래스의 객체로 구현하고자 하는 경우.
// 📢 추상 클래스, 인터페이스의 구현체를 익명 클래스의 객체로 구현하고자 하는 경우. interface MyInterface{ val name:String val age: Int fun greeting() } val myListener= object: MyInterface{ override val name = "Molrhmbo" override val age = 28 override fun greeting(){ println("my name is ${name}") println("and I'm ${age} years old!") } } fun main() { myListener.greeting() }
아래는 익명 객체로 리스너를 구현하는 예시이다.
/*
* 익명 객체로 리스너 구현하기
*/
binding.countingButton.setOnClickListener(
object: View.OnClickListener { // ⭐ 익명 객체로 클릭 리스너 선언
override fun OnClick(v: View?) {
...
}
}
)
위 예제에서 익명 클래스는 싱글톤이 아니다.
호출할 때 마라 새로운 인스턴스가 생성되고, 익명 클래스 안에서 자신이 포함된 함수의 로컬 변수에 접근할 수 있다.
자바에서는 무조건 final이어야만 접근이 가능한것에 비해, 코틀린에서는 final이 아닌 변수도 객체 식 안에서 사용할 수 있는 것이다.
fun countClick(){
val counter = 0 // ⭐ 로컬 변수
binding.countingButton.setOnClickListener( object: View.OnClickListener {
override fun OnClick(v: View?) {
counter ++ // ⭐ 로컬 변수의 값을 변경한다.
}
})
}
Static 멤버 선언
코틀린 언어는 자바의 static을 지원하지 않기 때문에, 클래스 안에는 static 멤버가 없다.
그 대신, 코틀린에서는 클래스 바깥에 선언하는 최상위 함수(==java의 static method역할)와 객체 선언을 활용한다. 대부분의 경우에는 최상위 함수를 활용하는 편을 더 권장한다. 하지만, 최상위 함수는 private 멤버에 접근을 할 수가 없다.
클래스 안에 “companion object”
를 정의 후, 그 안에 클래스의 프로퍼티나 메소드를 선언한다면, 자바의 static 호출 사용 구문과 동일하게 외부에서 접근할 수 있다.
또, companion object
객체 선언은, private 생성자를 호출하기 좋은 위치이다. 이는 자신을 포함한 클래스의 모든 private 멤버에 접근할 수 있기 때문에, 팩토리 패턴을 구현하기에 가장 적합한 위치이다.
간단한 예제로, 부 생성자가 두 개 있는 클래스를, companion object
를 사용해서 팩토리 클래스로 재정의 해보겠다.
class User{
val name: String
constructor(email: String){ // sub constructor 1
name = email.substringBefore('@')
}
constructor(naverEmailId: String){ // sub construtor 2
name = getNaverName(naverEmailId)
}
}
위 코드를 더 유용한 방법으로 클래스의 인스턴스를 생성하는 팩토리 메소드로 바꿔보겠다.
class User private constructor(val name: String){ // declare main constructor in private
companion object{
fun newEmailUser(email: String) = User(email.substringBefor('@'))
fun newNaverUser(naverEmail: String) = User(getNaverEmail(naverEmailId))
}
}
val emailUser = User.newEmailUser("abcd@gmail.com") // invoke method in companion object
println(emailUser.name)
팩토리 패턴을 적용하니 훨씬 더 유용해 보인다.
이런식으로 정의된 팩토리 메소드는 생성할 필요가 없는 객체를 생성하지 않을 수도 있는데, 예를 들어 이메일 주소별로 유일한 User인스턴스를 만드는 경우, 팩토리 메소드가 이미 존재하는 인스턴스에 해당하는 이메일 주소를 전달받으면 새 인스턴스를 만들지 않고, 캐시에 있는 기존 인스턴스를 반활할 수 있다.
즉, 매우 유용하다.👍🏼
Companion object를 일반 객체처럼 사용하기
companion object
는 클래스 안에 정의된 일반 객체이다.
그렇기 때문에 이름을 붙이거나, 인터페이스를 상속하거나, companion object
안에 확장 함수와 프로퍼티를 정의할 수 있다.
class User private constructor(val name: String){ // declare main constructor in private
companion object IdType{
fun newEmailUser(email: String) = User(email.substringBefor('@'))
fun newNaverUser(naverEmail: String) = User(getNaverEmail(naverEmailId))
}
}
val emailUser = User.newEmailUser("abcd@gmail.com")
println(emailUser.name)
// >>> abcd
val emailUser2 = User.IdType.newEmailUser("hello@gmail.com") //⭐ 이름을 붙인 companion object 메소드 호출
println(emailUser2.name)
// >>> hello
아래는 companion object
에서 인터페이스를 구현하는 예이다.
interface JSONFactory<T> {
fun fromJSON(jsonText: String): T
}
class User(val name: String){
companion object: JsonFactory<User> { // User 클래스 사용 가능
override fun fromJSON(jsonText: String): User = ...
}
}
Uploaded by N2T
'Kotlin > Basic' 카테고리의 다른 글
집합: Set, MutableSet (0) | 2023.12.03 |
---|---|
가변 인자 목록: vararg (0) | 2023.12.03 |
[Kotlin] lateinit와 lazy의 차이점 (0) | 2023.01.17 |
[Kotlin] 내부(Inner class)와 중첩 클래스(Nested class) (0) | 2023.01.11 |
[Kotlin] 더블콜론(::) 참조 (0) | 2023.01.10 |