코틀린 (Kotlin) object 키워드와 동반 객체 정리
코틀린 obeject 키워드
object 는 흔히 자바나 안드로이드에서 사용하는 무명 내부 클래스(anonymous inner class) 처럼 사용할 수 있습니다.object 키워드는 클래스를 정의하면서 객체를 생성합니다.
코틀린 싱글턴 만들기
코틀린은 object 키워드를 이용해서 별다른 코드없이 싱글턴 구현을 지원합니다.자바처럼 클래스를 만들고 static 객체로 한번만 할당해주는 코드가 object Payroll 이라고 클래스를 생성해 주기만하면 static 객체에 할당해주는 것을 자동으로 생성해줍니다.
object Payroll { //object 클래스 생성으로 싱글턴을 구현
val allEmplyees = arrayListOf<Person>()
fun calculateSalary() {
//...
}
}
//사용
Payroll.calculateSalary() //싱글턴처럼 . 으로 접근
Payroll.allEmplyees.add(Person("zerog", Company("zerog", Address(""))))
val allEmplyees = arrayListOf<Person>
fun calculateSalary() {
//...
}
}
object 생성자
object 객체는 주 생성자와 부 생성자 모두 사용할 수 없습니다.
//ERROR!!! object 는 생성자를 사용할 수 없음
object Payroll(val a:String) {
...
}
object Payroll(val a:String) {
...
}
object 상속
object 객체는 상속이 가능합니다.예를들어 멤버변수로 상태값을 저장하지않고 메소드만 override 하여 사용하는 클래스의 경우 object 로 상속을 받아 생성자없이 바로 사용가능하도록 만들수 있습니다.
//object 로 구현한 경우
object CaseInsensitiveFileComparator: Comparator {
override fun compare(o1: File?, o2: File?): Int {
return o1!!.path.compareTo(o2!!.path, ignoreCase = true)
}
}
//클래스로 구현한경우 class TestFileComparator : Comparator {
override fun compare(o1: File?, o2: File?): Int {
//...
}
}
//사용
val files = listOf(File("/a"), File("/b"))
files.sortedWith(TestFileComparator()) //class의 경우
files.sortedWith(CaseInsensitiveFileComparator) //인자로도 넘길수 있음
object CaseInsensitiveFileComparator: Comparator
override fun compare(o1: File?, o2: File?): Int {
}
//클래스로 구현한경우 class TestFileComparator : Comparator
override fun compare(o1: File?, o2: File?): Int {
}
}
//사용
files.sortedWith(TestFileComparator()) //class의 경우
files.sortedWith(CaseInsensitiveFileComparator) //인자로도 넘길수 있음
object 키워드 자바에서 접근하기
자바에서 싱글턴을 구현하면 관례적으로 INSTANCE 를 사용합니다.코틀린의 object 에서 생성되는 싱글턴도 INSTANCE 라고 이름지어진 변수를 생성하기때문에 자바에서 사용할 경우에 INSTANCE 로 접근해서 사용할 수 있습니다.
//자바에서 object 클래스 사용하기
CaseInsensitiveFileComparator.INSTANCE.compare(new File("/"), new File("/"));
CaseInsensitiveFileComparator.INSTANCE.compare(new File("/"), new File("/"));
'CaseInsensitiveFileComparator INSTANCE' 라는 멤버변수에 접근하는 것과 같습니다.
중첩 객체에서 object 클래스 생성하기
클래스 안에 object 클래스를 생성할 수 있습니다.클래스 안에 있다고해도 static 이기때문에 단 하나만 생성됩니다.
data class File(val name:String) {
//중첩으로 사용 가능
object FileComparator: Comparator {
override fun compare( o1: File, o2: File): Int {
return o1.path.compareTo(o2.path, ignoreCase = true)
}
}
}
//사용
val files = listOf(File("/a"), File("/b"))
files.sortedWith(File.FileComparator) //클래스안에 object 클래스 접근
//중첩으로 사용 가능
object FileComparator: Comparator
}
동반 객체(companion object)
코틀린에는 static 이 없습니다.대신 패키지 수준의 최상위 함수와 객체 선언을 사용할 수 있습니다.
최상위 함수는 static 메소드를 대신할 수 있습니다.
객체 선언은 최상위 함수가 접근할 수 없는 클래스 내에 private 멤버 변수에 접근해야 할 때 사용할 수 있습니다.
companion object {...} 로 사용할 수 있습니다.
companion object 는 클래스 내에 하나만 생성할 수 있습니다.
class A private constructor(val name: String) { //private 생성자
companion object {
fun bar(): A {
return A("zerog") //private 생성자를 호출할 수 있다
}
}
}
//사용
A.bar()
companion object {
fun bar(): A {
return A("zerog") //private 생성자를 호출할 수 있다
}
}
}
//사용
A.bar()
동반 객체와 팩토리 패턴
동반 객체(companion object) 는 팩토리 패턴을 구현하는데 효과적입니다.클래스를 생성할 때 여러 생성자(constructor) 를 만들어서 객체를 생성할 수 있지만, 생성자가 많아지면 어떻게 클래스를 생성해야될지 헷갈릴때가 많습니다.
이때 팩토리 패턴을 사용하면 클래스를 생성할때 어떤 목적으로 만들때 필요한 생성자를 선택하는데 도움이 될수 있습니다.
private 생성자인 클래스를 만들고 companion object 블럭에서 User()를 생성하는 팩토리를 구현할 수 있습니다.
class User private constructor(val name: String) { //private 생성자
companion object {
//이메일로 닉네임을 뽑아 User 를 생성
fun newSubscribingUser(email:String) = User(email.substringBefore("@"))
//id 로 User 를 생성
fun newFacebookUser(id:Int) = User("${id}")
}
}
//사용
UserObject.newSubscribingUser("zerogdevinfo@gmail.com")
UserObject.newFacebookUser(1)
companion object {
//이메일로 닉네임을 뽑아 User 를 생성
fun newSubscribingUser(email:String) = User(email.substringBefore("@"))
//id 로 User 를 생성
fun newFacebookUser(id:Int) = User("${id}")
}
}
//사용
UserObject.newSubscribingUser("zerogdevinfo@gmail.com")
UserObject.newFacebookUser(1)
이처럼 팩토리 패턴을 사용하면 User 를 생성할 때 SubscribingUser와 FacebookUser 클래스가 따로 존재하는 경우에도 필요에 따라 맞는 클래스를 리턴할 수 있습니다.
그리고 이미 존재하는 email 경우 캐시된 클래스를 리턴할 수도 있습니다.
그러나 하위 클래스에서 오버라이드가 필요한경우에는 사용할 수 없습니다.
동반 객체는 오버라이드 할수 없기때문입니다.
이런 경우에는 여러 생성자(constructor)를 생성하는 것이 더 좋습니다.
open class User {
val name:String
//부 생성자
constructor(email: String) {
this.name = email.substringBefore("@")
}
//부생성자
constructor(id:Int) {
this.name = "${id}"
}
}
//상속이 필요한 경우 부 생성자를 여러개 생성하는 편이 좋음
class UserTest : User {
constructor(email: String) : super(email)
constructor(id: Int) : super(id)
}
val name:String
//부 생성자
constructor(email: String) {
this.name = email.substringBefore("@")
}
//부생성자
constructor(id:Int) {
this.name = "${id}"
}
}
//상속이 필요한 경우 부 생성자를 여러개 생성하는 편이 좋음
class UserTest : User {
constructor(email: String) : super(email)
constructor(id: Int) : super(id)
}
동반 객체의 이름
동반 객체에 이름을 사용할 수 있습니다.'companion object Loader' 에서 Loader 가 이름으로 사용할 수 있습니다.
이름을 사용하지 않으면 자동으로 Companion 이 이름으로 사용됩니다.
class A private constructor(val name: String) {
companion object Sub{ //동반객체에 이름을 붙일 수 있음
fun bar(): A {
return A("")
}
}
}
//사용
A.Sub.bar() //아래와 같은 방법
A.bar() //이름없이 사용 가능
companion object Sub{ //동반객체에 이름을 붙일 수 있음
fun bar(): A {
return A("")
}
}
}
//사용
A.Sub.bar() //아래와 같은 방법
A.bar() //이름없이 사용 가능
동반 객체의 인터페이스 구현
동반 객체도 인터페이스를 구현할 수 있습니다.
interface JSONFactory {
fun fromJSON(jsonText: String) : T
}
class Person(val name: String) {
//동반 객체가 인터페이스를 구현한다
companion object : JSONFactory<JSONPerson>{
override fun fromJSON(jsonText: String): Person {
return Person(jsonText)
}
}
}
fun fromJSON(jsonText: String) : T
}
//동반 객체가 인터페이스를 구현한다
companion object : JSONFactory<JSONPerson>{
override fun fromJSON(jsonText: String): Person {
return Person(jsonText)
}
}
}
그리고 동반 객체가 인터페이스를 구현하면 컴파일 할 때 Person.Companion 으로 바뀌어 아래 loadFromJSON(...) 함수에 넣어집니다.
fun loadFromJSON(factory: JSONFactory) : T {
//... }
//사용
loadFromJSON(Person)
//... }
loadFromJSON(Person)
동반 객체 확장 함수 사용
동반 객체에 확장 함수를 넣고 싶은 경우 비어있는 동반 객체를 선언합니다.그리고 비어있는 동반 객체에 확장 함수를 선언합니다.
class Person(val name: String) {
companion object {
//빈 동반 객체 선언 필요
fun fromJSON(json: String) {
}
}
}
//동반 객체에 대한 확장함수
fun Person.Companion.fromJSON(json:String) {
//...
}
//일반적인 확장함수
fun Person.fromJSON(json: String) {
//...
}
companion object {
//빈 동반 객체 선언 필요
fun fromJSON(json: String) {
}
}
}
//동반 객체에 대한 확장함수
fun Person.Companion.fromJSON(json:String) {
//...
}
//일반적인 확장함수
fun Person.fromJSON(json: String) {
//...
}
무명 객체(anonymous object)
안드로이드에서 클릭 리스너를 설정할 때 많이 사용하는 방법으로 무명 객체를 사용할 수 있습니다.이때 object 는 매번 새로운 인스턴스를 생성합니다.
그리고 object 블럭 내에서 로컬 변수 값에 접근할 수 있습니다.
btn.setOnClickListener(object : OnClickListener {
override fun onClick(v: View?) {
//무명객체
}
})
val listener = object : OnClickListener {
override fun onClick(v: View?) {
//변수에 대입
}
}
var clickCount = 0
val listener2 = object : OnClickListener {
override fun onClick(v: View?) {
//final 없이 객체 식 안에서 변수 접근 가능
clickCount++
}
}
override fun onClick(v: View?) {
//무명객체
}
})
val listener = object : OnClickListener {
override fun onClick(v: View?) {
//변수에 대입
}
}
var clickCount = 0
val listener2 = object : OnClickListener {
override fun onClick(v: View?) {
//final 없이 객체 식 안에서 변수 접근 가능
clickCount++
}
}
*개인적으로 코틀린을 공부하면서 정리한 자료입니다. 수정 사항 및 이슈가 있는 경우 메일 부탁드립니다.
댓글
댓글 쓰기