포스트

Kotlin의 클래스 다루기


클래스와 프로퍼티


Java class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JavaPerson {

  private final String name;
  private int age;

  public JavaPerson(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

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

name은 final이기 때문에 setter 생성 불가 (불변의 특성)

  • 프로퍼티 : field + getter + setter


Kotlin class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person constructor(name: String, age: Int){
    val name = name
    var age = age
}

// 아래와 같이 constructor 생략 가능
class Person (name: String, age: Int){
  val name = name
  var age = age
}

// 아래와 같이 필드를 생성자에 직접 넣어서 사용할 수 있다.
// {} -> body 또한 존재하지 않는 다면 생략 가능 
class Person (
  val name: String,
  var age: Int
)

Kotlin은 class를 생성할 때 생성자(constructor)를 같이 작성

  • 프로퍼티 : field만 생성하면 getter + setter를 자동으로 생성
  • 생성자의 이름(constructor)는 생략할 수 있다.
  • field를 생략하고 생성자에 넣기 가능
  • {} body 또한 존재 하지 않는 다면 생략 가능


Kotlin의 setter 사용법

1
2
3
4
5
6
7
8
9
10
11
val person = Person("김용준", 100)
    println(person.name)
    // 바로 값을 넣을 수 있다. (setter)
    person.age = 10
    println(person.age)

    // Java class 를 가져 와서 쓴다고 해도 .field 를 사용할 수 있다.
    val javaPerson = JavaPerson("김용준", 100)
    println(javaPerson.name)
    javaPerson.age = 10
    println(javaPerson.age)

.field를 사용해서 값을 바로 수정 가능
.field를 사용해서 값을 바로 가져오기 가능
Java Class를 가져와서 쓰는 경우에도 .field를 사용 가능




생성장와 init


  • 클래스가 생성되는 시점(초기화)에 한번 호출되는 블럭
1
2
3
4
class Person (
    val name: String,
    var age: Int
)

class를 처음에 생성할 때 가지고 있는 생성자를 주 생성자(primary constructor)라고 한다.

  • 특징 : 반드시 존재해야 한다. 단, 주 생성자의 파라미터가 하나도 없다면 생략 가능
    • 부 생성자는 최종적으로 주 생성자this로 호출해야 한다.
    • {} body를 가질 수 있음


부 생성자는 사실상 쓸 일이 없다. 있다는 것을 알아만 두자!!


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

constructor로 만든 생성자를 부 생상자(secondary constructor)라고 한다.

  • 특징 : 있을 수도, 없을 수도 있다.


나이는 0살 이상이어야 하는 예제

1
2
3
4
5
6
7
8
9
10
11
class Person (
    val name: String,
    var age: Int
){
    // class(생성되는 시점) 가 초기화 되는 시점에 한번 호출되는 블럭
    init {
        if( age <= 0){
            throw IllegalArgumentException("나이는 ${age}일 수 없습니다");
        }
    }
}

{} body를 만들고 안에 init블럭을 만들어서 초기화 시점에 호출


최초로 태어나는 아기는 무조선 1살인 생성자 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person (
    val name: String,
    var age: Int
){
    // class(생성되는 시점) 가 초기화 되는 시점에 한번 호출되는 블럭
    init {
        if( age <= 0){
            throw IllegalArgumentException("나이는 ${age}일 수 없습니다");
        }
    }

    //새로운 생성자
    constructor(name: String): this(name, 1)
}

constructor를 사용해서 새로운 생성자를 만들 수 있음


부 생성자 호출 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person (
    val name: String,
    var age: Int
){
    // class(생성되는 시점) 가 초기화 되는 시점에 한번 호출되는 블럭
    init {
        if( age <= 0){
            throw IllegalArgumentException("나이는 ${age}일 수 없습니다");
        }
        println("초기화 블럭")
    }

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

    constructor(): this("홍길동") {
        println("두 번째 부 생성자")
    }
}

위에서 첫 번째 부 생성자주 생성자를 호출하고 두 번째 부 생성자첫 번째 부 생성자를 호출

  • 1 . 첫 번째 부 생성자초기화 블럭첫 번째 부 생성자을 출력
  • 2 . 두 번째 부 생성자초기화 블럭첫 번째 부 생성자두 번째 부 생성자를 출력


Kotlin은 사실 부 생성자 보다는 default parameter를 권장


1
2
3
4
class Person (
    val name: String = "김용준",
    var age: Int = 1
)

위와 같이 default parameter를 사용 하도록 권장


어쩔 수 없이 써야 하는 경우?


Converting과 같은 경우 부 생성자를 사용할 수 있지만, 정적 팩토리 메서드를 추천




커스텀 getter, setter


성인인지 확인하는 기능 추가


Java의 경우

1
2
3
public boolean isAdult() {
    return this.age >= 20;
  }


Kotlin의 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // 전형적인 방식
fun isAdult(): Boolean {
  return this.age >= 20
}

// custom getter
val isAdultV2: Boolean
  get() = this.age >= 20

// (custom getter)프로퍼티
val isAdultV3: Boolean
  get() {
    return age >= 20
  }

함수처럼 만드는 방식과 custom getter로 만드는 방식 두가지 존재


효과적인 방법은?

객체의 속성 » custom getter
그렇지 않은 경우 » 함수




backing field (보이지 않는 필드)


1
2
3
4
5
6
7
8
9
10
11
class Person (
    name: String = "김용준",
    var age: Int = 1
){

    // 원래 사용하던 val name: String = "김용준" 은 custom getter를 만들기 위해 아래로 내려서 get()으로 써준다
    // field. 을 사용하는 이유 : 밖에서 호출할 때는 get()을 부르고 name이라는 필드가 먼저 호출
    // 안에서도 name을 호출하면 name의 대한 getter가 호출된다.
    // 안에 있는 getter를 부르고 부르는 무한 루프가 발생하게 된다.
    val name = name
        get() = field.uppercase()
  • 원래 사용하던 val name: String = "김용준"custom getter를 만들기 위해 아래로 내려서 get() 사용
  • field. 을 사용하는 이유
    • 밖에서 호출할 때는 get()을 호출하면 name이라는 필드가 먼저 호출
    • 안에서도 name을 호출하면 name의 대한 getter가 호출된다.
    • 안에서는 안에 있는 getter를 부르고 부르는 무한 루프가 발생하게 된다.


field 예약어

  • 무한 루프를 막기 위한 예약어로 자기 자신을 나타낸다
  • 하지만 custom field에서 backing field를 사용하는 경우는 드뭄


this로 대체 가능

backing field를 사용하지 않는 예제

1
2
val upperCaseName: String
  get() = this.name.uppercase()


이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.