Skils/Kotlin

[Kotlin] 객체(Object)

재한 2022. 9. 12. 20:14

👀학습목표

  • 객채 선언의 정의
  • 객채 선언의 의미와 객체 선언을 통한 싱글톤
  • 자바 익명 클래스와 비슷한 객체 식

📕객체 선언

코틀린은 어떤 클래스에 인스턴스가 오직 하나만 존재하게 보장하는 싱글턴 패턴을 내장하고 있다. 

싱글턴의 선언 방법은 클래스와 비슷한 방법으로 선언한다. [class 대신 object라는 키워드 사용]

import java.io.*
object Application {
    val name="My application"
    override fun toString()=name
    fun exit(){}
}
fun describe(app:Application)=app.name //Application은 타입임.
fun main(){
    println(Application) //Application은 값임.
}

일반적으로 객체의 인스턴스는 단 하나뿐이므로 인스턴스만 가리켜도 어떤 타입을 쓰는지 충분히 알 수 있다.

따라서 객체를 타입으로 사용해도 무의미하다.

 

  • 클래스와 마찬가지로 객체 선언도 멤버 함수와 프로퍼티를 포함할 수 있고, 초기화 블록도 포함할 수 있다.
  • 하지만 객체에는 주생 성자나 부생 성자가 없다. 객체 인스턴스는 항상 암시적으로 만들어지기 때문에 객체의 경우 생성자 호출이 아무런 의미가 없다.
  • 객체의 본문에 들어있는 클래스에는 inner 사용을 금지한다.
  • 최상위 선언들과 마찬가지로, 객체의 멤버를 임포트 해서 간단한 이름만 사용해 다른 파일에서 참조할 수 있다.
    • ex) import Application.exit //success
  • 하지만 객체의 모든 멤버가 필요할 때 임포트 문으로 임포트 할 수는 없다.
    • ex) import Application.* //error
    • 객체 정의 안에는 다른 클래스 정의와 같이 toString()이나 equals() 같은 공통 메서드 정의가 들어있기 때문에 사용하지도 않을 공통 메서드까지 임포트 돼 문제가 생길 수 있기에 제약을 걸어둔다.

 

📕동반 객체

  • priavte로 생성자를 지정해 클래스로 외부에서 사용할 수 없게 한 다음 내포된 객체를 만듬.
  • companion이라는 키워드를 덧붙인 내포된 객체.
  • 동반 객체의 멤버에 접근할 때는 동반 객체의 이름을 사용하지 않고 동반 객체가 들어있는 외부 클래스의 이름을 사용할 수 있다.
  • 정의에서 이름을 아예 생략할 수 있다. 아래 코드에서는 동반 객체의 이름은 Factory이다.
    • 만약 생략한 경우 동반 객체의 디폴트 이름은 Companion으로 가정한다.
    • 동반 객체의 멤버를 임포트 하고 싶을 때는 객체 이름을 명시해야 한다.
      • import Application.create //error
      • import Application.Companion.create //ok
  • 한 클래스에서 동반 객체가 두 개 이상일 수는 없다.
import java.io.*
class Application private constructor(val name : String){
    companion object Factory{
        fun create(args: String) : Application?{
            val name= args?: return null
            return Application(name)
        }
    }
}
fun main()
{
    val str=readLine()!!.toString()
    val app=Application.create(str)?:return
    println("Application started : ${app.name}")
}

📕객체 식 

코틀린은 명시적인 선언 없이 객체를 바로 생성할 수 있는 객체식을 제공한다. 객체 식은 자바 익명 클래스와 아주 비슷하다.

fun main(){
    fun midPoint(xRange : IntRange, yRange:IntRange)= object{
        val x= (xRange.first + xRange.last)/2
        val y= (yRange.first+ yRange.last)/2
    }
    val midPoint= midPoint(1..5,2..6)
    println("${midPoint.x},${midPoint.y}")
}

위 코드를 보면 알 수 있듯이 객체 식은 이름이 없는 객체 정의처럼 보입니다.

그리고 코틀린에서 처럼 객체 식도 식이므로, 예제처럼 객체 식이 만들어내는 값을 변수에 대입할 수 있습니다. 클래스나 객체 식과 달리 객체를 함수 안에 정의할 수는 없습니다.

fun printMiddle(xRange : IntRange, yRange : IntRange){
    object MidPoint{
        val x= (xRange.first+xRange.last)/2
        val y=(yRange.first+yRange.last)/2
    }
    println("${MidPoint.x},${MidPoint.y}")
}

객체를 함수 안에 정의할 수 없도록 결정한 이유는 객체 선언이 싱글턴을 표현하지만, 지역 객체들은 자신을 둘러싼 바깥 함수가 호출될 때마다 매번 다시 생성돼야 하기 때문입니다.

 

fun MidPoint()의 반환 타입은 객체 식 안에 정의된 모든 멤버가 들어있는 클래스를 표현하는 익명 객체 타입(anonymous object type)이며, 단 하나만 존재하는 타입이다.

💡즉 똑같은 두 객체 식이 있다고 가정했을 때, 두 개의 익명 개체 타입은 다른 겁니다!

 


객체 식이 만들어내는 객체고 다른 클래스 인스턴스와 마찬가지로 사용할 수 있습니다.

fun main(){
    val o= object{ //익명 개체 타입이 추론됨.
        val x=readLine()!!.toInt()
        val y = readLine()!!.toInt()
    }
    println(o.x + o.y) //객체 밖에서 객체 타입 인스턴스에 접근이 가능함.
}

 

하지만 익명 객체 타입은 지역 선언이나 비공개 선언(private)에만 전달될 수 있습니다. 

아래 코드는 midPoint라는 함수를 최상위 함수로 정의한 코드입니다.

fun midPoint(xRange: IntRange, yRange:IntRange)=object{
    val x= (xRange.first+xRange.last)/2
    val y=(yRange.first+yRange.last)/2
}
fun main(){
    val midPoint(1..5, 2..6)
    println("${midPoint.x},${midPoint.y}") //객체 멤버에 접근할때 오류가 발생합니다
}

여기서 midPoint 함수의 타입은 익명 객체 타입이 아니라 Any타입이 됩니다. 그래서 midPoint.x 참조에서 x를 찾을 수 없는 것입니다.

✔정리

  • 코틀린은 싱글턴 패턴(인스턴스가 단 하나만 존재하는 클래스)을 내장하고 있다.
  • 객체 안에 정의된 클래스에 대해서는 inner 사용을 금지한다. -> 항상 인스턴스가 하나뿐이므로, 불필요함.
  • 동반 객체는 팩토리 디자인 패턴을 쉽게 구현하는 경우 유용하게 활용할 수 있다.
    • 선언은 companion이라는 키워드를 붙여준다.
  • 객체의 멤버를 임포트 해서 사용할 수 있지만, 모든 멤버가 필요할 때는 임포트 문으로 임포트 할 수 없다.
    • 객체 정의 안에 toString(), equals()등 공통 메서드 정의까지 불 필요하게 임포트 돼 문제가 생길 수 있기 때문이다.
  • 코틀린은 명시적인 선언 없이 객체를 바로 생성할 수 있는 객체식을 제공한다.
  • 객체 식은 기존 클래스의 하위 클래스를 선언하지 않고도 기존 클래스를 약간만 변경해 기술하는 간결한 방법을 제공한다.