Kim-Baek 개발자 이야기

Kotlin 함수 완벽 가이드 | fun으로 시작하는 간결한 코드 본문

개발/java basic

Kotlin 함수 완벽 가이드 | fun으로 시작하는 간결한 코드

김백개발자 2025. 12. 21. 21:38
반응형

이 글을 읽으면: Java 메서드보다 훨씬 강력하고 유연한 Kotlin 함수 작성법을 배울 수 있습니다. 단일 표현식 함수, 기본 매개변수, Named Arguments, 확장 함수까지 실전 예제로 마스터하세요.


📌 목차

  1. 들어가며 - Java 메서드의 불편함
  2. 함수 기본 문법 - fun 키워드
  3. 반환 타입과 Unit
  4. 단일 표현식 함수 - 한 줄로 끝내기
  5. 기본 매개변수 - 오버로딩 필요 없음
  6. Named Arguments - 가독성 극대화
  7. 가변 인자 - vararg
  8. 확장 함수 - 기존 클래스 확장
  9. 마무리 - 다음 편 예고

들어가며 - Java 메서드의 불편함

Java로 간단한 계산 메서드를 만들 때 이런 경험 있으신가요?

// Java - 너무 장황함
public class MathUtils {
    
    // 1. 항상 클래스 안에 있어야 함
    public static int add(int a, int b) {
        return a + b;
    }
    
    // 2. 오버로딩으로 기본값 구현
    public static int add(int a) {
        return add(a, 0);
    }
    
    // 3. 메서드명으로만 구분
    public static double calculateDiscount(double price, double rate) {
        return price * (1 - rate);
    }
    
    // 4. 가변 인자
    public static int sum(int... numbers) {
        int result = 0;
        for (int num : numbers) {
            result += num;
        }
        return result;
    }
}

Kotlin은 이렇게 간단합니다:

// Kotlin - 깔끔하고 직관적
fun add(a: Int, b: Int = 0) = a + b  // 기본값 지원, 한 줄 함수

fun calculateDiscount(price: Double, rate: Double = 0.1) = 
    price * (1 - rate)  // Named Arguments 가능

fun sum(vararg numbers: Int) = numbers.sum()  // 가변 인자

// 확장 함수 - 기존 클래스에 메서드 추가
fun String.isValidEmail() = this.contains("@")

오늘은 이 강력한 함수 기능들을 하나씩 배워보겠습니다!


함수 기본 문법 - fun 키워드

기본 함수 선언

// 형식: fun 함수명(매개변수: 타입): 반환타입 { ... }
fun greet(name: String): String {
    return "안녕하세요, ${name}님!"
}

fun main() {
    val message = greet("규철")
    println(message)  // 안녕하세요, 규철님!
}

Java와 비교

// Java - public static 필수
public class Utils {
    public static String greet(String name) {
        return "안녕하세요, " + name + "님!";
    }
}

// 사용
String message = Utils.greet("규철");
// Kotlin - 클래스 없이 최상위 함수 가능
fun greet(name: String): String {
    return "안녕하세요, ${name}님!"
}

// 사용
val message = greet("규철")

매개변수 없는 함수

fun getCurrentTime(): String {
    return java.time.LocalDateTime.now().toString()
}

fun main() {
    println(getCurrentTime())
}

매개변수 여러 개

fun calculateBMI(weight: Double, height: Double): Double {
    return weight / (height * height)
}

fun main() {
    val bmi = calculateBMI(70.0, 1.75)
    println("BMI: $bmi")  // BMI: 22.857142857142858
}

반환 타입과 Unit

반환 타입 명시

fun add(a: Int, b: Int): Int {
    return a + b
}

fun isPositive(number: Int): Boolean {
    return number > 0
}

fun getUser(id: String): User {
    return User(id, "규철")
}

Unit - 반환값이 없을 때

Java의 void와 같지만, Kotlin에서는 Unit이 실제 객체입니다.

fun printMessage(message: String): Unit {
    println(message)
}

// Unit은 생략 가능
fun printMessage2(message: String) {
    println(message)
}

fun main() {
    printMessage("안녕하세요")
    printMessage2("반갑습니다")
}

Java와 비교

