[Kotlin] Deep Dive into object

Kotlin에서 자주 사용하는 것 중에 하나로 object 키워드가 있다. Java에서 쓰던 것처럼 static factory 류의 메소드를 정의하기 위해 사용하거나 상수들을 모아둘 때, 또는 어쩔 수 없이 추상 클래스 또는 익명 클래스를 선언할 때 사용하기도 한다. 이처럼 Kotlin을 개발할 때 뗄레야 뗄 수 없는 object에 대해서 깊이 있게 알아보고 개발할 때 용도에 맞게 적절히 활용하도록 하자.


인스턴스 생성 관점

object 키워드는 크게 인스턴스의 생성 관점에서 두 가지 방식으로 나뉜다.

  • object 선언 : Singleton
  • object 표현식 : Non-Singleton

companion object도 있지 않나요? 라는 물음에는 답한다면 companion objectobject 선언과 인스턴스 생성 관점에선 한 묶음이라고 볼 수 있기 때문에 별도로 구분하진 않았다.


object 선언

object 선언은 Java로 디컴파일 해보면 알겠지만 public final class이며 내부에 해당 class의 인스턴스를 static 블록에서 초기화한다.

아래와 같이 object를 선언했다면

Java로 디컴파일 했을 경우 아래와 같은 코드를 확인할 수 있다.

object 선언에는 몇 가지 특징 및 제약사항이 있는데 다음과 같다.

  • object 선언은 생성자를 가질 수 없다.
  • object 안에 class처럼 멤버를 가질 수 있다.
  • class 내부에는 정의할 수 없는 const를 정의할 수 있다. (const는 Top level 또는 object에서만 정의 가능)
  • object 내부에 메소드를 정의하면 인스턴스 메소드로 정의된다. (@JvmStatic 어노테이션을 붙이면 static 메소드로 변경)
  • class or object 내부에 중첩 object 선언이 가능하다.
  • 중첩 object 선언에 개수 제한은 없다.
  • class or object 내부에 중첩으로 object를 선언하면 Java의 static inner class 형태가 된다.
  • object는 확장 가능한 classinterface를 상속받을 수 있다.

아래는 위와 같은 특징과 제약사항을 확인할 수 있는 샘플 코드다.


companion object

companion objectobject 선언과 비슷하지만 약간 다른 점이 있다. 우선 companion의 사전적 의미부터 찾아보자.

companion : 동반자, 동행, 친구(벗), 동지, 한 짝

사전적 의미를 바탕으로 유추를 해보면 class마다 한 쌍으로 동작하는 또는 종속되는 형태의 object라고 생각해 볼 수 있다. 그래서 object 선언과는 조금 다른 몇 가지 특징들이 있다.

아래는 companion object가 가지는 특징들인데 object 선언과 동일한 것도 있고 다른 것도 있다.

  • companion objectclass 당 한 개만 선언이 가능하다. class와 1 대 1 관계
  • companion object도 생성자를 가질 수 없다.
  • companion object는 top level에 선언할 수 없다.
  • companion objectobject 내부에는 선언할 수 없다.
  • companion object도 이름을 가질 수 있고 이름 없이 선언하면 Companion이라는 이름으로 인스턴스가 생성된다.
  • companion object도 내부에 정의된 메소드는 인스턴스 메소드다. @JvmStatic으로 선언하면 정적 메소드로 변경
  • companion object에서 Outer class의 멤버에 대한 접근은 불가능하다. static inner class 형태이기 때문에
  • Outer class에 대한 접근을 하기 위해서는 inner 키워드를 붙여야 하는데 objectcompanion object 모두 inner를 붙일 수 없다.
  • companion object도 확장 가능한 classinterface를 상속받을 수 있다.

위와 같은 특징들을 가지는 companion object는 어떻게 사용하는 것이 설계 목적에 맞게 적절히 사용하는 것일까?

필자가 생각하는 가장 적합한 용도는 위와 같이 해당 class의 팩토리 메소드를 정의하기 위해 사용하거나 또는 해당 class에 종속되는 개념의 상수들을 정의하기 위해 사용하는 것이다. 그 외에도 다양한 사용방법이 있을 수 있겠지만 그건 차차 다양한 이디엄을 경험해보면 깨닫게 될 것 같다.


object 표현식

object 선언과는 달리 object 표현식은 Singleton이 아니다. object 표현식은 다양하게 쓰일 수 있는데 object 표현식은 사용되는 곳은 다음과 같다.

  • 추상 클래스 인스턴스화
  • 인터페이스 인스턴스화
  • 익명 클래스 선언
  • 익명 오브젝트 선언
  • 단순 오브젝트 생성

예제 코드를 통해 하나씩 살펴보도록 하자.

추상 클래스 인스턴스화
인터페이스 인스턴스화
익명 클래스 선언

추상 클래스가 아닌 일반 클래스를 익명 클래스로 선언할 때는 확장 가능한 형태여야 한다. Kotlin의 기본 class는 final이기 때문에 익명 클래스로 선언하고자 한다면 확장 가능한 형태로 변경해야 한다.

익명 오브젝트 선언

위와 같이 메소드의 반환 타입으로 익명 오브젝트를 선언할 수 있다. 하지만 메소드의 접근 제어자가 private 일 때와 public 일 때 각각 동작이 다르다. private 일 때는 반환 타입이 익명 오브젝트 타입이지만 public 일 때는 반환 타입이 Any다.

단순 오브젝트 선언

위와 같이 단순히 몇몇 멤버 또는 메소드를 가지는 오브젝트를 선언할 수도 있다. 위의 코드를 Java로 디컴파일 해보면 다음과 같다.

위와 같이 단순 오브젝트 선언은 메소드 내부에 로컬로 정의할 수도 있고 클래스의 멤버로 정의할 수도 있다.


마치며

Kotlin에서 유용하게 사용하는 object 선언, object 표현식, companion object 등에 대해서 살펴보았다. 포스팅을 작성하면서 미처 알지 못했던 제약 사항도 알게 되었고 별 내용은 아니지만 많은 분들에게 공유드리고 싶었다.