코틀린을 사용하면서, 자바랑 확연히 달라 적응하기 어려웠던 부분들을 기록해둔다.
read-only vs mutable 관리
read-only vs mutable: 객체 내부 프로퍼티 접근제어
코틀린에서 val/var 차이와, 프로퍼티의 getter/setter를 정의하는 방식(특히 private set)을 정리한다.
1. val vs var
val: 재할당 불가(읽기 전용 참조). 프로퍼티 관점에서는 setter가 없음.var: 재할당 가능. 프로퍼티 관점에서는 getter + setter가 있음.
val a = 10
// a = 20 // 컴파일 에러
var b = 10
b = 20 // 가능
2. 기본 getter/setter
코틀린 프로퍼티는 기본적으로 접근자(accessor)가 자동 생성된다.
class User {
var name: String = "kim" // 기본 getter/setter 생성
val id: Long = 1L // 기본 getter만 생성
}
3. 커스텀 getter
get() 블록에서 반환 값을 정의한다.
class Rectangle(val width: Int, val height: Int) {
val area: Int
get() = width * height
}
- 이런 프로퍼티는 보통 계산 프로퍼티로 backing field 없이 동작한다.
4. 커스텀 setter + field(backing field)
var에서만 setter 커스터마이징이 가능하며, 내부 필드에 접근하려면 field를 쓴다.
class Account {
var balance: Long = 0
set(value) {
field = if (value < 0) 0 else value
}
}
field는 “이 프로퍼티의 실제 저장 공간”을 의미한다.- setter에서
balance = ...로 다시 대입하면 재귀 호출이 되므로field를 사용한다.
5. private set (외부 수정 차단)
외부에는 읽기만 허용하고, 클래스 내부에서만 변경 가능하게 만든다.
class Order {
var status: String = "CREATED"
private set
fun ship() {
status = "SHIPPED" // 클래스 내부에서는 변경 가능
}
}
이 패턴은 “상태는 노출하되 변경은 메서드로만” 같은 캡슐화에 자주 쓴다.
private set + 커스텀 setter도 가능
class Person {
var age: Int = 0
private set(value) {
field = value.coerceAtLeast(0)
}
fun grow() { age += 1 }
}
6. getter만 private로 할 수도 있음
읽기를 막고 내부에서만 읽게 하는 패턴이다.
class Secret {
val token: String = "t"
private get
}
- 클래스 밖에서는
token접근 자체가 불가능하다.
7. 프로퍼티의 가시성(visibility)을 프로퍼티 자체에 주는 방식
프로퍼티 선언에 붙이면, getter/setter 모두에 영향을 준다.
class A {
private var x: Int = 0 // getter/setter 모두 private
}
반면 아래는 “읽기는 public, 쓰기만 private”이다.
class A {
var x: Int = 0
private set
}
8. lateinit var (지연 초기화)
var+ non-null 타입에만 가능- DI/테스트에서 종종 사용
lateinit var service: MyService
read-only vs mutable: 컬렉션 인터페이스
코틀린 표준 라이브러리는 컬렉션을 두 계열로 나눈다.
- 읽기 전용(read-only): List, Set, Map<K,V>
- 수정 메서드가 인터페이스에 없음
- 가변(mutable): MutableList, MutableSet, MutableMap<K,V>
- add/remove/put 등 제공
활용: 외부에는 read-only로 노출, 내부에서만 수정
class Cart {
private val _items = mutableListOf<String>()
val items: List<String> get() = _items // 외부 read-only 뷰
fun add(item: String) { _items.add(item) }
}
코틀린의 스코프 함수
특정 객체를 일시적인 “수신 객체(receiver)” 또는 인자로 두고, 그 블록 안에서 간결하게 다루게 해주는 유틸리티 함수이다.
대표적으로
- let: it 사용, 결과 반환
- run: this 사용, 결과 반환
- apply: this 사용, 자기 자신 반환(초기화에 자주 사용)
- also: it 사용, 자기 자신 반환(부수효과/로깅에 자주 사용)
- with: (obj) { ... } 형태, this 사용, 결과 반환
가 있다.
예시 코드
val len = "abc".let { it.length } // 3
val len2 = "abc".run { length } // 3
val sb = StringBuilder().apply {
append("a")
append("b")
} // sb 자신이 반환됨
프로퍼티 문법(getter/setter)과 backing field
필드 직접 접근처럼 보이지만 실제로는 접근자를 사용하는 형태이다..
field 키워드가 backing field를 참조한다.
var x: Int = 0
set(value) { field = value.coerceAtLeast(0) }
코틀린의 {}
코틀린에서 {}로 만드는 “코드 블록”은 보통 람다 식(lambda expression) 또는 익명 함수/함수 리터럴(function literal) 이다.
코틀린은 { ... } 블록 자체가 값(객체) 이 될 수 있고, 그래서 함수 인자로 넘기거나 변수에 담을 수 있다.
1. 람다 식 (lambda expression)
가장 흔한 형태입니다.
val f: (Int) -> Int = { x -> x + 1 }
println(f(10)) // 11
{ x -> x + 1 }가 람다->왼쪽은 파라미터, 오른쪽은 본문- 마지막 식의 값이 반환값(return 없이)
파라미터가 1개면 it으로 생략 가능하다.
val f: (Int) -> Int = { it + 1 }
2. “트레일링 람다” (trailing lambda)
함수의 마지막 인자가 함수 타입이면, 괄호 밖으로 {}를 뺄 수 있습니다.
listOf(1, 2, 3).map { it * 2 }
원래는
listOf(1, 2, 3).map({ it * 2 })
이 문법 때문에 “중괄호로 코드블럭 만든다”는 느낌이 강하게 나게 된다.
3. 스코프 함수(let/run/apply/also/with)에서의 {}도 람다
val result = "abc".run {
length // 마지막 식이 반환
}
여기서 { ... }는 run에 전달되는 람다이다.
4. 함수 본문 블록 {} (일반 블록)
함수/조건문/반복문에서의 {}는 “값”이라기보다 문(statement) 블록이다.
fun foo() {
val x = 1
println(x)
}
if (a > 0) {
println("positive")
}
다만 코틀린에선 if/when이 식(expression) 이라서, 블록 안의 마지막 식이 값이 되는 패턴이 섞여 보일 수 있다.
val v = if (a > 0) {
1
} else {
0
}
'WEB BE Repository > JAVA & Kotlin' 카테고리의 다른 글
| [코틀린 코루틴] 구조적 동시성 (0) | 2026.01.07 |
|---|---|
| [코틀린 코루틴] 코틀린의 동시성 모델 - 코루틴 (0) | 2026.01.07 |
| Java - classpath (0) | 2025.12.11 |
| 가독성이 좋은 코드의 기준? (0) | 2025.06.06 |
| 자바 패키지 네이밍 컨벤션 (0) | 2025.04.28 |