Antilog의 개발로 쓰다
반응형

Kotlin 을 이용한 Spring Boot 스터디 진행 중 Kotlin 기초를 진행한 후 Java를 Kotlin 으로 변환하는 과정에서 자바와 다른 생성자를 만드는 과정에서 Kotlin 만의 생성자 생성 방식이 햇갈릴 수 있다는 생각이 들어 내용을 정리하게 되었습니다.

Kotlin 의 Class와 Interface의 경우 Java의 Class, Interface와 약간의 차이가 있습니다.

대표적인 부분이 Kotlin 은 기본적으로 대부분의 선언이 public final 이라는 부분에서 생기고, 중첩 클래스의 경우 기본적으로 내부 클래스가 아닌 점 등

기본적으로 Kotlin 에서는 Java에서 진행하던 번잡스러운 과정이 없기 때문에 발생하는 차이라고 생각됩니다.

이러한 부분에 대해 Kotlin 에서 생성자를 다루는 방법을 정리하고 또 Java로 Decompile 하면 어떤 형태가 되는지 정리해보고자 합니다.

Kotlin의 Primary Constructor(주 생성자)

파라미터 지정 및 프로퍼티 정의를 한번에 하는 생성자

class Member(
    private val birth: String,
    var age: Int,
    var name: String
)

Kotlin 에서는 위와 같이 클래스 이름 옆에 괄호로 둘러싸인 코드를 Primary Constuctor 주생성자 라고 부릅니다.

주 생성자는 주로 클래스를 초기화 할때 주로 사용하는 간략한 생성자를 이야기 하며, 클래스 본문이 아닌 밖에서 정의합니다.

주 생성자는 생성자 파라미터를 지정하고 그 생성자 파라미터에 의해 초기화 되는 프로퍼티를 정의하는 두 가지 기능을 할 수 있습니다.

따라서 위 코드에서는 Member 생성자 파라미터로 받은 값을 바탕으로 동시에 그에 상응하는 프로퍼티 또한 한번에 정의합니다.

Kotlin 에서 프로퍼티는 간단히 말하면 Java의 맴버 변수와 Getter/Setter를 포함한 개념입니다.

위 Kotlin 코드를 Java로 Decompile 하게 되면 아래의 코드를 확인 할 수 있습니다.

public final class Member {
   private final String birth;
   private int age;
   @NotNull
   private String name;

   public final int getAge() {
      return this.age;
   }

   public final void setAge(int var1) {
      this.age = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.name = var1;
   }

   public Member(@NotNull String birth, int age, @NotNull String name) {
      Intrinsics.checkNotNullParameter(birth, "birth");
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.birth = birth;
      this.age = age;
      this.name = name;
   }
}

직접 초기화를 하는 생성자

위와 같이 단순하게 파라미터를 지정하고 초기화 하는 경우가 아닌 직접 초기화를 해야 하는 경우가 발생하기도 합니다. 단순하게 주 생성자 부분을 사용한다면 별도의 코드를 포함할 수 없기 때문에 Kotlin 에서는 이를 지원하는 init 키워드를 활용한 초기화 블록을 제공합니다.

class Member(
    birth: String,
    age: Int,
    name: String
) {
    private val birth: String
    var age: Int
        private set
    var name: String = name

    init {
        // birth 유효성 검사
        this.birth = birth
        this.age = age
    }
}

위와 같이 작성하게 되면 Member 객체를 생성하는 시점에 출생일의 유효성 검사를 init 블록에서 진행함으로 유효한 출생일이 들어왔는지 확인이 가능합니다.

유효성 검사를 위한 확인 뿐 아니라 age와 같이 내부에서만 그 값을 변경할 수 있는 경우 Setter를 private으로 하는 프로퍼티에 값을 직접 초기화 하는 방식으로도 사용이 가능합니다.

유효성 검사 및 프로퍼티에 추가 작업이 필요한 경우가 아니라면 name과 같이 init 블록을 거치지 않고 프로퍼티에서 직접 초기화를 해주는 방법도 존재합니다.

위 코드를 Java로 Decompile 하면 다음과 같습니다.

public final class Member {
   private final String birth;
   private int age;
   @NotNull
   private String name;
     // 이전 코드와 다르게 setter를 private 으로 처리하였기에 
   // 내부에서만 값을 바꿀 수 있도록 setter를 생성하지 않음
   public final int getAge() {
      return this.age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.name = var1;
   }

   public Member(@NotNull String birth, int age, @NotNull String name) {
      Intrinsics.checkNotNullParameter(birth, "birth");
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.birth = birth;
      this.age = age;
   }
}

Private Primary Constructor

만약 객체의 생성을 제한 해야 하는 경우에는 생성자를 private으로 만들게 될 수 있습니다.

이 경우에는 주 생성자에 constructor 키워드를 통해 생성자를 감출 수 있습니다.

class Member private constructor(
    birth: String,
    age: Int,
    name: String
) {
    private val birth: String
    var age: Int
        private set
    var name: String = name

    init {
        // birth 유효성 검사
        this.birth = birth
        this.age = age
    }
}

Kotlin 의 Secondary Constructor( 부 생성자 )

객체 생성시 생성자가 여러가지 필요한 경우가 있습니다. 이런 경우에는 위에서 살펴본 constructor 키워드를 클래스 블록 내에서 사용하여 직접 부 생성자를 만들어 줄 수 있습니다.

class Member private constructor(
    private val birth: String,
    var name: String
) {
    var age : Int? = null
        private set

        // 부 생성자
    constructor(birth: String, age: Int, name: String): this(birth, name) {
        this.age = age
    }
}

constructor 키워드를 이용하면 위 코드와 같이 부 생성자를 만들 수 있습니다.

특이한 부분은 : this() 부분인데 Kotlin 에서는 주 생성자가 존재한다면 부 생성자는 반드시 주 생성자에게 생성을 위임해야하기 때문에 이 부분을 위임하고 있는 코드라고 보시면 될 것 같습니다 :)

하지만 위 코드의 예시처럼 사용하는 경우는 개인적 경험에서는 거의 없었습니다. Java의 경우는 생성자가 여럿 존재하는 경우가 많이 발생하지만, Kotlin 은 디폴트 값을 제공할 수 있기 때문에 이를 사용하면 부 생성자가 필요한 경우가 거의 없었습니다.

class Member(
    private val birth: String = "2000.01.01",
    age: Int,
    var name: String = "익명의 회원"
) {
    var age : Int = age
        private set
}

따라서 위와 같이 작성하면 age 만 받는 생성자, birth, age 를 받는 생성자, age, name 을 받는 생성자, birth, age, name 을 받는 생성자 총 4가지 생성자가 있는 것과 같은 효과로 사용할 수 있습니다.

또한 kotlin 에서는 객체 생성 과정에서 초기화 할 파라미터를 지정할 수 있어 디폴트 값을 잘 이용하면 builder 패턴과 유사한 효과 또한 얻을 수 있습니다.

fun main() {
    val memberA = Member(age = 20)
    val memberB = Member(birth = "1999.06.26", age = 25)
    val memberC = Member(age = 24, name = "JS")
}

참고 자료

  • Kotlin in Action

반응형
profile

Antilog의 개발로 쓰다

@Parker_J_S

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...