코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 함수를 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) 강의에 소금을 조금 친 내용이다
함수 선언 문법
- 코틀린에서 함수는 fun 키워드로 선언한다
// 기본 형태
fun max(a: Int, b: Int): Int {
if (a > b) {
return a
}
return b
}
// if-else
fun max(a: Int, b: Int): Int {
return if (a > b) {
return a
} else {
b
}
}
// 단일 표현식 함수 (single-expression function)
// 함수가 하나의 결과값이라면 {} 대신 =을 사용하고 return을 생략할 수 있다
fun max(a: Int, b: Int): Int =
if (a > b) {
a
} else {
b
}
}
// 반환 타입 추론 (Return Type Inference)
// 분기 처리가 모두 동일한 타입을 반환하고 단일 표현식 함수인 경우, 반환 타입(Int)을 생략할 수 있다
fun max(a: Int, b: Int) = if (a > b) a else b
- 단일 표현식 함수: 함수 본인이 단일 표현식으로 구성될 경우, 중괄호 {} 대신 =을 사용하고 return 키워드를 생략할 수 있다. 자바의 람다 단일 표현식과 유사한 간결함을 제공한다
- 반환 타입 추론: 단일 표현식 함수에서 컴파일러가 반환 타입을 명확하게 추론할 수 있다면, 반환 타입 명시를 생략할 수 있다. (단, {}를 사용하는 일반 함수에서는 Unit이 아닌 경우 반환 타입을 명시해야 한다)
- 유연한 선언 위치: 자바와 달리 코틀린 함수는 클래스 안에 메서드로 존재할 수도 있지만, 파일 최상단에 직접 선언될 수도 있다. 또한 하나의 파일에 여러 함수를 선언하는 것이 가능하다
기본 인자 (Default Parameter)
- 자바는 여러 오버로딩(Overloading) 함수를 통해 매개변수의 다양한 조합을 처리한다. 이는 코드가 중복되고 번거로울 수 있다
// Java - 오버로딩으로 기본값 처리
public void repeat(String str, int num, boolean useNewLine) {
/* ... */
}
public void repeat(String str, int num) {
repeat(str, num, true);
}
public void repeat(String str) {
repeat(str, 3, true);
}
- 코틀린은 기본 인자 (Default Parameter) 기능을 제공하여, 함수 선언 시 매개변수에 기본값을 지정할 수 있다. 이를 통해 하나의 함수로 자바의 여러 오버로딩 상황을 대체하며 코드를 간결하게 만든다
// Kotlin - 기본 인자로 간결하게 처리
fun repeat(str: String, num: Int = 3, useNewLine: Boolean = true) {
for (i in 1..num) {
if (useNewLine) println(str) else print(str)
}
}
// 호출 시 기본값 사용: repeat("Hello Kotlin"), repeat("Hello Kotlin", num = 5)
Java에 기본 인자가 없는 이유
- 복잡성 증가: 기본 인자는 오버로딩, 제네릭 등과 결합 시 메서드 해석 규칙을 복잡하게 만들어 언어 명세의 복잡도를 높인다
- 바이너리 호환성: 기본값을 컴파일 시점에 처리하면 라이브러리 업데이트 시 클라이언트 코드의 재컴파일이 필요해 질 수 있다
- 가독성 저하: 이름 있는 인자(Named Argument)가 없는 자바에서 기본 인자만 도입하면, 중간 매개변수를 생략하기 어렵고 호출 코드만으로 기본값을 파악하기 어려워 가독성 문제가 발생할 수 있다
- 대안 패턴 존재: 자바는 오버로딩, 빌더 패턴, 매개변수 객체 등 명시적이로 타입 안전한 대안 패턴들을 권장한다
- 자바는 개발자의 편의성보다 예측 가능성과 명시성을 우선하여 현재와 같은 설계 방식을 유지하고 있다
이름 있는 인자 (Named Argument)
- 코틀린은 함수 호출 시 매개변수의 이름을 직접 지정하여 값을 전달할 수 있는 이름 있는 인자 (Named Argument)를 지원한다. 이는 매개변수 순서에 얽매이지 않고 가독성을 높여주며, 자바의 빌더 패턴과 유사한 장점을 제공한다. 기본 인자와 함께 사용될 때 특히 유용하다
fun printFullName(firstName: String, lastName: String) {
println("${firstName} ${lastName}")
}
// 호출 예시
printFullName("톰", "크루즈")
printFullName(lastName = "크루즈", firstName = "톰") // 이름 지정으로 순서 변경 가능
Java에 이름 있는 인자가 없는 이유
- API 계약 및 호환성 문제: 자바에서 매개변수 이름은 공식적인 API 계약의 일부가 아니며, 컴파일된 바이트코드에 기본적으로 포함되지 않는다. 이름 있는 인자를 도입하면 매개변수 이름이 API의 일부가 되어, 라이브러리 업데이트 시 이름 변경만으로도 하위 호환성 문제가 발생할 수 있다
- 메서드 해석 복잡성: 자바는 이미 오버로딩, 제네릭 등 복잡한 메서드 해석 규칙을 가지고 있다. 이름 있는 인자를 추가하면 어떤 메서드를 호출해야 할 지 모호성이 크게 증가하여 컴파일러의 해석 규칙이 기하급수적으로 복잡해진다
// Java
void draw(int width, int height) {}
void draw(int height, int width) {}
// draw(height = 10, width = 20) // 자바에서 이러한 호출은 어떤 함수를 의미하는지 모호함
- 기본 인자와의 시너지 부족: 이름 있는 인자는 기본 인자와 함께 사용될 때 그 진가가 발휘되는데, 자바는 기본 인자도 지원하지 않아 단독 도입 시 효용성이 제한적이다
- 기존 생태계 영향: 자바의 리플렉션, 바이트코드 조작 도구 등은 “매개변수 이름은 계약이 아니다”는 전제로 설계되어 있어, 이름 있는 인자를 도입하려면 광범위한 생태계 업데이트가 필요하다
같은 타입의 여러 매개변수 받기 (가변 인자)
- 함수가 동일한 타입의 인자를 개수에 상관없이 받을 때 가변 인자(Varargs)를 사용한다. 이는 인자 개수가 유동적인 상황에서 유연성을 제공한다
// Java - 가변 인자 선언
public static void printAll(String... strings) {
for (String str: strings) {
System.out.println(str);
}
}
// 호출 예시
printAll("Hello", "World", "Java"); // 여러 값 직접 전달
- 자바는 … (점 세 개)를 사용하여 가변 인자를 선언하고 호출 시에는 여러 값을 쉼표로 구분하여 직접 전달하거나, 이미 존재하는 배열을 그대로 전달할 수 있다
// Kotlin - 가변 인자 선언
fun printAll(vararg strings: String) {
for(str in strings) {
println(str)
}
}
// 호출 예시
printAll("Hello", "World", "Kotlin"); // 여러 값 직접 전달
val array = arrayOf("Hello", "World", "Kotlin")
printAll(*array) // 배열 앞에 스프레드 연산자 (*) tkdyd
가변 인자는 내부적으로 배열로 처리된다. 인수의 개수가 가변적이고 유연성이 필요할 때 유용하지만, 성능이 중요한 상황에서는 호출마다 배열이 생성될 수 있으므로 신중하게 사용하는 것이 좋다
출처 – 인프런 강의 중 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)