by 키워드로 일을 다른 객체에 넘기는 문법.
클래스 위임 — 상속 대신 합성
interface Repository { fun load(): List<Item> }
class CachedRepository(
private val delegate: Repository
) : Repository by delegate { // 나머지 메서드는 delegate 에게 자동 위임
override fun load(): List<Item> { // 필요한 것만 재정의
return cache ?: delegate.load().also { cache = it }
}
}
"상속보다 합성"을 문법이 지원한다 — 자바에서 인터페이스의 모든 메서드를 손으로 위임하던 일이 한 줄이 된다.
프로퍼티 위임
// 게터/세터 호출을 다른 객체에게 넘긴다
val adapter by lazy { UserAdapter() }
var name: String by Delegates.observable("") { _, old, new ->
Log.d("TAG", "$old → $new")
}
var count: Int by Delegates.vetoable(0) { _, _, new -> new >= 0 } // 조건에 안 맞으면 거부
직접 만들기
class PrefDelegate(private val key: String, private val default: String) {
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String =
prefs.getString(key, default)!!
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
prefs.edit().putString(key, value).apply()
}
}
var token: String by PrefDelegate("token", "") // 읽고 쓰면 SharedPreferences 로 간다
getValue·setValue 연산자만 있으면 무엇이든 위임 대상이 된다.
맵으로 위임
class Config(map: Map<String, Any?>) {
val host: String by map
val port: Int by map
}
Config(mapOf("host" to "a.com", "port" to 80))
안드로이드에서 널리 쓴다
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by viewModels() // 액티비티/프래그먼트 스코프
private val shared: SharedViewModel by activityViewModels()
private val args: HomeFragmentArgs by navArgs() // Navigation 인자
}
Jetpack 라이브러리 API의 상당수가 프로퍼티 위임 형태로 제공된다 — 생명주기에 맞춰 인스턴스를 얻는 복잡한 로직을 한 줄로 감춘다.
비용
위임마다 객체가 하나씩 생기고 접근이 메서드 호출을 거친다
- 대부분 무시할 수준이지만, 초당 수만 번 읽는 핫 필드에는 부담이 될 수 있다
- lazy 는 첫 접근 이후 필드 읽기와 거의 같아진다
면접 함정
- ❌ "by lazy는 특별한 문법" → 프로퍼티 위임의 한 사례일 뿐이다. 직접 만들 수도 있다.
- ❌ "클래스 위임은 상속과 같다" → 위임은 내부 객체를 바꿔 끼울 수 있고, 위임 대상의 메서드가 자기 자신을 부를 때 재정의가 반영되지 않는다.