Kotlin 클래스를 다루는 방법

코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 클래스를 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) 강의에 소금을 조금 친 내용이다

클래스 선언과 프로퍼티

  • Kotlin은 클래스 선언 시 주 생성자를 통해 프로퍼티를 바로 정의할 수 있으며, 자동으로 Getter / Setter를 생성해준다
Java 코드
public class Person {
    private final String name; // 불변 (setter 없음)
    private int age; // 가변

    public Person(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;
    }
}
Kotlin 코드
// 가장 간결한 형태
class Person(
    val sname: String, // 불변 (Getter만 자동 생성)
    var age: Int // 가변 (Getter, Setter 자동 생성)
) {
    // 추가 로직이 필요한 경우 init 블록 사용
    init {
        if (age <= 0) {
            throw IllegalArgumentException("나이는 ${age}일 수 없습니다")
        }
    }
}
  • val: 읽기 전용 프로퍼티 (Java의 final 필드와 Getter에 해당)
  • var: 읽기 / 쓰기 가능 프로퍼티 (Java의 일반 필드와 Getter / Setter에 해당)
  • init 블록: 주 생성자가 호출될 때 실행되는 초기화 로직을 정의. 객체 생성 시 유효성 검사 등에 활용된다

생성자: 주 생성자와 부 생성자

Kotlin은 주 생성자 외에 constructor 키워드를 사용하여 부 생성자를 정의할 수 있다. 부 생성자는 반드시 주 생성자나 다른 부 생성자를 통해 궁극적으로 주 생성자를 호출해야 한다

class Person(
    val name: String,
    var age: Int
) {
    init { println("초기화 블록") }

    // 부 생성자 1: 주 생성자 호출 (name만 받는 경우)
    constructor(name: String): this(name, 1) {
        println("첫 번째 부 생성자")
    }

    // 부 생성자 2: 다른 부 생성자 호출 (파라미터 없는 경우)
    constructor(): this("임꺽정") {
        println("두 번째 부 생성자")   
    }
}

fun main() {
    val person = Person("혁", 5) // 초기화 블록
    val person2 = Person("홍길동") // 초기화 블록 -> 첫 번째 부 생성자
    val person3 = Person() // 초기화 블록 -> 첫 번째 부 생성자 -> 두 번째 부 생성자
}
  • 권장 사항: 부 생성자보다는 기본 인자 사용
  • 대 부분의 경우 부 생성자 대신 주 생성자의 기본 인자와 이름 있는 인자 조합을 사용하는 것이 Kotlin스럽고 유용하다. 이는 “텔레스코핑 생성자(Telescoping Constructor)” 문제를 해결하고, 가독성을 높이며, 단일 초기화 경로를 제공한다
// 기본 인자를 사용한 Person 클래스
class personWithDefaults(
    val name: String = "혁" // 기본값 "혁"
    var age: Int = 1 // 기본값 1
) {
    init {
        if (age <= 0) throw IllegalArgumentException("나이는 ${age}일 수 없습니다")
    }
}

fun main() {
    val person1 = PersonWithDefaults("김철수", 30) // 모든 인자 제공
    val person2 = PersonWithDefaults("맹구") // age는 기본값 1 사용
    val person3 = PersonWithDefaults(age = 25) // name은 기본값 "혁"사용, age는 25
    val person4 = PersonWithDefaults() // 모두 기본값 사용
}

결과적으로, Kotlin에서 부 생성자는 서로 다른 super() 호출이 필요한 드문 경우에 사용되며, 대부분의 상황에서는 기본 인자 또는 정적 팩토리 메서드가 선호된다

커스텀 Getter, Setter 및 Backing Field

  • Kotlin 프로퍼티는 Getter / Setter를 자동으로 제공하지만, 필요에 따라 커스텀 로직을 추가할 수 있다
class Person(
    initialName: String = "혁",
    var age: Int = 1
) {
    // 커스텀 Getter: name 프로퍼티의 값을 항상 대문자로 변환
    var name: String = initialName
        get() = filed.uppercase() // 'field'는 backing field를 참조

    // val 프로퍼티에 커스텀 Getter 추가 가능 (read-only 계산 프로퍼티)
    val isAdult: Boolean
        get() = this.age >= 20

    // 커스텀 Setter: name 설정 시 항상 대문자로 저장
    var mutableName: String = initialName
        set(value) {
            field = value.uppercase() // 'field'에 실제 값을 저장
        }

    // 커스텀 Getter를 사용한 read-only 프로퍼티 예시
    val upperCaseName: String
        get() = this.name.uppercase() // 'this.name'은 위에서 정의된 name의 Getter를 호출
}

fun main() {
    val person = Person("hyeok", 100)
    println(person.name) // HYEOK (커스텀 Getter 적용)
    println(person.isAdult) // true (커스텀 Getter 적용)
    person.mutableName = "kotlin"
    println(person.mutableName) // // KOTLIN (커스텀 Getter) 적용
}
  • field: field 예약어는 프로퍼티의 실제 값을 저장하는 “backing field”를 참조한다. 커스텀 Getter / Setter 내에서 무한 루프를 방지하고 실제 값을 조작할 때 사용된다
  • 커스텀 Getter: 프로퍼티를 읽을 때 특정 로직을 수행하도록 정의한다. val 프로퍼티에도 사용할 수 있으며, 마치 새로운 속성처럼 보이는 “계산된 프로퍼티”를 만들 때 유용하다
  • 커스텀 Setter: 프로퍼티에 값을 할당할 때 특정 로직을 수행하도록 정의한다
Custom Getter vs 함수 선택 기준
  • 객체의 속성(상태 / 특성)을 나타내면 Custom Getter: person.isAdult, person.fullName
  • 객체의 행동(작업 / 동작)을 나타내면 함수: person.calculateTax(), person.save()
Backing Field vs Custom Getter 선택 기준
  • 대부분의 경우, 기존 프로퍼티의 값을 가공하여 새로운 “계산된 프로퍼티”를 제공하는 Custom Getter가 더 직관적이고 간단하다. backing field는 프로퍼티의 실제 저장 값을 변형하거나 유효성을 검사할 때 사용된다. 일반적으로 Custom Getter가 더 자주 사용되며 API 설계 측면에서도 자연스럽다

출처 – 인프런 강의 중 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)