Kotlin - Property초기화(lateinit, by lazy, Kproperty)

이동수·2024년 9월 9일

Kotlin

목록 보기
19/33
post-thumbnail

자바는 프로퍼티가 없고 코틀린에만 있음

Property

  • oop에서 클래스는 캡슐화된 데이터를, 메소드를 통해 제어 및 접근하는 것을 원칙으로 한다. → side effect(부수효과), side effect가 없는거는 고차함수임
  • field에 접근하는 get/set(value)를 모두 포함해 property라고 함
  • property선언시 var,val가 반드시 존재
    • var : get(읽기), set(쓰기)
    • val : get
    • var, val로 선언된 property는 컴파일시 private 접근지정자로 지정

backing field

  • getter/setter을 재정의해 속성값을 수정, 비교 등을 할 때 사용
var name = ""
get() = field
set(value){
	field = value
	}
	----
var p = Person()
p.name // --> name속성 get호출
p.name = "표인수" // --> name속성 set(value)호출 
  • 자바는 프로퍼티가 없고 코틀린에만 있음
  • 코틀린 클래스는 프로퍼티를 가질 수 있음
    • var(mutable), val(read-only)

      class A{
      var
      val
      }
  • 프로퍼티 사용은 자바의 필드를 사용하듯이 하면 됨
  • 프로퍼티 문법
    var 이름:타입 = 초기자
    getter
    setter
    • 생략가능
      • 타입
      • 초기자
      • getter
      • setter
  • 그러면 프로퍼티와 변수와 다른점은 무엇인가? var와 val로 선언하는 변수들이 프로퍼티이다. 코틀린에서는 변수를 선언하면 내장된 getter와 setter함수가 자동으로 생성된다. 그래서 ‘변수’말고 ‘프로퍼티’라고 부르는거다.
    var name: String = "haruple"
    val age: Int = 26
    
    var name: String = "haruple"
        get() = field
        set(value) {field = value}
       
    val age: Int = 26
        get() = field
    		  
    위 두 코드는 동일한 코드이다. var - 변경할수 있으므로 get,set 모두 추가 val - 변경 못하므로 get만 추가
  • 프로퍼티 변경 예제
    • 밑에 두 코드는 거의 같다

      class A {
      	var name = "Koltin"
      }
      class A {
      	var name = "Kotlin"
      	get() {
      		return field + "!!!!!!!!!!!"
      	}
      	set(value) {
      		field = value
      	}
      }
      //출력 Koltin!!!!!!!!!!
  • var,val
class A {
	var a = 1 //getter,setter
	val b = 1 //getter
	var c:Int? //오류 - 명시적인 초기화 필요
	val d:Int? //오류 - 명시적인 초기화 필요
}

Custom accessors (getter, setter) - getter,setter 재정의

  • 프로퍼티 선언 내부에, 일반 함수 처럼 선언 할 수 있음
  • getter
    val isEmpty: Boolean
            get() = this.size == 0 
  • setter
    • 관습적으로 setter의 파라미터 이름은 value임(변경가능)

      val isEmpty: String
              get() = this.toString()
              set(value) {
                  setDataFrimString(value)
              }
  • 타입생략
    • getter를 통해 타입을 추론할수 있으면 프로퍼티의 타입을 생략가능함
      val isEmpty
      	 get() = this.size == 0 

property 초기화

init을 이용한 초기화

  • java식 방법이긴함
class Person constructor(){ - - - -> Default 생성자는 생략가능
	val name: String - - - - > 속성 선언 시 초기화를 진행하지 않을 시 Type(String)을 반드시 선언
	var age: Int
	val gender: Char
	init {
		name = "Pyo Insoo"
		age = 32
		gender = 'M'
	}
}

null허용 속성타입으로 선언한 후 나중에 초기화

  • null허용(String?)으로 type선언 후 나중에 초기화
  • 반드시 var Null 허용 타입으로 선언해야함
  • (null타입으로 쓰지 않도록 노력하자, null하고 나중에 초기화하는거 안좋음)

lateinit 이용한 초기화

(late-initialized)

  • var property에만 사용가능 (안드로이드에서 많이 사용함)
  • class body에 선언된 property만 사용가능 (주생성자는 안됨)
  • getter/setter 재정의 못함
  • null허용 property에서는 사용 불가
  • primitive(byte, short, int, float 등) type에는 사용불가 (기본타입에서 사용불가)
  • 객체 타입에서만 사용 가능
  • 초기값 할당 전까지 변수를 사용할 수 없음
  • 기본 자료형에는 사용할 수 없음(String 클래스는 가능)
  • lateinit 변수가 초기화 되었나 확인 할때는 ::a.isInitalized해서 초기화 됐나 확인하고 사용
    • 잘 알아두기. lateinit초기화 됐나 확인할때 많이 쓰이니까
class Person {
	lateinit var name: String
	//lateinit var age:Int - - - -> 기본타입에는 사용불가
	// lateinit var gender: Char - - - -> 기본타입에는 사용불가
	constructor(name: String, age:Int, gender:Char){
		this.name = name
		//this.age = age
		//this.gender = gender
	}
}
fun main(){
	val person = Person("Pyo Insoo", 32, 'M')
}
  • 생성자에서 초기화 할 수 없을때
  • 객체가 constructor에서 할당되지 못하지만 여전히 non-null타입으로 사용하고 싶을 때
  1. 조건
  • 클래스의 바디에서 선언된 프로퍼티만 가능 (top-level에서 사용못함, 클래스 내부에서만 가)
  • 기본 생성자에서 선언된 프로퍼티는 x
    • class A (lateinit var ~){} XXXX
  • var 프로퍼티만 가능
    • lateinit val xx
  • custom accessor이 없어야함
    lateinit var a: String
            get() = {return field} xxxxxxxxxxxxxxxx
  • non-null 타입이어야함
    • late var a: String? xxxxxxxxxxxxx
  • 프리미티브 타입이면 x

