1. 코틀린에서의 람다 표현식이 무엇인가?
- 람다 표현식은 함수를 명시적으로 선언하지 않고도 함수와 유사한 구조를 정의하는 간결한 방법입니다.
- 인수로 전달하거나 변수에 저장할 수 있는 코드 블록을 만들 수 있습니다.
- 람다 표현식은 간결하고 인라인 함수 동작을 제공하는 데 유용합니다.
val sum = { a: Int, b: Int -> a + b } // Lambda expression
val result = sum(3, 4) // Invoking the lambda expression
println(result) // Output: 7
2. 코틀린에서 고차 함수란 무엇인가?
- 고차 함수(High-Order Function)란 다른 함수를 인자로 받거나, 함수를 반환하는 함수를 의미합니다.
- 함수를 변수처럼 다룰 수 있게 해주는 개념입니다.
- 고차 함수는 특히 람다 표현식과 결합할 때 유연하고 간결한 코드를 제공합니다.
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b) // 전달받은 함수(`operation`) 실행
}
fun main() {
val sum = calculate(5, 3) { x, y -> x + y } // `operation`에 `(x, y) -> x + y` 람다 전달
println(sum) // 출력: 8
}
3. 코틀린에서 확장 함수란 무엇인가?
- Kotlin의 확장 함수는 소스 코드를 변경하지 않고도 기존 클래스에 새 함수를 추가할 수 있는 기능을 제공합니다.
- 상속에 의존하거나 원래 클래스를 직접 수정하지 않고도 클래스의 기능을 확장할 수 있는 수단을 제공합니다.
fun String.addExclamation(): String = "$this!"
val message = "Hello"
val modifiedMessage = message.addExclamation() // Using an extension function
println(modifiedMessage) // Output: Hello!
4. 코틀린에서 Sealed Class란 무엇인가?
- Sealed class는 상속이 가능한 클래스로, 같은 파일 내에서만 하위 클래스를 정의할 수 있도록 제한하는 클래스입니다.
- 클래스의 상속을 제한하여 예측 가능한 계층 구조를 만들 때 유용합니다.
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
- Result 클래스는 sealed class로 선언되어 다른 파일에서 상속할 수 없음
- Success, Error, Loading은 Result를 상속하는 하위 클래스
- Sealed class를 사용하면 컴파일러가 모든 가능한 하위 클래스를 알고 있기 때문에 when문에서 else 없이 처리 가능
5. 코틀린에서 객체 표현식의 개념을 설명하시오.
- 코틀린에서 객체 표현식은 익명 객체를 생성할 때 사용하는 기능입니다.
- 즉, 클래스를 별도로 선언하지 않고, 즉석에서 새로운 객체를 만들 수 있는 방식입니다.
val obj = object {
val name = "Kotlin"
fun printName() = println("Name: $name")
}
fun main() {
obj.printName() // 출력: Name: Kotlin
}
- 객체 표현식은 인터페이스를 구현하는 익명 객체를 만들 때 주로 사용됩니다.
val clickListener = object : OnClickListener {
override fun onClick() {
println("버튼 클릭됨!")
}
}
clickListener.onClick() // 출력: 버튼 클릭됨!
- 객체 표현식을 사용하여 익명 객체로 기존 클래스를 상속할 수도 있음.
open class Animal {
open fun makeSound() = println("동물이 소리를 냅니다.")
}
val cat = object : Animal() {
override fun makeSound() = println("야옹!")
}
fun main() {
cat.makeSound() // 출력: 야옹!
}
- 객체 표현식과 object declaration(싱글턴) 차이
6. Kotlin에서 reified 수정자의 용도는 무엇인가?
- reified는 제네릭 타입 정보를 런타임에서도 유지할 수 있도록 하는 기능입니다.
- 일반적으로 제네릭 타입은 컴파일 시 제거(타입 소거, Type Erasure)되지만 reified를 사용하면 런타임에서도 타입 정보를 사용 가능
inline fun <reified T> getTypeName(): String {
return T::class.simpleName ?: "Unknown"
}
val typeName = getTypeName<Int>()
println(typeName) // Prints "Int"
- 예를 들어보자면, 안드로이드에서 Activity를 시작할 때 클래스 정보를 전달해야 하지만 제네릭 타입은 런타임에서 알 수 없기 때문에 reified를 사용하면 편리합니다.
inline fun <reified T : Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}
fun main() {
// ExampleActivity로 이동
context.startActivity<ExampleActivity>()
}
7. 코틀린에서 delegates에 대해 설명하시오.
- delegates는 속성이나 함수의 구현을 다른 객체에 위임하는 메커니즘을 제공하여 상속을 사용하지 않고도 공통된 동작을 재사용하거나 기존 객체에 추가 기능을 추가할 수 있도록 해줍니다.
- by 키워드를 활용한 클래스 위임
interface Printer {
fun printMessage()
}
// Printer 인터페이스 구현 클래스
class RealPrinter : Printer {
override fun printMessage() {
println("프린터 출력 중...")
}
}
// Printer 기능을 RealPrinter에 위임하는 클래스
class PrinterDelegate(private val printer: Printer) : Printer {
override fun printMessage() {
printer.printMessage() // 위임
}
}
// 위의 `PrinterDelegate`를 간단하게 `by` 키워드로 변환 가능!
class SmartPrinter(printer: Printer) : Printer by printer
fun main() {
val printer = RealPrinter()
val smartPrinter = SmartPrinter(printer)
smartPrinter.printMessage() // "프린터 출력 중..." (실제 실행은 `RealPrinter`에서 수행됨)
}
- by lazy를 활용한 프로퍼티 위임
val lazyValue: String by lazy {
println("초기화 수행 중...")
"Hello, Kotlin!"
}
fun main() {
println("프로그램 시작")
println(lazyValue) // "초기화 수행 중..." + "Hello, Kotlin!"
println(lazyValue) // "Hello, Kotlin!" (이미 초기화되었으므로 실행 X)
}
- observable을 활용한 값 변경 감지
class User {
var name: String by Delegates.observable("초기값") { prop, old, new ->
println("${prop.name} 변경: $old -> $new")
}
}
fun main() {
val user = User()
user.name = "Kotlin" // 출력: name 변경: 초기값 -> Kotlin
}
- vetoable을 활용한 값 변경 제한
class User {
var age: Int by Delegates.vetoable(18) { _, old, new ->
new >= 18 // 18세 이상만 변경 허용
}
}
fun main() {
val user = User()
println("현재 나이: ${user.age}") // 18
user.age = 16
println("변경 시도 후 나이: ${user.age}") // 20 (변경 거부됨)
}
- map을 활용한 프로퍼티 위임
class User(map: Map<String, Any>) {
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf("name" to "Kotlin", "age" to 25))
println("${user.name}, ${user.age}") // 출력: Kotlin, 25
}
8. 코틀린의 꼬리 재귀에 대해 설명하시오.
- Kotlin의 Tail Recursion(꼬리 재귀)은 재귀 함수의 최적화를 위한 기법입니다.
- 재귀 호출이 함수의 마지막 실행 단계(꼬리 위치)에 위치할 경우, 컴파일러가 이를 반복문으로 최적화하여 StackOverflow를 방지합니다.
- Kotlin 컴파일러는 tailrec을 만나면 재귀 호출을 반복문으로 변환하여 최적화합니다.
tailrec fun factorial(n: Int, acc: Int = 1): Int {
return if (n == 1) {
acc
} else {
factorial(n - 1, n * acc) // ✅ 마지막 호출 (꼬리 위치)
}
}
fun main() {
println(factorial(5)) // 출력: 120
}
9. Kotlin에서 연산자 오버로딩의 개념을 설명하시오.
- Kotlin의 연산자 오버로딩은 사용자가 자신의 클래스 인스턴스에 적용할 때 연산자에 대한 사용자 정의 동작을 정의할 수 있도록 해줍니다.
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector): Vector {
return Vector(x + other.x, y + other.y)
}
}
fun main() {
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
val sum = v1 + v2
println(sum) // Output: Vector(x=4, y=6)
}
10. Kotlin의 closure가 무엇인가?
- closure는 해당 범위가 실행을 마친 후에도 둘러싼 범위에서 변수와 매개변수에 대한 액세스를 유지하는 함수를 말합니다.
fun createIncrementFunction(incrementBy: Int): () -> Int {
var count = 0
return {
count += incrementBy
count
}
}
fun main() {
val incrementByTwo = createIncrementFunction(2)
println(incrementByTwo()) // Output: 2
println(incrementByTwo()) // Output: 4
}
- 위 예제에서 createIncrementFunction은 호출될 때마다 지정된 increaseBy 값만큼 count를 증가시키는 람다 함수를 반환합니다.
- 변수 count는 람다에 의해 캡처되고 호출 사이에 상태를 유지합니다.
- 따라서 increaseByTwo()가 호출될 때마다 count를 2만큼 증가시키고 업데이트된 값을 반환합니다.
참고)
https://medium.com/@mohit2656422/top-10-kotlin-questions-2024-365bc020198f
Top 10 Kotlin Questions 2024
Kotlin, a versatile programming language developed by JetBrains, has rapidly gained popularity among Android developers due to its modern…
medium.com