[Kotlin] 클래스 10— object 키워드의 사용

dEpayse
dEpayse_publication
11 min readDec 29, 2020

--

object 키워드의 의미와 특징

object 키워드의 의미

Kotlin에서 object 키워드가 의미하는 바는 클래스를 정의함과 동시에 객체를 생성하는 것이다. object 키워드는 다양한 상황에서 쓰인다. 이번 포스트에서는 object 키워드가 어떻게 쓰이는 지 알아보려고 한다.

object 키워드로 생성된 객체의 특징

  • ‘class’ 키워드 대신 ‘object’ 키워드를 사용하여 클래스를 정의함과 동시에 객체를 생성한다.
  • 일반 클래스처럼 프로퍼티, 메서드, 초기화 블록을 가질 수 있지만, 생성자는 가질 수 없다.
  • 클래스를 상속 받을 수 있고, 여러 개의 인터페이스를 구현할 수 있다. 그러나 다른 클래스가 객체 선언 클래스를 상속 받을 순 없다. 클래스를 상속 받거나 인터페이스를 구현하는 방법은 클래스와 같다. 콜론(:) 뒤에 상속 받을 클래스나 인터페이스를 나열하면 된다.

object 키워드로 Kotlin에서 할 수 있는 것은 크게 세 개로 나뉜다.

Fig1. usage of object keyword

1. 객체 선언(Object Declaration)

객체 선언은 싱클턴 패턴을 쉽게 구현할 수 있게 해준다. 싱글턴 패턴이란 디자인 패턴 중 하나로, 객체가 단 하나 존재하는 클래스를 말한다. 이런 클래스가 필요한 경우 객체 선언을 통해 쉽게 구현할 수 있다. Example1에서는 클래스이자 단일 객체인 Score 하나로 학생들의 성적을 관리하는 경우를 다뤘다.

객체 선언은 클래스 정의하는 방법에서 ‘class’ 키워드를 ‘object’로 바꾼 것 뿐이다.

Example1. object declaration//객체 선언(object delcaration)
object
Score {
private val students = arrayListOf<Student>()
fun printScores() {
for (student in students) {
println("${student.name} score : ${student.score}")
}
}

fun addStudents(arrayList: ArrayList<Student>) {
students.addAll(arrayList)
}
}

class Student(val name: String, var score: Int, var group: String)

fun main() {
val students = arrayListOf(Student("Jim", 97, "SunFlower"),
Student("Sarah", 100, "Lilly"))
Score.addStudents(students)
Score.printScores()
}
//결과 :
Jim score : 97
Sarah score : 100

위와 같이 class 키워드 대신 object를 사용하면 객체가 단 하나 만들어지는 클래스를 만들 수 있고, 그 객체를 사용할 때엔 클래스의 이름을 통하여 사용할 수 있다.

2. 객체 식(Object Expression)

무명 객체 반환(Returning Anonymous object)

객체 ‘식’이라는 이름에서 볼 수 있듯이, 객체 식은 객체를 반환한다. 여기서 객체는 이름이 없는 무명(Anonymous) 객체이다.

Example2. Returning anonymous classfun main(){
val obj = object{}
}

객체 식의 활용

포스트 상단에서 object 키워드로 만들 객체도 클래스를 상속 받을 수 있고 인터페이스를 구현할 수 있다고 하였다. 추상 클래스나 인터페이스를 구현할 때, object 키워드를 사용하면 클래스 따로 정의하지 않아도 간편하게 구현을 할 수 있다. 객체 식을 정의하면 추상 클래스나 인터페이스를 구현한 객체가 생성되고, 이 객체를 변수에 저장하여 재사용하는 것도 가능하다.

익명의 내부 클래스 한 개는 여러 개의 추상 클래스나 인터페이스를 구현할 수 있고, 익명 객체를 포함하는 함수나 클래스의 프로퍼티에도 접근이 가능하고 var 프로퍼티의 경우 값을 변경할 수 있다.

Example3. 객체 식(object expression)의 활용interface Instrument {
fun play()
}

class Wind : Instrument {
override fun play() {
println("Wind.play()")
}
}

class Stringed : Instrument {
override fun play() {
println("Stringed.play()")
}
}

class Percussion : Instrument {
override fun play() {
println("Percussion.play()")
}
}

fun main() {
val arr: Array<Instrument> = arrayOf(Wind(), Stringed(), Percussion(), object:Instrument{
override fun play() {
println("Electric piano played")
}
}
)
for (instrument in arr) {
instrument.play()
}
}
/*
결과:
Wind.play()
Stringed.play()
Percussion.play()
Electric piano played
*/

클래스6 — 인터페이스, 추상클래스’ 포스트에서 본 예제 Example1을 확장한 것이다. 만약 관악기, 현악기, 타악기가 아닌 악기를 추가하고 싶은데, 그 악기는 새로운 종류의 클래스로 만들어 다루고 싶지 않고 프로그램 내에서 일시적으로 다룬다고 하자. 이런 경우를 구현한 것이 Example3이다. Example3의 메인 함수에서 arr이라는 배열에 객체 식을 이용하여 전자 피아노 악기를 추가했다.

3. 동반 객체(Companion Object)

동반 객체(companion object) 정의하기

