1. 코틀린 확장함수(Extension Function) 란?
언어별 프레임워크 안에 오픈소스로 구현되어 있는 여러 함수들 있다.
예를 들면 자바 toString() 함수를 생각해보자. 해당 메서드는 출력 형식은 고정되어 있지만, 우리가 필요한 형식이 아닐 수 있다.
여기서 우린 이 부분을 사용하는 목적에 맞게 커스텀해서 사용하고 싶은 생각을 할 수 있지만, 자바는 안 된다.
하지만, 코틀린에서는 이러한 요구사항을 처리할 수 있는 함수가 라이브러리에 존재한다. 이게 바로 확장함수이다.
기본으로 정의된 함수인 것처럼 새로운 기능을 추가하는 기능이다.
정리하자면, 기존 클래스에 새로운 함수를 확장한다.
2. 코틀린 범위지정함수(Scope Function)란?
범위지정 함수는 고차함수로 정의되고, 객체 Context 내에서 코드 블럭을 실행하는 것이 유일한 목적인 함수이다.
범위지정 함수를 람다식을 이용하여 호출하면 일시적인 Scope 가 생성되는데, 이 Scope 내에서 이름이 없어도(it, this을 통해) 객체에 접근할 수가 있다.
또한 코틀린 범위지정 함수는 여러 개가 존재하는데, 두 가지 기준으로 차이를 나눌 수가 있다.
- Context Ojbect 를 참조하는 방법 (this, it)
- 전체 표현식의 결과 (Context Object 또는 Lamda Result)
💡Context object 란? 즉, 맥락을 의미한다. 새롭게 생성된 객체가 지금 무슨일이 일어나고 있는지 알수 있도록 한다.
또한 범위지정함수 별 의도하는 목적이 다르다.
- let : null 이 아닌 객체에서 람다 실행
- let : 가독성을 위해 코드 블럭 내에 지역변수를 제공
- apply : 객체 구성
- run : 객체 구성 및 결과 계산
- run(확장함수가 아님) : 표현식이 필요한 실행문
- also : 추가 효과
- with : 객체에 함수 호출을 그룹화, 초기화 작업 및 마지막 블록 결과값 리턴 시 자주 사용 (null safe 하지 못하다)
3. 코틀린 let, run, with, apply, also 차이를 설명하시오.
let
let 함수는 자기 자신을 인수로 전달하고 수행된 결과. 즉, 블록의 마지막 값을 반환하게 된다. 인수로 전달한 객체는 it 으로 참조한다. 주로 ?. 연산자와 함께 사용하여 null 이 아닐 때만 실행하는 코드로 자주 사용한다.
fun<T, R> T.let(block : (T) -> R) : R
// 예제1
fun main() {
var company: String? = "아이티"
company?.let { // ?. 는 null 이 아닌 경우 실행하겠다는 의미입니다
println("첫번째 let -> $it")
}
company = null
company?.let { println("두번째 let -> $it") }
}
// 예제2
fun main() {
val str = "1234"
val result = str.let {
it + "aaaa" //블록의 마지막 결과값을 반환한다.
}
println("str 값 -> $str")
println("result 값 -> $result")
}
run
run 함수는 익명 함수처럼 사용하는 방법 과 객체에서 호출하는 방법 을 모두 제공한다. 앞선 let 과 같이 블록의 마지막 결과를 반환한다. 블록 안에 선언된 변수는 모두 임시로 사용되는 변수이고, 복잡한 계산이나 임시변수가 많이 필요할 때 유용하다.
fun<R> run(block : () -> R ) : R //익명함수처럼 사용하는 방법
fun<T, R> T.run(block : T.() -> R ) : R
// 예제1, 익명함수 처럼 사용
fun main() {
val result = run {
val name = "홍길동"
val hobby = "프로그래밍"
val age = 27
"이름 : $name, 취미 : $hobby, 프로그래밍 : $age"
}
print(result) //print(name) 컴파일에러 }
}
// 예제2 객체에서 호출
fun main() {
val str = "abCdEfG"
val result1 = str.run {
// receiver 로 암시적 전달
this.toLowerCase() //this == str
}
val result2 = str.run {
toLowerCase()// this 생략가능, 마지막 코드 블록 수행결과 반환
}
println("result1 -> $result1")
println("result2 -> $result2")
println("str -> $str")
}
with
with 함수는 인수로 객체를 받고 블록에 리시버 객체로 전달한다. 그리고 블록에서 수행된 마지막 결과를 반환한다. 리시버 객체로 전달된 객체는 this 로 접근 가능하고, 생략도 가능하다. with() 를 사용 시 주의할 점은 null safe 호출이 불가능하므로 인자가 절대로 null 이 아닌게 확실할 경우에만 사용해야 한다.
💡리시버 객체란? 바로 이어지는 블록 내에서 메서드 및 속성에 바로 접근할 수 있도록 할 객체를 의미합니다. (접근 제어자에 따라 접근 가능한 범위에 한함)
fun<T, R> with(receiver : T, block T.() -> R) : R
fun main() {
val person = Person("홍길동", 27, "서울")
with(person) {
println(this.name)
println(age) //this 생략가능
println(city)
}
println()
val result1 = with(person) {
println("Result With") // 블록 마지막 결과값 println() 함수의 리턴타입인 Unit 반환, Unit은 자바로 치면 void와 같습니다.
}
println()
val result2 = with(person) {
"RESULT" // 블록 마지막 결과값 String 반환
}
println("result1 -> $result1")
println("result2 -> $result2")
}
data class Person(val name: String, val age: Int, val city: String)
apply
apply() 함수는 블록에 객체 자신이 리시버 객체로 전달되고 이 객체가 반환된다. 객체의 상태를 변화시키고 그 객체를 다시 반환할 때 주로 사용된다. 그리고 자기 자신을 리턴하기 때문에 apply 적용한 값으로 객체상태가 변하게 된다.
fun<T> T.apply(block : T.() -> Unit ) : T
// 예제1
fun main() {
val person = Person("에이스", 20, "이스트블루")
val result = person.apply {
this.name = "흰수염"
age = 75 // this 생략가능
city = "신세계 스핑크스"
}
println("person -> $person")
println("result -> $result")
}
data class Person(var name: String, var age: Int, var city: String)
// 안드로이드 예제
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ItemBoardBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_board,
parent,
false
)
return ViewHolder(binding).apply {
binding.root.setOnClickListener { view ->
val position = bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }
?: return@setOnClickListener
itemClick(items[position])
}
}
}
also
also 함수는 수신 객체 지정 람다에 매개변수 T 로 코드 블록 내에 명시적으로 전달되며 코드 블록 내에 전달된 수신 객체를 그대로 다시 반환한다. 수신 객체 람다가 전달된 수신 객체를 전혀 사용하지 않거나, 수신 객체의 속성을 변경하지 않고 사용하는 경우 also 를 사용한다. 또한, 객체의 데이터 유효성을 확인하거나, 디버그, 로깅 등의 부가적인 목적으로 사용할 때 적합하다.
also 는 apply 와 마찬가지로 수신 객체를 반환하므로 블록 함수가 다른 값을 반환 해야하는 경우에는 해당 함수를 사용 할수 없다.
fun<T> T.also(block: (T) -> Unit): T
// 예제1
fun main() {
val name = "HongGillDong"
val fixedName = name.also {
it.toUpperCase()
}
println("name -> $name")
println("fixedName -> $fixedName")
val person = Person(name = "홍길동", age = 27, city = "서울")
val result = person.also {
it.name = "백종원"
it.age = 50
it.city = "부산"
}
println("person -> $person")
println("result -> $result")
val numbers = mutableListOf("one", "two", "three")
numbers.also { println("numbers: $it") }.add("four")
println("numbers -> $numbers")
}
data class Person(var name: String, var age: Int, var city: String)
// 안드로이드 예제
@Database(entities = [GardenPlanting::class, Plant::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun gardenPlantingDao(): GardenPlantingDao
abstract fun plantDao(): PlantDao
companion object {
// For Singleton instantiation
@Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
4. Entry Point 란?
소프트웨어에서 Entry Point 는 진입점을 의미하다. 예를 들어 Java 를 사용할 때는 main 함수가 EntryPoint 이다.
보통 EntryPoint 는 main 과 유사하게 오직 하나만 존재하며 단일 진입점이라고 부르기도 한다.
하지만, 안드로이드 시스템에서는 단일 진입점이 아닌 4대 컴포넌트를 통해서 진입이 가능하다.
즉, Activity 도 Entry Point 중 하나로 사용자와 상호작용하는 UI 제공과 동시에 진입점 역할도 하게 된다.
안드로이드 시스템에서 Entry Point 를 통해 해당 애플리케이션의 컴포넌트로 제어권을 넘기기 위해서 매니페스트에 명시해야 한다.
5. LifeCycle 에 대해서 설명하시오.
뷰(Activity/Fragment)가 시작하고 끝날 때까지의 모든 루틴을 가리킨다.
LifecycleOwner는 Activity를 의미하고, 내부에 Lifecycle을 갖고 있다.
뷰(Activity/Fragment)의 상태 변화에 따른 콜백 메스드를 총칭하고 상태 변화를 알려준다.
Lifecycle은 뷰의 상태를 저장하고 옵저버들에게 상태변화를 알려줍니다.
Lifecycle 상태를 알고 싶다면 LifecycleObserver를 생성하고 Lifecycle에 등록을 하면 됩니다.
왜 필요할까?
일반적으로 뷰의 생명 주기 메소드에 데이터를 불러오거나, 리소스를 정리한다.
하지만, 프로젝트 규모가 커지면서 코드가 광범위해져 생명준기 관련하여 잠재적인 에러 유발 요소가 존재한다.
그래서 생명 주기에 의존적이었던 코드를 걷어내고 lifecycle-aware 에 이에 대한 처리를 위임함으로써 유지보수성을 높인다.
컴포넌트가 라이프사이클에 따른 작업을 소유할 수 있도록 한다.
💡Lifecycle-aware 컴포넌트란? 액티비티와 프래그먼트 같은 컴포넌트의 라이프사이클이 변경될 때, 이에 대응하는 라이브러리이다. 라이브사이클오너의 상태변화를 옵저브하여 필요한 작업을 스스로하는 기능을 말한다.
'Android Q&A > Android One a day' 카테고리의 다른 글
[안드로이드 면접] - 19 : (0) | 2023.02.24 |
---|---|
[안드로이드 면접] - 18 : (0) | 2023.02.20 |
[안드로이드 면접] - 16 : (1) | 2023.02.17 |
[안드로이드 면접] - 15 : (0) | 2023.02.15 |
[안드로이드 면접] 면접 질문 리스트 (0) | 2023.02.15 |