by lazy를 이용한 실행시점(run time) 늦은 초기화

  • 람다 함수
  • by lazy{…}을 이용해 property사용 시점에 {…}이 실행됨
  • val property만 사용 가능
  • primitive type에도 사용가능
  • class body뿐만 아니라 top-level에도 사용 가능
  • 안드로이드 개발할때 ui오브젝트들이 클래스가 생성될때 초기화 안되는 경우가 많음, 객체가 다 생성되고 런타임에 써야하는 경우가 많음
  • 변수를 사용하는 시점까지 초기화를 자동으로 늦춰줌
  • 변수를 사용할때 초기화 돼서 코드 실행시간을 최적화 할 수 있음
  • 람다함수로 초기화가 진행됨(여러개의 구문이 들어갈 수 있음)
  • 마지막의 결과가 변수에 할당됨( 처음 실행시 {}안에 다 실행되고 그 다음부터는 맨마지막 값만 할당되서 쓰임
    val a : Int by lazy {
    print("초기화됨")
    7 //lazy 값이라는 뜻
    }
    println(a) //초기화됨 7
    println(a) //7
class Person {
	val name: String by lazy { //->람다 함수임
		println(“lazy init) // ->lazy init은 맨처음 한번만 출력되고 그 후로는 출력되지 않음
		"Pyo Insoo“ //- - - -> by lazy는 표현식이므로 할당 할 값이 맨 마지막에 와야 한다
	}
	val age:Int by lazy {
		32
	}
	val gender: Char by lazy{
		'M'
	}
}
var temp = 1
val increment: Int by lazy{
	++temp
}
fun main(){
	val person = Person()
	println("name:${person.name}") //lazy init, 표인수
	println("name:${person.name}") //표인수
	println("age:${person.age}")
	println("gender:${person.gender}")
	println("increment: $increment") //처음에만 증가 - 2출력
	println("increment: $increment") //증가하지 않는다 - 2출력
}
  • lazy init은 맨 처음 호출에만 출력되고 그 후부터는 출력되지않음.
  • by lazy{..}안의 할당할 값은 맨 마지막에 와야함

예시

fun main(){
    val foodCourt = FoodCourt()
    foodCourt.searchPrice(FoodCourt.FOOD_CREAM_PASTA)
    foodCourt.searchPrice(FoodCourt.FOOD_PIZZA)
    foodCourt.searchPrice(FoodCourt.FOOD_STEAK)
    //크림파스타 가격은 13000 입니다
    //피자 가격은 15000 입니다
    //스테이크 가격은 25000 입니다

    lateinit var b : String
    //lateinit var a = "아" 오류뜸 lateinit은 초기화를 하면 안되네

    var lateInitSample = LateInitSample()
    println(lateInitSample.getLateInitTest())   // 기본값
    lateInitSample.text = "쿵따딱"
    println(lateInitSample.getLateInitTest())   // 쿵따닥

    val a : Int by lazy {
        println("초기화됨")
        7           //lazy 값이라는 뜻
    }
    println(a)          //초기화됨 7
    println(a)          //7

}
class FoodCourt{
    fun searchPrice(foodName: String){
        val price = when(foodName){
            FOOD_CREAM_PASTA -> 13000
            FOOD_STEAK -> 25000
            FOOD_PIZZA -> 15000
            else -> 0
        }
        println("$foodName 가격은 $price 입니다")
    }

    companion object{
        const val FOOD_CREAM_PASTA = "크림파스타"
        const val FOOD_STEAK = "스테이크"
        const val FOOD_PIZZA = "피자"
    }
}

class LateInitSample{
    lateinit var text: String

    fun getLateInitTest(): String {
        if(::text.isInitialized){
            return text
        } else {
            return "기본값"
        }
    }
}

KPorperty를 이용한 property delegate

delegate : 위임, (디자인 패턴중 하나)

  • 속성의 책임을 다른 객체에 위임할 수 있는 강력한 기능.
  • Kotlin Refection 기능을 활용한 위임이며 속성에 값을 할당하는 개념이 아니고 할당할 수도 없다
  • val/var : by <DelegateClass,Function(둘중 하나)>
  • Expression Class Implementation(실행)
    • var : operator function getValue/setValue를 구현
    • val : operator function getValue 만 구현
    • Property getter/setter를 호출하면 getValue/setValue가 자동 호출

ex)

import kotlin.reflect.KProperty

class PropertyDelegate(var value: String) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("${property.name} get value ${this.value}")

        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("${property.name} set value ${this.value} -> $newValue")

        this.value = newValue
    }
}

class Person {
    var name:String by PropertyDelegate("이동수") // getValue, setValue
    val age: String by PropertyDelegate("27") // getValue
}

fun main() {
    val person = Person() 
    println("person name is ${person.name}")
    //name get value 이동수
		//person name is 이동수
    
 

    person.name = "소진호" 
    //name set value 이동수 -> 소진호
    
    println("person name is ${person.name}")
    //name get value 소진호
		//person name is 소진호

    
    println("person age is ${person.age}") 
    //age get value 27
		//person age is 27
}
  • by키워드를 이용해 위임할 객체를 프로퍼티 뒤에 명시한다.
    • var name:String by PropertyDelegate("no name") —> name을 propertyDelegate에게 책임을 맡기겠다.
  • thisRef - 위임을 사용하는 클래스와 같은 타입이거나 Any 타입이어야 한다.
  • property - KProperty<*>거나 Any 타입이어야 한다.
  • newValue - 위임을 사용하는 프로퍼티와 같은 타입이거나 Any 타입이어야 한다.

0개의 댓글