Do it! 코틀린 프로그래밍 [둘째마당, 객체 지향 프로그래밍] 학습
📌 자바의 필드
📌 자바에서 필드를 사용할 때의 문제점
📌 게터와 세터 동작 확인하기
class User(_id: Int, _name: String){
val id: Int = _id // 읽기전용 (변경 불가능, 세터 없음)
var name: String = _name
}
// 위의 코드를 아래처럼 간소화 할 수 있음
class User(val id: Int, var name: String){ }
fun main() {
val user = User(1, "Sean", 30)
val user = user.name // user.getName() 형태와 같음
user.name = "Ddockdae" // user.setName("Ddockdae") 형태와 같음
user.id = 2 // id는 val이므로 세터로 값 지정 불가능
}
-> user.name은 프로퍼티에 직접 접근하는 것처럼 보이나 코틀린 내부적으로 접근 메서드(getName())가 내장되어 있음
-> 코틀린 코드도 JVM에서 동작하기 때문에 역컴파일된 코드는 자바 코드와 거의 동일. 변환된 코드를 보면 프로퍼티에 대한 게터와 세터가 자동으로 생성되어 있음을 확인할 수 있음
📌 프로퍼티 선언 구조
var 프로퍼티 이름: 프로퍼티 자료형 = 프로퍼티 초기화
get() { /*게터 본문*/ }
set(value) { /*게터 본문*/ }
val 프로퍼티 이름: 프로퍼티 자료형 = 프로퍼티 초기화
get() { /*게터 본문*/ }
// 세터 없음
📌 기본 게터와 세터 지정하기
class User(_id: Int, _name: String){
val id: Int = _id
get() = field
var name: String = _name
get() = field
set(value) {
field = value
// this.setName(value) 형태로 변환
// 무한 재귀호출 (!스택 오버플로 오류 발생!)
name = value
}
}
📌 커스텀(Custom) 게터와 세터의 사용
⬇️ 받은 인자를 대문자로 변경해주는 커스텀 세터
class User(_name: String){
var name: String = _name
set(value) {
field = value.toUpperCase()
}
}
📌 보조 프로퍼티의 사용
class User(_name: String) {
// 보조 프로퍼티 정의
private var tempName: String? = null
var name: String = _name
get() {
if(tempName == null) tempName = "Noname"
return tempName ?: throw AssertionError("Asserted by others")
}
}
📌 프로퍼티의 오버라이딩
open class First {
// 오버라이딩 가능하게 open 키워드 사용
open val x: Int = 0
get() {
return field
}
val y: Int = 0
}
class Second: First() {
// override와 함께 게터가 재정의
override val x: Int = 0
get() {
return field + 3
}
override val y: Int = 0 // !오류! 오버라이딩 불가
}
📌 '프로퍼티' 지연 초기화 하기
class Person {
lateinit var name: String
}
fun main() {
val kildong = Person() // name 초기화 X
kildong.name = "kildong" // 이 시점에서 name 초기화
}
📌 '객체' 지연 초기화하기
data class Person(var name:String, var age: Int)
lateinit var person1: Person // 객체 생성의 지연 초기화
fun main() {
person1 = Person("Kildong", 30) // 생성자 호출 시점에 초기화
}
by lazy {...}
정의에 의해 블록 부분의 초기화를 진행📌 '프로퍼티' 지연 초기화 하기
class LazyTest {
val subject by lazy {
println("lazy initialized")
"Kotlin Programming" // lazy 반환값
}
fun flow() {
println("not initialized")
println("subject one: $subject") // 최초 초기화 시점
println("subject two: $subject") //이미 초기화된 값을 재사용
}
}
fun main() {
val test = LazyTest()
test.flow()
}
-> 프로퍼티에 최초로 접근한 시점에 해당 프로퍼티가 초기화
-> by는 프로퍼티를 위임할 때 사용하는 키워드
📌 '객체' 지연 초기화 하기
class Person(val name: String, val age: Int)
fun main() {
val person: Person by lazy { // lazy를 사용한 person 객체의 지연 초기화
Person("Kim", 23) // 이 부분이 Lazy 객체로 반환
}
// 위임 변수를 사용한 초기화
val personDelegate = lazy { Person("Hong", 40) }
// 이 시점에서 초기화
println("person.name = ${person.name}")
// 이 시점에서 초기화
println("personDelegate.value.name = ${personDelegate.value.name}")
}
-> by lazy는 객체의 위임(기능을 떠넘기는것)을 나타냄
-> lazy는 변수에 위임된 Lazy 객체 자체를 나타내므로 이 변수의 value를 한 단계 더 거쳐 객체의 멤버인 value.name과 같은 형태로 접근해야함
📌 lazy의 모드
⬇️ lazy 모드 사용 예시
private val model by lazy(mode = LazyThreadSafetyMode.NONE) {
Injector.app().transactionModel() //
}
<val|var|class> 프로퍼티 혹은 클래스 이름: 자료형 by 위임자
📌 클래스의 위임(Class Delegation)
interface Animal {
fun eat() {...}
...
}
class Cat : Animal { }
val cat = Cat()
class Robot : Animal by cat //Animal의 정의된 Cat의 모든 멤버를 Robot에 위임
-> Robot은 Cat이 가지는 모든 Animal의 메소드를 가지게 되는데 이를 클래스의 위임이라고 함
-> Cat은 Animal 자료형의 private멤버로 Robot 클래스 안에 저장
-> Cat에서 구현된 모든 Animal의 메서드는 정적 메서드로 생성
-> Robot 클래스를 사용할 때 Animal을 명시적으로 참조하지 않고도 eat()을 바로 호출 할 수 있음
📌 위임을 사용하는 이유
📌 프로퍼티 위임과 by lazy