코틀린 생성자, primary constructor

jYur·2022년 9월 6일
0

코틀린에서 생성자의 역할이 자바나 C++ 등의 다른 언어에서 생성자의 역할과 다르다고 생각하지는 않아. 근데 나도 처음 봤을 때는 좀 어리둥절했어. 생성자가 뭔지 아는데도 코틀린에서 primary constructor 때문에 헷갈리는 사람은 아마 이 글이 좀 도움될 거야.

1. 차근차근

먼저 많이들 알고 있을 자바와 비슷하게 생성자들을 만들어 볼게.
그리고 나서 하나씩 primary constructor로 한번 바꿔 볼게.

1.0. 자바와 비슷하게

일단, 여기선 이해하기 쉽게 이름들을 한글로 지을게.
현대 그랜저(자동차)를 클래스로 만들 건데, 차주는 없으면 empty string으로 둘 거고 연료 기본값은 0으로 설정할게.

class 현대그랜저 {
    var 차주: String = ""
    var 연료: Int = 0

    constructor() { // 파라미터가 없는 기본 생성자
    }

    constructor(차주: String) { // 파라미터가 String 하나인 생성자
        this.차주 = 차주
    }

    constructor(차주: String, 연료: Int) { // 파라미터가 String 하나, Int 하나인 생성자
        this.차주 = 차주
        this.연료 = 연료
    }
}

자바나 기타 언어에 익숙하다면 여기까지는 따로 설명을 하지 않아도 이해가 될 거라고 생각해. 코틀린에서는 위에 나온 생성자들을 전부 secondary constructors라고 불러.

1.1. 기본 생성자를 primary로

이제 파라미터가 없는 기본 생성자가 primary 생성자인 경우를 보자.

class 현대그랜저 constructor() { // (가)
    var 차주: String = "" 
    var 연료: Int = 0

    constructor(차주: String) : this() { // (나)
        this.차주 = 차주
    }

    constructor(차주: String, 연료: Int) : this() { // (다)
        this.차주 = 차주
        this.연료 = 연료
    }
}

(가) 파라미터가 없는 기본 생성자를 primary로 선언했어.
(나), (다) 생성자 오른쪽에 : this()가 붙었어.
primary 생성자를 호출한다는 뜻이야.
primary 생성자가 있으면 secondary 생성자들은 반드시 : this()로 primary 생성자를 호출해야 해.
호출 안 하면? 에러: Primary constructor call expected

1.2. 차주만 받는 생성자를 primary로

이번에는 차주 파라미터만 가진 생성자가 primary 생성자인 경우를 보자.

class 현대그랜저 constructor(차주: String) { // (가)
    var 차주: String
    var 연료: Int = 0

    constructor() : this("") { // (나)
    }

    init { // (다)
        this.차주 = 차주
    }

    constructor(차주: String, 연료: Int) : this(차주) { // (라)
        this.연료 = 연료
    }
}

(가) 차주 파라미터만 가진 생성자를 primary로 선언했어.
(나) 파라미터가 없는 기본 secondary 생성자가 primary 생성자를 호출하면서 empty string을 전달하고 있어.
(다) 아마 처음 볼 텐데, initializer blocks라는 거야.
차주만 받는 생성자가 primary가 되면서, this.차주 = 차주(secondary 생성자였을 때의 코드)가 init {} 안으로 이동했어.

아래에서 primary 생성자를 훨씬 간결하게 사용하는 법을 보여준다.

(라) 차주연료를 받는 secondary 생성자가 primary 생성자를 호출하면서 자기가 받은 차주를 primary 생성자에게 다시 전달하고 있어.

1.3. 차주와 연료를 받는 생성자를 primary로

마지막으로 차주 파라미터와 연료 파라미터를 가진 생성자가 primary 생성자일 때를 보자.

class 현대그랜저 constructor(차주: String, 연료: Int) {
    var 차주: String
    var 연료: Int

    init {
        this.차주 = 차주
        this.연료 = 연료
    }

    constructor() : this("")

    constructor(차주: String) : this(차주, 0)
}

패턴은 위에서 바꿔 본 것과 같아.

참고로, init 블록을 생성자들 위로 올렸지만 변수 선언 아래라면 순서는 상관없어.

2. 간결하게!

여기서부터가 바로 primary 생성자를 사용하는 이유지.

2.0. constructor 키워드 생략

위에서 primary 생성자를 만들 때, 클래스 이름 오른쪽에 constructor 키워드와 괄호()를 사용했어. 그런데 constructor 키워드 왼쪽에 private이라든지 기타 다른 키워드들이 안 붙었을 때는 아래와 같이 constructor 키워드를 생략할 수 있어.

class 현대그랜저(차주: String, 연료: Int) {
	// 생략
}

primary 생성자 파라미터 및 프로퍼티 선언과 초기화 코드 작성을 한 번에 하는 방법도 있어.

2.1. 파라미터를 정의하면서 클래스 프로퍼티 선언 및 초기화까지 한 번에

일단 코드부터 볼까?

BEFORE

class 현대그랜저(차주: String) {
	var 차주: String

	init {
	    this.차주 = 차주
	}
}

또는

class 현대그랜저(차주: String) {
	var 차주 = 차주
}

AFTER

class 현대그랜저(var 차주: String)

파라미터 차주 왼쪽에 var 하나 붙여 줬어.
AFTER 코드는 BEFORE 코드와 똑같이 동작해.

2.2. 응용

일반 파라미터, var 붙은 파라미터, val 붙은 파라미터 섞어 볼까?

class 현대그랜저(
    var 차주: String,
    var 연료: Int,
    블랙박스옵션유무: Boolean,
    val 연식: String,
) {
    var 블랙박스옵션유무 = 블랙박스옵션유무
        private set
}

fun main() {
    val 김그랜저 = 현대그랜저(
        차주 = "김씨",
        연료 = 20,
        블랙박스옵션유무 = true,
        연식 = "2011"
    )
    println(김그랜저.차주)          // 김씨
    김그랜저.연료 = 30
    println(김그랜저.연료)          // 30
    println(김그랜저.블랙박스옵션유무) // true
    println(김그랜저.연식)          // 2011
    
    // 김그랜저.블랙박스옵션유무 = false // ERROR!
}

블랙박스옵션유무에 붙은private set은 외부에서 프로퍼티를 변경할 수 없도록 하기 위한 것.
더 자세한 내용은 객체 데이터 보호 (Feat. Kotlin) 참고.

3. private constructor

필요에 따라 객체 외부에서 생성자를 사용하지 못하게 할 수도 있지.

private secondary constructor

class 현대그랜저 {
	var 차주: String

	private constructor(차주: String) {
		this.차주 = 차주
	}
}

private primary constructor

class 현대그랜저 private constructor(var 차주: String)

아니, 생성자를 막아버리면 객체는 어떻게 만드냐..!?
아래처럼 만들면 돼.

class 현대그랜저 private constructor(var 차주: String) {

	companion object {
		fun create현대그랜저of차주(차주: String): 현대그랜저 {
			return 현대그랜저(차주)
		}
	}
}

fun main() {
    val 김그랜저 = 현대그랜저.create현대그랜저of차주("김씨")
}

0개의 댓글