코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 클래스를 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(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)