일반적으로 클래스에 정의한 메서드, 프로퍼티는 생성한 객체마다 다른 값을 가질 수 있다. 그러나 어떤 경우에는 하나의 클래스에 속하는 객체들이 각각 다른 값을 갖는 것이 아닌 같은 값을 공유하도록 의도할 수 있다. 동반 객체는 그 경우에 사용될 수 있다. 즉, 하나의 클래스가 갖는 동반 객체(companion object)에는 그 클래스의 모든 객체가 공유하는 메서드, 프로퍼티를 정의할 수 있다. 동반 객체는 클래스 내부에 ‘companion object’라는 키워드를 사용하여 정의할 수 있고, 내부에는 메서드, 프로퍼티와 초기화블록이 올 수 있으나, 생성자는 만들 수 없다.

Example4. companion objectenum class Suit {
SPADE, HEART, CLUB, DIAMOND, JOKER
}

enum class Rank {
ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, JOKER;
override fun toString(): String {
return when(this.name){
"ACE" -> "A"
"TWO" -> "2"
"THREE" -> "3"
"FOUR" -> "4"
"FIVE"-> "5"
"SIX" -> "6"
"SEVEN" -> "7"
"EIGHT" -> "8"
"NINE" -> "9"
"TEN" -> "10"
"JACK" -> "J"
"QUEEN" -> "Q"
"KING" -> "K"
else -> this.name
}
}
}
class Card(val suit:Suit, val rank:Rank){
companion object {
var shape = "Rounded Rectangle"
}

override fun toString(): String {
return "(${suit}, ${rank})"
}
}

fun main(){
val myCards: ArrayList<Card> = arrayListOf()
myCards.add(Card(Suit.HEART,Rank.JACK))
myCards.add(Card(Suit.CLUB,Rank.QUEEN))
myCards.add(Card(Suit.DIAMOND,Rank.ACE))
println(myCards)
println(Card.Companion.shape)//companion object 사용법
}
/*
결과:
[(HEART, J), (CLUB, Q), (DIAMOND, A)]
Rounded Rectangle
*/

Example4는 원카드의 카드를 구현하여, 내 카드에 하트 모양 J, 클로버 모양 Q, 다이아몬드 모양 A를 추가한 것이다. 그러나 원카드의 각 카드 자체는 모두 둥근 직사각형 모양이므로, Card라는 클래스에 companion object의 프로퍼티로 선언하였다. 메인 함수에서 사용한 것을 확인할 수 있다. 만약 카드를 원모양으로 바꾸고 싶다면, companion object의 shape 프로퍼티의 값을 바꾸어주면 되는 것이다.

compile time에 결정되는 상수(const val)

Kotlin에서 compile time은 작성한 소스 코드가 Byte code로 바뀔 때까지의 시간을 말한다. 이 companion object에는 const val이라는 키워드를 사용하여 상수를 정의할 수 있다. const val은 우선 기본 자료형(Int, Char,…)과 String 타입의 값만 가질 수 있으며, compile time에 정의된다. 또한 관용적으로 변수명을 camelCase로 짓는 것이 아닌 UPPER_CASE_SNAKE_CASE로 지어 상수로 쓰일 이 변수를 다른 변수들과 구분짓는다. const val은 객체 선언(object declaration)과 파일의 최상위 위치에도 정의할 수 있다.

Example5. const val의 사용class A{
companion object{
const val PI = 3.141592
}
}

동반 객체 특징

  • 동반 객체의 접근은 객체를 통한 것이 아닌 클래스를 통하여 이루어진다.
  • 동반 객체도 이름을 가질 수 있다. 이름을 가졌을 때는 Companion이라는 키워드로 접근할 수 없고, 정의한 동반 객체의 이름을 통하여 접근할 수 있다. 그러나 접근할 때 동반 객체의 이름 없이 접근하는 것도 가능하다.
  • 동반 객체는 클래스 당 최대 하나 정의가 가능하다. ‘동반 객체’라는 이름에서 알 수 있듯이, 동반 객체를 생성하면 클래스와 동반하는 단 하나의 객체가 된다.
  • 동반 객체 역시 확장 함수를 정의할 수 있다. 그러나 동반 객체의 확장 함수를 정의하고 싶은 경우에는 반드시 클래스에서 빈 객체라도 동반 객체를 선언해주어야 한다.
Example6. 동반 객체의 특징class Card(val suit:Suit, val rank:Rank){
companion object {
var shape = "Rounded Rectangle"
}
override fun toString(): String {
return "(${suit}, ${rank})"
}
}

class Card2(val suit:Suit, val rank:Rank){
companion object CardConfig{
var shape = "Rounded Rectangle"
}
override fun toString(): String {
return "(${suit}, ${rank})"
}
}

fun main(){
//특징1. 동반 객체의 접근은 객체를 통한 것이 아닌 클래스를 통하여 이루어짐
val cardObj= Card(Suit.DIAMOND, Rank.QUEEN)
//println(cardObj.shape)//오류:객체 통해companion object에 접근불가
println(Card.shape)

//특징2. 동반 객체도 이름을 가질 수 있다.
println(Card.Companion.shape)
println(Card.shape)

//println(Card2.Companion.shape) //오류:companion object 이름 보유
println(Card2.CardConfig.shape)
println(Card2.shape)

}

//특징4. 동반 객체도 확장 함수 정의 가능
fun Card.Companion.showSuit(){}
Fig2. object keyword 정리

Reference

Overall part

  1. Dmitry Jemerov and Svetlana Isakova. (2017). Kotlin in Action. USA: Manning
  2. Kotlin documentshttps://kotlinlang.org/docs/reference/

--

--

dEpayse
dEpayse_publication

나뿐만 아니라 다른 사람들도 이해할 수 있도록 작성하는, 친절한 블로그를 목표로.