본문 바로가기
Language/Kotlin

[Kotlin IN ACTION] - Kotlin 클래스 선언(주 생성자, 프로퍼티)

by Jman 2022. 7. 25.

클래스 초기화

아래의 코드를 보고 설명하겠다.

class User(val nickname : String)

보통 클래스의 모든 선언은 중괄화 '{ }' 사이에 들어간다.

하지만, 이 클래스 선언에는 중괄호가 없고 괄호 사이에 val 선언만 존재한다.

그 이유는 무엇일까?

이렇게 클래스 이름 뒤에 오는 괄호로 둘러 싸인 코드를 주 생성자 (primary constructor)라고 부른다.

 

주 생성자는 두 가지 목적에 쓰인다.

  • 생성자 파라미터를 지정한다
  • 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.

 

위 코드처럼 간단한 클래스 선언이 아닌, 같은 목적을 달성할 수 있는 가장 명시적인 선언으로 푼 코드를 확인해보자.

class User constructor(_nickname : String) { // 밑줄(_)은 프로퍼티와 생성자 파라미터를 구분해준다.
    private val nickname : String
    
    init {
        nickname = _nickname
    }
}

새로운 키워드 constructor 이랑 init 을 볼 수 있다.

  • constructor : 주 생성자나 부 생성자 정의를 시작할 때 사용한다.
  • init : 초기화 블록을 시작한다. 초기화 블록에는 클래스의 객체가 만들어질 때(인스턴스화될 때) 실행될 초기화 코드가 들어간다.

초기화 블록은 주 생성자와 함께 사용된다.

주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다.

 

[Java]

class User() {
	String nickname;
    
	public User(String nickname) {
    	this.nickname = nickname;
    }
}

위 자바 코드를 아래 코틀린 코드와 비교해서 보면 좋을 거 같다.

 

[Kotlin]

class User (_nickname : String) {	// 파라미터가 하나 뿐인 주 생성자
	val nickname = _nickname // 프로퍼티를 주 생성자의 파라미터로 초기화 한다.
}

코틀린에서 위 코드는 클래스를 정의하는 여러 방법 중 하나다.

프로퍼티를 초기화하는 식이나, 초기화 블록 안에서만 주 생성자의 파라미터를 참조할 수 있다는 점을 유의하라.

또한, 앞에 별다른 애노테이션이나 가시성 변경자가 없다면 constructor를 생략해도 된다.

 

위 코드보다 동일하지만, 더 간결한 방법이 있다.

val 를 추가하는 방식으로 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.

// 가장 간결!
class User(val nickname : String) // 'val' 은 이 파라머티에 상응하는 프로퍼티가 생성된다는 뜻이다.

 

또한, 생성자 파라미터에도 디폴트 값을 정의할 수가 있다.

class User(val nickname : String, 
			val isSubscribed Boolean = true) // 생성자 파라미터에 대한 디폴트 값을 제공

 

클래스의 인스턴스를 만들려면 new 키워드 없이 생성자를 직접 호출하면 된다.

val chief = User("정민")
println(chief.isSubscribed)

//true

 

클래스에 기반 클래스가 있다면, 주 생성자에서 기반 클래스의 생성자를 호출해야 할 필요가 있다.

open class User(val nickname : String) {...}

class TwitterUser(nickname: String) : User(nickname) { ...}

 

클래스를 정의할 때, 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무 일도 하지 않는 인자가 없는 디폴트 생성자가 만들어진다.

open class Button // 인자가 없는 디폴트 생성자가 만들어짐.

그리고 위 코드를 확인 해보면 Button 생성자는  아무 인자도 받지 않지만, Button 클래스를 상속한 하위 클래스는 반드시 Button 클래스의 생성자를 호출해야 한다.

class RadioButton : Button()

위 코드를 보면 빈 괄호가 들어간 것을 확인할 수 있다.

생성자 인자가 있다면 괄호 안에 인자가 들어가겠지만, 기반 클래스의 이름 뒤에는 꼭 괄호를 넣어야 한다.

 

반면, 인터페이스는 생성자가 없기 때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무 괄호도 없다.

 

어떤 클래스를 클래스 외부에서 인스턴스화 하지 못하게 막고 싶다면, 모든 생성자를 private 으로 만들면 된다.

class Secretive private constructor() {} // 이 클래스의 (유일한) 주 생성자는 비공개가 됨.