// Java - void
public static void printMessage(String message) {
    System.out.println(message);
}
// Kotlin - Unit (생략 가능)
fun printMessage(message: String) {
    println(message)
}

Nothing - 절대 반환하지 않는 함수

fun fail(message: String): Nothing {
    throw IllegalStateException(message)
}

fun main() {
    val user = getUser() ?: fail("사용자를 찾을 수 없습니다")
    // fail() 이후 코드는 실행되지 않음
}

단일 표현식 함수 - 한 줄로 끝내기

기본 형태

함수 본문이 한 줄이면 중괄호와 return을 생략할 수 있습니다.

// 일반 함수
fun add(a: Int, b: Int): Int {
    return a + b
}

// 단일 표현식 함수
fun add(a: Int, b: Int): Int = a + b

// 반환 타입도 생략 가능 (타입 추론)
fun add(a: Int, b: Int) = a + b

실전 예제

// 최댓값 구하기
fun max(a: Int, b: Int) = if (a > b) a else b

// 절댓값
fun abs(number: Int) = if (number >= 0) number else -number

// 제곱
fun square(x: Int) = x * x

// 문자열 길이 확인
fun isLongText(text: String) = text.length > 100

fun main() {
    println(max(10, 20))        // 20
    println(abs(-5))            // 5
    println(square(4))          // 16
    println(isLongText("짧음"))  // false
}

when을 사용한 단일 표현식

fun getGrade(score: Int) = when {
    score >= 90 -> "A"
    score >= 80 -> "B"
    score >= 70 -> "C"
    score >= 60 -> "D"
    else -> "F"
}

fun main() {
    println(getGrade(85))  // B
    println(getGrade(95))  // A
}

Java와 비교

// Java - return 필수
public static int max(int a, int b) {
    return (a > b) ? a : b;
}

public static String getGrade(int score) {
    if (score >= 90) return "A";
    else if (score >= 80) return "B";
    else if (score >= 70) return "C";
    else if (score >= 60) return "D";
    else return "F";
}
// Kotlin - 훨씬 간결
fun max(a: Int, b: Int) = if (a > b) a else b

fun getGrade(score: Int) = when {
    score >= 90 -> "A"
    score >= 80 -> "B"
    score >= 70 -> "C"
    score >= 60 -> "D"
    else -> "F"
}

기본 매개변수 - 오버로딩 필요 없음

기본값 설정

Java의 메서드 오버로딩 대신 기본값을 사용합니다.

fun greet(name: String, greeting: String = "안녕하세요") {
    println("$greeting, ${name}님!")
}

fun main() {
    greet("규철")                    // 안녕하세요, 규철님!
    greet("규철", "반갑습니다")       // 반갑습니다, 규철님!
}

여러 기본값

fun sendEmail(
    to: String,
    subject: String = "제목 없음",
    body: String = "",
    cc: String? = null
) {
    println("받는사람: $to")
    println("제목: $subject")
    println("내용: $body")
    cc?.let { println("참조: $it") }
}

fun main() {
    sendEmail("user@example.com")
    
    sendEmail(
        to = "user@example.com",
        subject = "중요한 메일",
        body = "잘 부탁드립니다"
    )
}

Java 오버로딩과 비교

// Java - 오버로딩으로 구현 (코드 중복)
public static void sendEmail(String to) {
    sendEmail(to, "제목 없음", "");
}

public static void sendEmail(String to, String subject) {
    sendEmail(to, subject, "");
}

public static void sendEmail(String to, String subject, String body) {
    System.out.println("받는사람: " + to);
    System.out.println("제목: " + subject);
    System.out.println("내용: " + body);
}
// Kotlin - 기본값으로 간단하게
fun sendEmail(
    to: String,
    subject: String = "제목 없음",
    body: String = ""
) {
    println("받는사람: $to")
    println("제목: $subject")
    println("내용: $body")
}

실전 예제 - API 호출 함수

fun callApi(
    url: String,
    method: String = "GET",
    timeout: Int = 5000,
    retry: Int = 3,
    headers: Map<string, string=""> = emptyMap()
) {
    println("API 호출: $method $url")
    println("타임아웃: ${timeout}ms")
    println("재시도: ${retry}회")
}

