
이번 포스팅에서는 코틀린에서 배열과 컬렉션을 어떻게 다루는지 알아보고, 이어서 확장함수(Extension Function), 중위함수(Infix Function), Inline 함수, 지역함수, 그리고 람다(Lambda)와 클로저(Closure)까지 간단히 정리해보겠습니다.
1. 배열과 컬렉션
코틀린에서 배열은 상대적으로 자주 사용되지 않지만, 기본 문법을 알아두면 좋습니다. 그리고 코틀린 컬렉션은 '불변(Immutable)'인지 '가변(Mutable)'인지 미리 명시해야 하는 점이 큰 특징입니다.
배열 (Array)
배열을 선언할 때는 arrayOf() 함수를 사용합니다.
Kotlin
fun arraySample() {
val array: Array<Int> = arrayOf(100, 200)
for (i in array.indices) {
println("${i} ${array[i]}")
}
for ((idx, value) in array.withIndex()) {
println("$idx, $value")
}
array.plus(300)
}
- array.indices : 0부터 마지막 인덱스까지의 범위를 나타냅니다.
- withIndex() : 인덱스와 값을 동시에 꺼낼 수 있습니다.
- plus() : 새로운 값을 추가한 새로운 배열을 반환합니다.
컬렉션 (List, Set, Map)
코틀린에서 컬렉션을 만들 때는 반드시 '불변'인지, '가변'인지를 구분해야 합니다.
1) List
Kotlin
fun listSample() {
// 불변 리스트
val numbers = listOf(100, 200)
// 비어있는 불변 리스트 생성
val emptyList = emptyList<Int>()
// 값 가져오기
println(numbers[0])
for (number in numbers) {
println(number)
}
for ((idx, value) in numbers.withIndex()) {
println("$idx $value")
}
// 가변 리스트
val mutableNumbers = mutableListOf(200, 300)
mutableNumbers.add(400) // 가능
for (number in mutableNumbers) {
println(number)
}
}
- listOf() : 기본적으로 불변 리스트 생성
- mutableListOf() : 가변 리스트
- emptyList() : 비어있는 불변 리스트
2) Set
Kotlin
private fun sampleSet() {
// 불변
val numbers = setOf(100, 200, 200, 300)
for (number in numbers) {
println(number)
}
for ((idx, value) in numbers.withIndex()) {
println("$idx $value")
}
// 가변
val mutableSet = mutableSetOf(100, 200)
mutableSet.add(300)
}
- 중복을 허용하지 않는 구조로, 빠른 검색에 유리
3) Map
Kotlin
fun mapSample() {
// 가변
val oldMap = mutableMapOf<Int, String>()
oldMap[1] = "MON"
oldMap[2] = "TUES"
// 불변
val mapOf: Map<Int, String> = mapOf(1 to "MON", 2 to "TUES")
for (key in oldMap.keys) {
println("$key : ${oldMap[key]}")
}
for ((key, value) in oldMap.entries) {
println("$key $value")
}
}
- mapOf(key to value) : 불변 map 생성
컬렉션의 Null 가능성
- List<Int?> : 리스트에는 null이 들어갈 수 있으나, 리스트 자체는 null이 아님
- List? : 리스트 자체가 null 일 수 있지만, 요소들에는 null이 들어갈 수 없음
- List<Int?>? : 리스트도 null일 수 있고, 요소에도 null이 들어갈 수 있음
2. 확장함수와 확장 프로퍼티
Kotlin에는 '확장함수'가 있어, 클래스 밖에서 함수나 프로퍼티를 추가하되 마치 클래스 내부의 멤버처럼 호출할 수 있습니다.
확장함수
Kotlin
fun String.customLastChar(): Char {
return this[this.length - 1]
}
fun main() {
val str = "ABC"
println(str.customLastChar()) // 'C'
}
- 실제 String 클래스 내부에 존재하지 않는 함수를 확장함수로 추가하여 사용
- 클래스의 private, protected 멤버에는 접근할 수 없음 (캡슐화는 여전히 보호)
확장 프로퍼티
Kotlin
val String.lastChar: Char
get() = this[this.length - 1]
val Person.customLastNamePlusMj: String
get() = this.lastName + "Mj"
fun main() {
val str: String = "ABC"
println(str.lastChar) // 'C'
val person = Person("MJ", "Kim", 20)
println(person.customLastNamePlusMj) // 'KimMj'
}
- 멤버 변수를 직접 추가한 것처럼 보이지만, 실제로는 getter를 확장해 만들어낸 방식
- 확장함수와 동일하게 private 멤버에는 접근 불가능
3. 중위함수 (Infix Function)
“함수를 호출하는 새로운 방법!”이라는 말처럼, infix 키워드를 붙이면 함수를 연산자처럼 사용할 수 있습니다.
Kotlin
fun Int.add(other: Int): Int {
return this + other
}
infix fun Int.add2(other: Int): Int {
return this + other
}
fun main() {
println(3 add2 4) // infix call
println(3.add2(5)) // 일반 호출
}
- infix로 선언한 함수는 “수식처럼” 가독성 좋게 쓸 수 있음
- 인자가 하나만 필요하거나, 둘 이상의 인자를 받으면 파라미터를 묶어야 함
4. Inline 함수와 지역 함수
Inline 함수
함수를 호출하는 대신, 함수를 호출한 지점에 함수 본문을 그대로 복사해 붙이는 개념입니다. 함수를 파라미터로 전달할 때 발생하는 오버헤드를 줄일 수 있습니다.
Kotlin
inline fun Int.addCustom(other: Int): Int {
return this + other
}
- 디컴파일 시 함수 본문 자체가 붙여넣기된 형태로 보임
- 성능 측정 후 신중히 사용하는 것을 권장
지역함수
함수 안에 함수를 선언하는 기능입니다. 하지만 별도의 함수로 분리하는 게 가독성 면에서 더 낫다는 의견이 많아 상대적으로 잘 쓰이지 않습니다.
Kotlin
fun createPersonUsingLocalFunction(
firstName: String,
lastName: String
): Person {
fun validateName(name: String, fieldName: String) {
if(name.isEmpty()) {
throw IllegalArgumentException("$fieldName is empty.")
}
}
validateName(firstName, "first name")
validateName(lastName, "last name")
return Person(firstName, lastName, 1)
}
- 코드 깊이가 깊어지고, 여러 책임을 한 함수에 몰아넣게 될 수 있어 자주 권장되진 않음
5. 람다(Lambda)와 클로저(Closure)
코틀린에서는 함수가 일급 시민(First-class citizen)이므로, 변수에 할당하거나, 파라미터로 넘기는 것이 자유롭습니다.
람다 문법
Kotlin
val isApple = { fruit: Fruit -> fruit.name == "사과" }
fun main() {
val fruits = listOf(
Fruit("사과", 1000),
Fruit("바나나", 3000)
)
println(isApple(fruits[0])) // true
println(isApple.invoke(Fruit("메론", 2000))) // false
}
- (Fruit) -> Boolean : 람다의 타입
- invoke() 로 명시적으로 호출 가능
클로저(Closure)
람다가 시작되는 시점에, 참조하고 있는 외부 변수들을 함께 포획(Capture)해서 유지합니다.
Java (개념 비교)
String targetFruitName = "바나나";
targetFruitName = "수박";
// Variable used in lambda expression should be final or effectively final
filterFruits(fruits, fruit -> targetFruitName.equals(fruit.getName()));
// 컴파일 에러
Kotlin
var targetFruitName = "바나나"
targetFruitName = "수박"
println(filterFruits(fruits) { it.name == targetFruitName })
- 코틀린에선 람다가 사용하는 변수가 final이 아니어도 문제없이 동작
- 람다 내부에서 필요한 외부 변수들을 람다 실행 시점에 모두 포획해 두는 구조
→ 이를 클로저라고 함
✏️ 마무리 정리
- 배열(arrayOf)과 다양한 컬렉션(List, Set, Map)에서 불변/가변을 명시 → 의도된 코드 작성
- 확장함수와 확장 프로퍼티로 클래스 외부에서 멤버를 추가하듯 확장
- 중위함수(infix)로 연산자처럼 가독성 있게 함수 호출
- inline 함수는 오버헤드를 줄이지만, 성능 측정이 필수
- 지역함수는 가독성을 해칠 수 있어 사용 시 주의
- 코틀린 람다는 함수가 일급 시민이므로 자유로운 활용 가능 → 클로저로 외부 변수까지 포획
'Languege > Kotlin' 카테고리의 다른 글
[Kotlin Basic] 기초 문법 학습 4 (0) | 2025.03.10 |
---|---|
[Kotlin Basic] 기초 문법 학습 3 (1) | 2025.03.06 |
[Kotlin Basic] 기초 문법 학습 2 (0) | 2025.03.04 |
[Kotlin Basic] 기초 문법 학습 1 (0) | 2025.03.03 |