Properties and Fields


Properties 란?

 코틀린의 클래스는 properties를 가질 수 있습니다. Java로 생각하면 인스턴스 변수입니다. 멤버 변수라고 하지 않는 이유는 멤버 변수에 static 변수도 포함되기 때문입니다.


Java의 멤버 변수

 class Person {

   int id; // 인스턴스 변수 -> 멤버 변수

   String name; // 인스턴스 변수 -> 멤버 변수

   static int age; // static 변수, 클래스 변수 -> 멤버 변수

 }


 코틀린에서 properties는 다음과 같이 선언할 수 있습니다. Java와 변수 선언 방법만 다를 뿐 큰 차이가 없습니다.

 class Person {

  var id: Int = 0

  var name: String = "Suzuki"

 }


클래스의 인스턴스 생성하기

 properties 사용방법도 거의 동일합니다. 객체를 만들어서 Java의 field처럼 쉽게 변수명으로 부를 수 있습니다.

 val student = Person() // new 키워드를 사용하지 않음

 student.id = 10

 student.name = "Kim"


static 변수처럼 사용하기

 위에서 언급한 Java의 static 변수를 코틀린에서 유사하게 사용하는 방법이 있습니다. Companion Objects을 사용하면 됩니다. '클래스명.변수'로 접근하는 형태가 Java의 static 변수와 동일합니다.
 (Companion Objects에 대해서는 다시 한 번 공부해야겠습니다.)

     fun kotlin() {

        val student = Person()

        student.id = 10

        student.name = "Kim"

        Person.age = 20


        println("student.id = ${student.id}")

        println("student.name  = ${student.name}")

        println("Person.age = ${Person.age}")

    }


    class Person {

        var id: Int = 0

        var name: String = "Suzuki"

        companion object {

            var age: Int = 0

        }

    }

 student.id = 10

 student.name  = Kim

 Person.age = 20


코틀린 클래스의 initializer blocks

 코틀린의 primary constructor(이하 주생성자)는 별도의 코드를 포함할 수 없습니다. 대신 초기화 코드는 init 이라는 키워드를 이용한 initializer blocks에서 선언할 수 있습니다.

    fun kotlin() {

        Student(name = "John")

    }


    class Student(var name: String, var id: Int = 0) {

        constructor(name: String) : this(name, 100) {

            println("constructor block :: name is $name and id is $id")

        }

        init {

            name = "kim"

            println("init block :: name is $name and id is $id")

        }

    }


 위 코드의 결과는 아래와 같습니다.

init block :: name is kim and id is 100

constructor block :: name is John and id is 100 

 이것으로 init의 호출 순서를 유추해 볼 수 있습니다.

먼저 객체를 생성하면 호출되면 부생성자를 통해 주생성자가 호출됩니다. [name=John, id=100] 이때 initializer block이 존재하기 때문에 해당 block에서 name을 초기화 합니다. [name=kim, id=100]

이 과정이 끝난 후에야 부생성자의 출력문이 동작합니다.


 익숙한(?) Java 코드로 보면 이해가 쉽습니다.

    public static final class Student {

      private String name;

      private int id;


      public Student(String name) {

         this(name, 100);

         System.out.println(constructor block :: name is " + name + " and id is " + this.id);

      }

      public Student(String name, int id) {

         this.name = name;

         this.id = id;

         this.name = "kim"; // init block

         System.out.println("init block :: name is " + this.name + " and id is " + this.id);

      }

   }


 Java 코드를 참고하니 조금 더 생각하기가 쉬워졌습니다. 그러면 다음과 같이 객체를 생성하면 어떻게 동작할까요?

Student(name = "John", id = 500)

 Java 코드로 유추해보면 바로 다음 코드를 실행할 것입니다.

      public Student(String name, int id) {

         this.name = name;

         this.id = id;

         System.out.println("init block :: name is " + this.name + " and id is " + this.id);

      } 


 이걸 그대로 코틀린으로 생각해보면 다음과 같겠죠.

class Student(var name: String, var id: Int = 0) {

        constructor(name: String) : this(name, 100) {

            println("constructor block :: name is $name and id is $id")

        }

        init {

            println("init block :: name is $name and id is $id")

        }

    }

 곧바로 주생성자를 거쳐 init block을 실행할 것입니다. 따라서 결과는 아래처럼 나옵니다.

init block :: name is John and id is 500 


 이제 여러가지 방법으로 테스트를 진행하면서 익숙해질 수 있습니다. 실제로 위에서 설명한 내용을 유추하고 이해하기 위해서 테스트한 코드는 다음과 같습니다.

class ExampleUnitTest {
@Test
fun kotlin() {
Student(name = "John", id = 500)
Student2(name = "John")
Student3(name = "John")
}

class Student(var name: String, var id: Int = 0) {
constructor(name: String) : this(name, 100) {
println("constructor block :: name is $name and id is $id")
}
init {
name = "kim"
println("init block :: name is $name and id is $id")
}
}

class Student2(var name: String, var id: Int = 0) {
constructor(name: String) : this(name, 100) {
println("constructor block2 :: name is $name and id is $id")
}
init {
name = "kim"
println("init block2-1 :: name is $name and id is $id")
}
init {
id = 200
println("init block2-2 :: name is $name and id is $id")
}
}

class Student3(var name: String, var id: Int = 0) {
constructor(name: String) : this(name, 100) {
println("constructor block3 :: name is $name and id is $id")
}
init {
id = 600
println("init block3-1 :: name is $name and id is $id")
}
init {
name = "kim"
id = 200
println("init block3-2 :: name is $name and id is $id")
}
}

} 

init block :: name is kim and id is 500

init block2-1 :: name is kim and id is 100

init block2-2 :: name is kim and id is 200

constructor block2 :: name is John and id is 200

init block3-1 :: name is John and id is 600

init block3-2 :: name is kim and id is 200

constructor block3 :: name is John and id is 200 


코틀린의 클래스 선언 기본형태

 class 클래스명 constructor(변수) { }

또는 

 class 클래스명(변수) { }


Java와 비교해보면

 class 클래스명 {

   클래스명(변수) {

   }

}


다중 생성자

Java

 class Constants {

   private String name;

   private int age;

   Constants(String name) {

    this.name = name;

   }

   Constants(String name, int age) {

    this.name = name;

    this.age = age;

   }

}

 (Java에서 위와 같이 생성자를 선언하면 기본 생성자가 자동으로 생성되지 않기 때문에 명시적으로 선언하는 것이 좋습니다.)


코틀린

// primary constructor는 constructor 생략 가능

class Constants constructor(val name: String, val age: Int) {

    // secondary constructor는 constructor 생략 불가능

    constructor(name: String) : this(name, 0)

}


코틀린의 default 생성자

class ExampleUnitTest {

    @Test

    fun kotlin() {

        Person(age = 25)

    }


    class Person(var name: String = "Jane", var age: Int)


 위 코드를 보면 생성자에서 name을 Jane으로 지정했습니다. 이때 Person 클래스 생성시 age의 값만 명시하여도 name이 Jane으로 초기화 됩니다. 물론 age는 25로 초기화 됩니다.

 Java에서의 기본 생성자 형태인 Person() { }와 다른 개념입니다. Java에서 해당 Person 클래스를 사용하려면 name과 age 모두 선언해주셔야 합니다.

+ Recent posts