[코틀린] study day 7

승아·2020년 10월 23일
0

코틀린의 프로퍼티(Properties)

  • 변수 선언과 기본적인 접근 메서드를 모두 가지고 있음
  • 따로 접근 메서드를 만들지 않아도 내부적으로 생성하게 됨

게터(Getter)와 세터(Setter)의 구성

  • 게터와 세터를 합쳐 접근 메서드라고 함
fun main(){
    val user = User(1, "Sean", 30)
    val name = user.name // 게터에 의한 값 획득
    user.age = 41 // 세터에 의한 값 지정, 당연히 var(가변형) 일때만 가능
}
  • 기본 게터와 세터 지정
class User(_id: Int, _name: String, _age: Int){
    // 프로퍼티
    val id: Int = _id
    	get() = field 
    // field : 프로퍼티를 참조하는 변수로 보조 필드로 불림
    // 프로퍼티를 대신할 임시 필드로 만일 프로퍼티를 직접 사용하면 게터나 세터가 무한 호출되는 재귀에 빠짐 
        
    var name: String = _name
    	get() = field
        set(value) { // value : 세터의 매개변수로 외부로부터 값을 가져옴
            field = value
        }
    
    var age: Int = _age
    	get() = field
        set(value) {
            field = value
        }
}
            
  • 커스텀 게터와 세터 사용
class User(_id: Int, _name: String, _age: Int){
    // 프로퍼티
    val id: Int = _id
    	get() = field 
    var name: String = _name
    	get() = field
        set(value) { 
            field = value.toUpperCase() 
        }
   
}
 
  • 오버라이딩도 가능함

지연 초기화

  • 변수나 객체의 값은 생성시 초기화 필요
    • 클래스에서는 기본적으로 선언하는 프로퍼티 자료형들은 null을 가질 수 없음
    • 하지만, 객체의 정보가 나중에 나타나는 경우 나중에 초기화 할 수 있는 방법 필요
    • 지연 초기화를 위해 lateinit과 lazy 키워드 사용

lateinit

  • 의존성이 있는 초기화나 unit 테스트를 위한 코드를 작성 시
  • var로 선언된 프로퍼티만 가능
  • 프로퍼티에 대한 게터와 세터를 사용할 수 없음
class Person {
    lateinit var name: String // 늦은 초기화를 위한 선언
    
    fun test(){
        if(!::name.isInitialized) { // 로퍼티의 초기화 여부 판단
            println("not initialized")
        }
        else {
            println("initialized")
        }
    }
}

fun main() {
    val kildong = Person()
    kildong.test() // "not initialized"
    kildong.name = "Kildong" // 이 시점에서 초기화됨(지연 초기화)
    kildong.test() // "initialized"
}
  • 객체 생성 시 lateinit을 통한 지연 초기화 가능
data class Person(var name: String, var age: Int)

latinit var person1: Person // 객체 생성의 지연 초기화

fun main() {
    person1 = Person("Kildong", 30) // 생성자 호출 시점에서 초기화됨
}

lazy

  • 호출 시점에 by lazy {...} 정의에 의해 블록 부분의 초기화를 진행한다.
  • 불면의 변수 선언인 val에서만 사용 가능하다.(읽기 전용)
  • val이므로 값을 다시 변경할 수 없다.
class LazyTest {
    init {
        println("init block") // (2) 
    }
    val subject by lazy {
        println("lazy initialized") // (6)
        "Kotlin Programming" // (7) lazy 반환값
    }
    fun flow() {
        println("not initialized") // (4)
        println("subject one: $subject") // (5)  최초 초기화 시점
        println("subject two: $subject")  // (8) 이미 초기화된 값 사용
    }
}
fun main() {
    val test = LazyTest() // (1)
    test.flow() // (3)
}
  • 3가지 모드 지정 가능
    • SYNCHRONIZED 락을 사용해 단일 스레드만이 사용하는 것을 보장(기본값)
    • PUBLICATION 여러 군데서 호출될 수 있으나 처음 초기화된 후 반환 값을 사용
    • NONE 락을 사용하지 않기 때문에 빠르지만 다중 스레드가 접근할 수 있음 (값의 일관성을 보장할 수 없음)
    private val model by lazy(mode = LazyThreadSfetyMode.NONE) {
    	Injector.app().transactionsModel() // 이 코드는 단일 스레드의 사용이 보장될 때
    }

