기록하기

[Kotlin 기본 정리] Classes, Constructors, Companion Object 본문

language/kotlin

[Kotlin 기본 정리] Classes, Constructors, Companion Object

jjungdev 2023. 9. 24. 20:12

혼자 만들어 보려는 toy project 에는 Kotlin 을 활용해서 만들어보려고 한다.

 

그러기 위해서는 일단 기초적인 내용 학습과 더불어 간단한 web application 을 만들어봐야겠다고 생각을 했고, 학습을 위해 인프런 강의 2개를 수강하고 있다.

수강을 하면서 간단하게 내용을 정리해보려는데 강의 내용과 코틀린 공식 문서를 참고해서 내용 정리를 해보려고 한다.

 

(* Kotiln 을 코틀린이라고 명시함.)

 

Classes

먼저, Class 는 간단한데 코틀린에서는 class 라는 키워드를 활용해서 정의하며 body 가 없으면 {} 는 생략 가능하다. 

class Person(
    //기본 생성자
    val name: String,
    var age: Int
) {

 

Constructors

위 예시를 보면 () 안에 작성한 내용을 확인할 수 있는데 이는 기본 생성자를 뜻한다.

기본 생성자는 클래스 헤더에서 클래스 인스턴스와 해당 속성을 초기화하며, constructor 라고 명시적으로 작성할 수도 있는데 만약 어떠한 annotation 이나 modifier 가 없다면 contructor 는 생략할 수 있다.

class Person constructor(name: String, age: Int) {}

이때 기본 생성자와 관련해서 초기화 블록과 부생성자를 같이 이해해야 한다.

 

초기화 블록

클래스 헤더에 작성한 기본 생성자에는 실행 가능한 코드가 포함될 수 없는데, 만약 객체 생성 중에 어떤 코드를 실행하려면 초기화 블록을 사용해야한다.

fun main() {
    val person = Person("park", 20)
}

class Person(
    //기본 생성자
    val name: String,
    var age: Int
) {
    init {
        if (age <= 0) {
            //코드 실행
        }
        println("초기화 블록")
    }

}

 

이렇게 작성한 뒤 main 함수를 실행해보면 아래와 같이 출력되는 것을 확인할 수 있다.

 

부생성자

코틀린에서는 기본 생성자와 하나 이상의 보조 생성자가 있을 수 있다.

여기서 기본 생성자와 부생성자를 같이 사용한다면 아래와 같이 작성해볼 수 있고, 여러 부생성자를 작성할수도 있다.

fun main() {
    val person = Person()
}

class Person(
    //기본 생성자
    val name: String,
    var age: Int
) {
    init {
        if (age <= 0) {
            //코드 실행
        }
        println("초기화 블록")
    }

    //첫번째 생성자, 부생성자
    constructor(name: String) : this(name, 1) {
        println("부생성자1")
    }

    //두번째 생성자를 부르고 -> 두번째 생성자는 다시 첫번째 생성자를 부른다.
    constructor() : this("홍길동") {
        println("부생성자2")
    }

}

부생성자는 기본 생성자를 호출하게 되는데 main 함수를 호출하면 결과는 아래와 같다.

그런데 부생성자 방식 보다는 default parameter 나 정적 팩토리 메소드 방법을 추천하는데 이는 이펙티브 자바에서도 나오는 내용이다. 생성자 보다 정적 팩토리 메소드를 활용하게 되면 다음과 같은 이점이 있다.

 

  • 이름을 가질 수 있다.
  • 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  • 하위 객체를 반환할 수 있다.
  • 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다.

자바에서는 static 을 활용해서 정적 메소드를 작성할 수 있지만 코틀린에서는 static 키워드가 없다고 하여 이때 Companion Object 를 활용한다고 한다.

 

Companion Object

클래스와 동행하는 유일한 오브젝트로 동반객체도 하나의 객체로 간주된다. 그렇기 때문에 이름을 붙일 수도 있고, interface 를 구현할 수도 있다.

 

만약 다음과 같은 예시가 있다고 가정해보자

class Person(
    var name: String,
    var age: Int,
) {

    // static 대신 companion object 사용한다.
    companion object Factory {
        //val => 런타임 시에 변수가 할당
        //const => 컴파일 시에 변수가 할당, 진짜 상수에 붙이기 위한 용도, 기본 타입과 String 에만 붙일 수 있다.
        private const val MIN_AGE = 1
        fun newPerson(name: String): Person {
            return Person(name, MIN_AGE)
        }
    }

}

 

이 정적 팩토리 메소드를 활용하기 위해서는 다음과 같은 방법을 활용할 수 있다. 

  1. Factory 라는 이름이 있다면 Person.Factory.newPerson("ABC") 이렇게 사용할 수 있다.
  2. 그런데 만약 Factory 라는 이름이 없다면,
    • newPerson 위에 @JvmStatic 을 붙여주어 Person.newPerson("ABC") 로 활용할 수 있다.
    • 혹은 Person.Companion.newPerson("ABC") 로 작성해서 활용할 수 있다.

 

fun main() {
    val person = Person.Factory.newPerson("ABC")
    println(person.name)
    println(person.age)
}

class Person private constructor(
    var name: String,
    var age: Int,
) {

    // static 대신 companion object 사용한다.
    companion object Factory {
        //val => 런타임 시에 변수가 할당
        //const => 컴파일 시에 변수가 할당, 진짜 상수에 붙이기 위한 용도, 기본 타입과 String 에만 붙일 수 있다.
        private const val MIN_AGE = 1
        fun newPerson(name: String): Person {
            return Person(name, MIN_AGE)
        }
    }

}

 

출력을 해보면 다음과 같은 결과를 얻을 수 있다.

 

static 의 경우 자바와는 방법이 달라서 헷갈리는 부분이 있었지만, 활용 방법들이 다양해서 3가지의 방법으로 정적 팩토리 메소드를 만들 수 있다는 점이 유연한 부분인 것 같다.

 

다음에는 상속에 대한 이야기를 다뤄보도록 하겠다.