BE/Kotlin

[Kotlin] 함수형 프로그래밍과 불변성

baek-dev 2025. 2. 24. 20:31

✅ 함수형 프로그래밍과 불변성(immutability) 완벽 가이드

 

함수형 프로그래밍(Functional Programming, FP)은 수학적 함수의 개념을 기반으로 한 프로그래밍 패러다임입니다.

**불변성(immutability)**은 함수형 프로그래밍에서 가장 중요한 개념 중 하나이며,

코드의 예측 가능성, 안전성, 유지보수성을 향상시키는 역할을 합니다.

 

🔹 1. 함수형 프로그래밍이란?

함수형 프로그래밍(Functional Programming, FP)은 “순수 함수(Pure Function)“를 기반으로 하는 프로그래밍 방식입니다.

 

✅ 함수형 프로그래밍의 핵심 원칙

1. 순수 함수(Pure Function) 사용 → 같은 입력이 들어오면 항상 같은 출력 반환.

2. 불변성(Immutability) 유지 → 변수나 데이터 변경 없이 새 값을 생성.

3. 고차 함수(Higher-Order Function) 지원 → 함수를 인자로 전달하거나 반환 가능.

4. 선언형 프로그래밍(Declarative Programming) → “어떻게”가 아니라 “무엇”을 할지 표현.

5. 부수 효과(Side Effect) 최소화 → 함수 외부의 값을 변경하지 않음.

6. 일급 객체(First-Class Citizen)로 함수 지원 → 함수를 변수처럼 다룰 수 있음.

 

📌 대표적인 함수형 프로그래밍 언어: Haskell, Lisp, Scala, Kotlin, Java (Java 8+에서 함수형 스타일 지원)

 

🔹 2. 불변성이란?

**불변성(immutability)**이란 객체(변수, 데이터 등)의 상태가 한 번 생성되면 변경되지 않는 속성을 의미합니다.

 

✅ 불변성의 장점

1. 멀티스레드 환경에서 안전(Thread-Safe) → 동기화 문제 없음.

2. 예측 가능한 코드 작성 가능 → 상태 변화가 없으므로 디버깅이 쉬움.

3. 버그 방지 → 예상치 못한 값 변경이 발생하지 않음.

4. 함수형 프로그래밍과 잘 맞음map, filter, reduce 같은 연산에서 유용.

 

📌 불변성을 유지하는 방법:

변수 대신 val(Kotlin) / final(Java) 사용.

변경할 때는 새로운 객체를 생성 (copy(), map 함수 활용).

컬렉션도 불변으로 사용 (listOf(), setOf() 등).

 

🔹 3. 불변성과 가변성 비교

특징 가변(Mutable) 객체 불변(Immutable) 객체
상태 변경 가능 여부 ✅ 가능 (값 변경 가능) ❌ 불가능 (값 변경 불가)
멀티스레드 환경 ❌ 동기화 필요 ✅ 안전(Thread-Safe)
예측 가능성 ❌ 예측 어려움 (side-effect 발생 가능) ✅ 예측 가능
메모리 사용량 ✅ 적음 (값을 변경함) ❌ 많음 (새로운 객체 생성)
대표적인 예제 ArrayList, MutableList List, data class

 

🔹 4. 함수형 프로그래밍에서 불변성 활용하기

✅ (1) 변수 대신 val 사용 (Kotlin)

var mutableValue = 10 // ❌ 가변 변수 (값 변경 가능)
mutableValue = 20

val immutableValue = 10 // ✅ 불변 변수 (값 변경 불가)
// immutableValue = 20  // ❌ 컴파일 오류 발생

📌 val을 사용하면 변수를 재할당할 수 없음 → 불변성 유지 가능.

 

✅ (2) data class와 copy()를 활용한 불변성 유지

data class User(val name: String, val age: Int)

val user1 = User("Alice", 25)
val user2 = user1.copy(age = 26) // ✅ 새로운 객체 생성 (불변성 유지)

println(user1) // User(name=Alice, age=25)
println(user2) // User(name=Alice, age=26)

📌 객체를 직접 수정하는 대신 copy()를 사용하여 새로운 객체를 생성.

 

✅ (3) 불변 컬렉션 사용

val immutableList = listOf(1, 2, 3) // ✅ 불변 리스트 (수정 불가능)
val mutableList = mutableListOf(1, 2, 3) // ❌ 가변 리스트 (수정 가능)

mutableList.add(4) // ✅ 가능
// immutableList.add(4) // ❌ 불가능 (컴파일 오류 발생)

📌 Kotlin에서는 기본적으로 불변 컬렉션(listOf, setOf)을 사용해야 함.

 

✅ (4) map, filter 활용 (불변 컬렉션 유지)

val numbers = listOf(1, 2, 3, 4, 5)

// 원본 리스트를 변경하지 않고 새로운 리스트 반환
val doubled = numbers.map { it * 2 } 
println(doubled) // [2, 4, 6, 8, 10]

📌 map 함수는 원본 데이터를 변경하지 않고 새로운 리스트를 생성하여 반환.

 

✅ (5) reduce를 활용한 순수 함수 구현

val numbers = listOf(1, 2, 3, 4, 5)

val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 15

📌 기존 데이터를 변경하지 않고, 새로운 값을 반환하는 함수형 스타일.

 

🔹 5. 불변성이 필요한 이유 (왜 불변성을 유지해야 할까?)

✅ (1) 멀티스레드 환경에서 동기화 문제 방지

가변 객체는 여러 스레드에서 동시에 접근하면 동기화 문제 발생 가능.

불변 객체는 동기화 없이 안전하게 공유 가능.

 

❌ 가변 객체 사용 시 문제

class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // ❌ 여러 스레드에서 동시에 접근하면 문제 발생 가능
    }
}

 

✅ 불변 객체 사용 시 해결

data class Counter(val count: Int) {
    fun increment(): Counter = copy(count = count + 1) // ✅ 새로운 객체 반환
}

📌 불변 객체를 사용하면 멀티스레드 환경에서도 안전하게 동작 가능.

 

✅ (2) 예측 가능성 증가

가변 객체는 어디서 값이 변경될지 예측하기 어려움.

불변 객체는 한 번 값이 설정되면 변하지 않으므로 예측 가능성이 높아짐.

 

✅ (3) 함수형 프로그래밍과 궁합이 잘 맞음

불변 객체를 사용하면 순수 함수(Pure Function)를 쉽게 구현할 수 있음.

원본 데이터를 변경하지 않고 새로운 값을 생성하는 방식 → 데이터 흐름이 명확해짐.

 

🔥 결론

1. 함수형 프로그래밍은 순수 함수, 불변성, 고차 함수를 활용하는 패러다임.

2. 불변성을 유지하면 멀티스레드 환경에서도 안전하고, 버그를 줄이며, 예측 가능성을 높일 수 있음.

3. Kotlin에서는 val, data class, copy(), map, reduce 등을 활용하여 불변성을 유지할 수 있음.

4. 불변 객체를 사용하면 동기화 문제 없이 멀티스레드 환경에서 안정적으로 사용할 수 있음.

5. Java는 기본적으로 가변 객체 중심이지만, Kotlin은 불변성을 기본으로 지향하여 유지보수가 쉬운 코드 작성을 가능하게 함.

 

📌 Kotlin에서 불변성을 적극적으로 활용하면, 안전하고 유지보수하기 쉬운 코드 작성이 가능! 🚀