by를 이용한 위임

  • 위임(delegation)
    • 하나의 클래스가 다른 클래스에 위임하도록 선언
    • 위임된 클래스가 가지는 멤버를 참조없이 호출
    < val|var|class > 프로퍼티 혹은 클래스 이름 : 자료형 by 위임자
  • 클래스의 위임
interface Animal {
    fun eat() { ... }
    ...
}
class Cat : Animal { }
val cat = Cat()
class Robot : Animal by cat // Animal의 정의된 Cat의 모든 멤버를 Robot에 위임함
// cat은 Animal 자료형의 private 멤버로 Robot 클래스 내에 저장
// Cat에서 구현된 모든 Animal의 메소드는 정적 메소드로 생성
// 따라서, Animal에 대한 명시적인 참조를 사용하지 않고도 eat()을 바로 호출
  • 코틀린의 기본라이브러리는 open되지 않은 최종 클래스
    • 표준 라이브러리의 무분별한 상속의 복잡한 문제들을 방지
    • 단, 상속이나 직접 클래스의 기능 확장을 하기 어렵다.
  • 위임을 사용하면?
    • 위임을 통해 상속과 비슷하게 최종 클래스의 모든 기능을 사용하면서 동시에 기능을 추가 확장 구현할 수 있다.
interface Car {
    fun go(): String
}
class VanImpl(val power: String): Car{
    override fun go() = "는 짐을 적재하며 $power 마력을 가집니다."
}
class SportImpl(val power: String): Car {
    override fun go() = "는 경주용에 사용되며 $power 마력을 가집니다."
}
class CarModel(val model: String, impl: Car): Car by impl {
    fun carInfo() {
        println("$model ${go()}") // 참조 없이 각 인터페이스 구현 클래스의 go를 접근
    }
}
fun main() {
    val myDamas = CarModel("Damas 2010", VanImpl("100"))
    val my350z = CarModel("350Z 2008", SportImpl("350"))
    
    myDamas.carInfo() // "Damas 2010는 짐을 적재하며 100마력을 가집니다."
    my350z.carInfo() // "350Z 2008는 경주용에 사용되며 350마력을 가집니다."
}
  • observable
    • 프로퍼티를 감시하고 있다가 특정 코드의 로직에서 변경이 일어날 때 호출
import kotiln.properties.Delegates

class User {
    // observable은 값의 변화를 감시하는 일종의 콜백 루틴
    var name: String by Delegates.observable("NONAME") { // (1) 프로퍼티를 위임
        prop, old, new -> // (2) 람다식 매개변수로 프로퍼티, 기존값, 새로운 값
        println("$old -> $new") // (3) 이부분은 이벤트가 발생할 때만 실행됨
    }
}
fun main() {
    val user = User()
    user.name = "Kildong" // (4) 값이 변경되는 시점에서 첫 이벤트 발생
    user.name = "Dooly" // (5) 값이 변경되는 시점에서 두번째 이벤트 발생
}
  • vetoable
    • 감시보다는 수여한다는 의미로 반환값에 따라 프로퍼티 변경을 허용하거나 취소
import kotiln.properties.Delegates

fun main() {
   var max: Int by Delegates.vetoable(0) { // (1) 초기값은 0
       prop, old, new ->
       new > old // (2) 조 건에 맞지 않으면 거부권 행사
   }
   println(max) // 0
   max = 10
   println(max) // 10
   
   max = 5
   // 여기서는 기존값이 새 값보다 크므로 false 따라서 5를 재할당하지 않는다.
   println(max) // 10
}

컴패니언 객체

  • 동적인 초기화 없이 사용할 수 있는 개념으로 자바에서 static 변수 또는 객체
class Peron { 
    var id: int = 0
    var name: String = "Youngdeok"
    companion object {
        var language: String = "Korean"
        fun work() {
            println("working...")
        }
    }
}

fun main() {
    println(Person.language) // 인스턴스를 생성하지 않고 기본값 사용
    Person.language = "English" // 기본값 변경 가능
    println(Person.language) // 변경된 내용 출력
    Person.work() // 메서드 실행
    // println(Person.name) // name은 companion object가 아니므로 에러
}

부스트코스 코틀린강좌를 참고하였습니다.

0개의 댓글