Uknow's Lab.
article thumbnail

 

 

코틀린에서 전역변수 사용

class Person {
    String name;
}

public class Main {
    static Person person;

    public static void main(String[] args) {
        person = new Person();
    }
}

 

자바 개발자라면 클래스 내 변수를 어느 메소드에서 자유롭게 접근하도록 하기 위해

전역변수로 선언만 해둔 후, 이를 특정 시점에 초기화 및 생성시켜 써본 경험이 한 번쯤은 있으실 겁니다.

 

 

class Person

val person:Person // 초기화하지 않으면 에러 발생!!

fun main() {
    val person = Person()
}

하지만 코틀린에서는 위와 같은 코드는 성립이 안됩니다.

코틀린에서는 변수의 선언과 동시에 초기화하도록 권장하기 때문입니다.

 

 

class Person

var person:Person? = null // null로 초기화 하여 에러는 나지 않지만, 권장하진 않음

fun main() {
    val person = Person()
}

물론, ? 키워드를 붙여 nullable 타입으로 만든 뒤, null로 초기화하여 사용할 수 있으나,

null로 초기화하는 것은 코틀린에서 그닥 권장하는 사용법은 아닙니다.

 

 

lateinit을 사용한 늦은 초기화

class Person

lateinit var person: Person // lateinit을 붙여 늦게 초기화해준다고 명시
lateinit val person2: Person // 에러발생 !! val에는 사용 불가능

fun main() {
    val person = Person()
}

 

이럴때 사용할 수 있는게 lateinit 키워드입니다.

키워드 이름에서 알 수 있듯, 늦게 초기화해준다는 의미입니다.

 

이는 val에는 사용 불가하며 오직 var에만 사용 가능합니다.

또한, Int, Long, Double, Char와 같은 primmitive 타입에는 사용이 불가합니다.

뭐... int 같은 경우는 어차피 자바에서도 별 값을 주지 않으면 0으로 초기화되니,

var n = 0 과 같이 그냥 초기화하는게 좋을 것 같네요.

 

 

초기화 했는지는 어떻게 확인해?

class Person

lateinit var person: Person // lateinit을 붙여 늦게 초기화해준다고 명시

fun main() {

    if (::person.isInitialized) { // 초기화가 되었는지 확인
        println("Initialized")
    } else {
        println("Not initialized")
    }
}

// 출력
>> Not initialized

 

초기화 여부는 ::(객체).isInitialized 를 통해 확인할 수 있습니다.

앞에 꼭 ::를 붙여야 확인할 수 있습니다.

 

 

초기화 지연 - lazy

class Person {
    init { println("Person created") }
    fun sayHello() { println("Hello") }
}

val person by lazy { Person() } // 초기화를 지연시킴

fun main() {
    println("Point1")
    person.sayHello() // 처음 사용되는 시점에 초기화
    println("Point2")
}


// 출력
>> Point1
Person created
Hello
Point2

 

lazy는 지연 초기화를 사용할 때 쓰는 키워드로, 오직 val만 가능합니다.

이는 말 그대로 초기화를 지연시키는 역할을 하는데요.

by lazy { }와 같이, 람다식을 이용해 사용합니다.

 

위 예시 코드에서, Person 클래스는 생성되는 순간 init에서 println(”Person created”)를 출력합니다.

그리고, 전역변수로써, person을 선언했고, lazy의 바디부분에 생성 코드를 삽입했습니다.

 

이후, main()에서 person 객체를 처음 사용하는 순간 lazy 바디 내 코드가 실행됩니다.

 

 

class Person {
    init {
        println("Person created")
    }

    fun sayHello() {
        println("Hello")
    }
}

val person by lazy {
		// lazy 바디 내 코드가 실행됨.
    val a = 10
    val b = 100
    println(a + b)
    println("코드 실행")
    Person() // 제일 마지막 줄이 person에 대입
}

fun main() {
    println("Point1")
    person.sayHello() // 처음 사용되는 시점에 초기화
    println("Point2")
}


>>> Point1
110
코드 실행
Person created
Hello
Point2

 

참고로, lazy의 바디 부분은 람다식으로써,

person.sayHell()를 통해 person 객체가 처음 호출되는 순간,

lazy의 바디 내 코드가 순차적으로 실행되며,

제일 마지막줄만 person에 대입됩니다.

 

lazy 키워드를 잘 활용하면 메모리와 시간 측면에서 더 효율적으로 작동할 수 있습니다.

 

 

부록. lazy를 유용하게 써본 개인적 경험

사실 저는 lazy 키워드를 메모리 측면보다는, 프로그램 흐름을 맞추는데 유용하게 썼는데요.

 

private val chatAdapter = ChatAdapter(applicationContext, chatModels)
// 엥 applicationContext 없는데요. 앱 팅김!!!
  

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
		
    binding.rvChat = chatAdapter
}

 

리사이클러뷰 등을 사용할 땐, 저는 주로 Adapter를 전역변수로 두는 편입니다.

Adapter에 굳이 context를 건네줄 필요는 없지만,

어댑터에서 특정 작업을 할 때 context가 필요한 경우가 있어 종종 넘겨주곤 합니다.

 

하지만 applicationContext는 onCreate()가 호출될때 생성되며,

전역변수로 선언+초기화를 할 경우,

onCreate()보다 먼저 생성되어 context를 가져오지 못해 앱이 팅기곤 했습니다.

 

 

private lateinit chatAdapter:ChatAdapter 

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

		// 전역변수로 선언만 해두고, 생성은 onCreate() 이후에 해야함.
		chatAdapter = ChatAdapter(applicationContext, chatModels)

		binding.rvChat = chatAdapter
}

 

따라서, 해당 예제에서 context를 넘겨주고 싶을 땐, 전역변수로 선언만 해두고,

onCreate() 이후 생성하는 코드를 따로 넣어줘야 했습니다.

 

 

private val chatAdapter by lazy { ChatAdapter(applicationContext, chatModels) }
  
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
		
    binding.rvChat = chatAdapter
}

 

하지만 lazy 키워드를 사용하여, 선언과 초기화를 동시에 처리할 수 있었습니다.

(실제로 초기화가 된 것은 아닙니다)

 

안드로이드에서 거의 모든 코드는 onCreate() 이후 작동합니다.

따라서, lazy를 사용하면 자연스럽게 context를 onCreate() 이후에 넘겨줄 수 있게 되었습니다.

 

다시 한 번 말씀드리지만… 리사이클러뷰 어댑터에 굳이 context를 넘겨줄 필요는 없습니다.

액티비티보단 리사이클러뷰에서 처리하는게 더 나을 것 같은 작업이 있어 context를 넘겨줬을 뿐…

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.