fun main() {
    // 필수 파라미터만
    callApi("https://api.example.com/users")
    
    // 일부만 커스터마이징
    callApi(
        url = "https://api.example.com/posts",
        method = "POST",
        timeout = 10000
    )
}
</string,>

Named Arguments - 가독성 극대화

기본 사용법

파라미터 이름을 명시하여 순서 상관없이 호출 가능합니다.

fun createUser(
    name: String,
    age: Int,
    email: String,
    isActive: Boolean = true
) {
    println("이름: $name, 나이: $age, 이메일: $email, 활성: $isActive")
}

fun main() {
    // 순서대로
    createUser("규철", 30, "user@example.com")
    
    // Named Arguments로 순서 바꾸기
    createUser(
        email = "user@example.com",
        name = "규철",
        age = 30
    )
    
    // 일부만 Named Arguments
    createUser("규철", 30, email = "user@example.com")
}

가독성 향상 예제

// ❌ 나쁜 예 - 무슨 의미인지 모름
createRectangle(100, 200, 50, 50)

// ✅ 좋은 예 - 명확함
createRectangle(
    width = 100,
    height = 200,
    x = 50,
    y = 50
)

Boolean 파라미터는 항상 Named Arguments!

fun saveFile(
    path: String,
    overwrite: Boolean,
    createBackup: Boolean
) {
    println("파일 저장: $path")
}

fun main() {
    // ❌ 나쁜 예 - 무슨 의미인지 헷갈림
    saveFile("/data/file.txt", true, false)
    
    // ✅ 좋은 예 - 명확함
    saveFile(
        path = "/data/file.txt",
        overwrite = true,
        createBackup = false
    )
}

Java에는 없는 기능!

// Java - Named Arguments 없음
createUser("규철", 30, "user@example.com", true);
// 파라미터 순서를 외워야 함!
// Kotlin - 순서 상관없이 명확하게
createUser(
    age = 30,
    name = "규철",
    email = "user@example.com"
)

가변 인자 - vararg

기본 사용법

fun sum(vararg numbers: Int): Int {
    var result = 0
    for (num in numbers) {
        result += num
    }
    return result
}

fun main() {
    println(sum(1, 2, 3))           // 6
    println(sum(1, 2, 3, 4, 5))     // 15
    println(sum())                   // 0
}

배열을 가변 인자로 전달 - Spread 연산자

fun printAll(vararg messages: String) {
    messages.forEach { println(it) }
}

fun main() {
    val arr = arrayOf("사과", "바나나", "오렌지")
    
    // ❌ 에러 - 배열을 그대로 전달 불가
    // printAll(arr)
    
    // ✅ Spread 연산자 (*) 사용
    printAll(*arr)
    
    // 일반 인자와 혼합 가능
    printAll("딸기", *arr, "포도")
}

실전 예제 - 로그 함수

fun log(level: String, vararg messages: Any) {
    val timestamp = java.time.LocalDateTime.now()
    println("[$timestamp] [$level] ${messages.joinToString(" ")}")
}

fun main() {
    log("INFO", "서버 시작됨")
    log("ERROR", "DB 연결 실패:", "timeout after", 5000, "ms")
    log("DEBUG", "사용자", 100, "개 조회됨")
}

Java와 비교

// Java - ... 문법
public static int sum(int... numbers) {
    int result = 0;
    for (int num : numbers) {
        result += num;
    }
    return result;
}

// 배열 전달
int[] arr = {1, 2, 3};
sum(arr);  // OK
// Kotlin - vararg 키워드
fun sum(vararg numbers: Int) = numbers.sum()

// 배열 전달 시 * 필요
val arr = intArrayOf(1, 2, 3)
sum(*arr)

확장 함수 - 기존 클래스 확장

개념 이해

기존 클래스를 수정하지 않고 새 메서드를 추가하는 마법!

// String 클래스에 메서드 추가
fun String.isValidEmail(): Boolean {
    return this.contains("@") && this.contains(".")
}

fun main() {
    val email = "user@example.com"
    println(email.isValidEmail())  // true
    
    val invalid = "notanemail"
    println(invalid.isValidEmail())  // false
}

실전 예제 1 - String 확장

// 전화번호 포맷팅
fun String.formatPhoneNumber(): String {
    return if (this.length == 11) {
        "${this.substring(0, 3)}-${this.substring(3, 7)}-${this.substring(7)}"
    } else {
        this
    }
}

// 앞뒤 공백 제거 후 대문자 변환
fun String.trimAndUpperCase() = this.trim().uppercase()

fun main() {
    val phone = "01012345678"
    println(phone.formatPhoneNumber())  // 010-1234-5678
    
    val text = "  kotlin  "
    println(text.trimAndUpperCase())    // KOTLIN
}

실전 예제 2 - Int 확장

// 홀수/짝수 판별
fun Int.isEven() = this % 2 == 0
fun Int.isOdd() = this % 2 != 0

// 범위 안에 있는지 확인
fun Int.isBetween(min: Int, max: Int) = this in min..max

fun main() {
    println(4.isEven())           // true
    println(5.isOdd())            // true
    println(50.isBetween(1, 100)) // true
}

실전 예제 3 - List 확장

// 두 번째 요소 가져오기
fun <T> List<T>.secondOrNull() = if (this.size >= 2) this[1] else null

// 마지막 N개 요소 가져오기
fun <T> List<T>.takeLast(n: Int) = this.drop(maxOf(0, this.size - n))

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    
    println(numbers.secondOrNull())  // 2
    println(numbers.takeLast(2))     // [4, 5]
}

Java에서는 불가능!

// Java - Utility 클래스로만 가능
public class StringUtils {
    public static boolean isValidEmail(String email) {
        return email.contains("@") && email.contains(".");
    }
}

// 사용
StringUtils.isValidEmail("user@example.com");
// Kotlin - 마치 원래 있던 메서드처럼
fun String.isValidEmail() = this.contains("@") && this.contains(".")

// 사용
"user@example.com".isValidEmail()

주의사항

// 확장 함수는 멤버 함수를 오버라이드할 수 없음
class MyClass {
    fun method() = "멤버"
}

fun MyClass.method() = "확장"  // 무시됨!

fun main() {
    println(MyClass().method())  // "멤버" 출력
}

마무리 - 다음 편 예고

오늘 배운 것 ✅

  • fun 키워드로 함수 선언
  • 단일 표현식 함수 (=로 간결하게)
  • 기본 매개변수로 오버로딩 제거
  • Named Arguments로 가독성 향상
  • vararg로 가변 인자 처리
  • 확장 함수로 기존 클래스 확장

다음 편에서 배울 것 📚

4편: 제어 구조 완벽 가이드 | if, when, for, while 마스터하기

  • if를 표현식으로 사용하기
  • when - Java switch의 강력한 대안
  • for 반복문과 range
  • while, do-while
  • break, continue, 라벨

실습 과제 💪

// 1. 단일 표현식 함수로 작성하기
// - 두 수 중 작은 값 반환
// - 문자열이 숫자인지 확인

// 2. 기본 매개변수 활용
// - 사용자 정보 출력 함수 (이름 필수, 나이/이메일 선택)

// 3. 확장 함수 만들기
// - String: 한글인지 확인
// - Int: 소수인지 확인
// - List<Int>: 평균 계산

자주 묻는 질문 (FAQ)

Q: 함수를 클래스 밖에 선언해도 되나요?
A: 네! Kotlin은 최상위 함수를 지원합니다. 유틸리티 함수는 클래스 없이 선언하세요.

Q: 기본 매개변수와 오버로딩 중 뭐가 좋나요?
A: 대부분은 기본 매개변수가 더 간결합니다. 완전히 다른 로직이면 오버로딩을 쓰세요.

Q: 확장 함수를 언제 쓰나요?
A: 기존 클래스에 자주 쓰는 유틸리티 기능을 추가할 때 씁니다.

Q: Named Arguments를 항상 써야 하나요?
A: 필수는 아니지만, Boolean 파라미터나 3개 이상 파라미터가 있으면 권장합니다.


관련 글


💬 댓글로 알려주세요!

  • 어떤 확장 함수를 만들어보고 싶으신가요?
  • 기본 매개변수가 유용했던 경험이 있나요?
  • 이 글이 도움이 되셨나요?

 

반응형
Comments