[Kotlin In Action] 3. 함수 정의와 호출
#1. 함수 호출
fun <T> joinToString(
collection: Collection<T>,
separator: String,
prefix: String,
postfix: String
): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
이름 붙인 인자
- 자바
joinToString(collection, /* separator */ " ", /* prefix */ " ",
/* postfix */ ".");
- 코틀린
joinToString(collection, separator = " ", prefix = " ", postfix = ".")
함수에 전달하는 인자 중 일부(또는 전부)의 이름을 명시할 수 있다.
호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
자바로 작성한 코드를 호출할 때는 이름 붙인 인자를 사용할 수 없다. (코틀린은 JDK 6와 호환된다.)
디폴트 파라미터 값
코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로, 오버로드를 줄일 수 있다.
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): Strin
코틀린 함수를 자바에서 호출하는 경우에는 그 코틀린 함수가 디폴트 파라미터 값을 제공하더라도 모든 인자를 명시해야 한다.
@JvmOverloads를 함수에 추가하면 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 자바 메서드를 추가해준다.
최상위 함수
함수를 직접 소스 파일의 최상위 수준, 클래스 밖에 위치시킬 수 있다.
JVM이 클래스 안에 들어있는 코드만을 실행할 수 있기 때문에, 컴파일할 때 새로운 클래스를 정의해준다.
package strings;
public class JoinKt { //join.kt -> 코틀린 소스 파일 이름과 자바 클래스 이름이 대응된다.
public static String joinToString(...) { ... }
}
@file:JvmName(“StringFunctions”): 클래스 이름을 지정하는 애노테이션
최상위 프로퍼티
프로퍼티도 최상위 수준에 놓을 수 있다.
-> 정적 필드에 저장된다.
var opCount = 0
fun performOperation() {
opCount++
// ...
}
fun reportOperationCount() {
println("Operation performed $opCount times")
}
최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.
val UNIX_LINE_SEPARATOR = "\n"
최상위 프로퍼티도 다른 프로퍼티처럼 접근자 메서드를 통해 자바 코드에 노출된다.
const
변경자를 추가하면 프로퍼티를 public static final
필드로 컴파일하게 만들 수 있다.
(primitive 타입과 String 타입만 const로 지정할 수 있다.)
#2. 확장 함수와 확장 프로퍼티
확장 함수
확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만, 그 클래스의 밖에 선언된 함수다.
- 수신 객체 타입(receiver type): 클래스 이름
- 수신 객체(receiver object): 확장 함수가 호출되는 대상이 되는 값(객체)
확장 함수를 사용하기 위해서는 import해야 한다.
import strings.lastChar
val c = "Kotlin".lastChar()
import strings.lastChar as last //as 키워드를 사용하면 다른 이름으로 부를 수 있다.
val c = "Kotlin".last()
- 자바에서 확장 함수 호출 내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드이다.
char c = StringUtilKt.lastChar("Java");
확장 함수는 오버라이드할 수 없다.
수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정된다.
* 클래스의 멤버 함수 중 확장 함수와 이름 및 시그니처가 같은 함수가 있다면, 멤버 함수가 호출된다.
* 멤버 함수의 우선순위가 더 높다.
확장 프로퍼티
val String.lastChar: Char
get() = get(length - 1)
뒷받침하는 필드(Backing field)가 없어서 기본 Getter 구현을 제공할 수 없으므로, 최소한 Getter는 꼭 정의해야 한다.
초기화 코드에서 계산한 값을 담을 장소가 없으므로, 초기화 코드도 쓸 수 없다.
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value) //실제로 확장 프로퍼티는 아무 상태도 가질 수 없다.
}
#3. 가변 인자 함수
vararg
키워드 사용
fun listOf<T>(vararg values: T): List<T> { ... }
코틀린에서는 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 해야 한다.
fun main(args: Array<String>) {
val list = listOf("args: ", *args) //스프레드 연산자(*)를 붙이면 배열의 내용을 펼쳐준다.
println(list)
}
#4. 값의 쌍
중위 호출
인자가 하나뿐인 메서드나 확장 함수에 중위 호출을 사용할 수 있다.
수신 객체와 유일한 메서드 인자 사이에 메서드 이름을 넣어 호출한다.
infix
를 함수 선언 앞에 붙여서, 중위 호출을 허용할 수 있다.
infix fun Any.to(other: Any) = Pair(this, other)
1.to("one") //일반적인 방식의 호출
1 to "one" //중위 호출
구조 분해 선언
val (number, name) = 1 to "one" //두 변수를 즉시 초기화할 수 있다.
루프에서도 구조 분해 선언을 활용할 수 있다.
for ((index, element) in collection.withIndex()) {
println("$index: $element")
}
#5. 문자열
3중 따옴표 ("""
)
3중 따옴표 안에서는 역슬래시를 포함한 어떤 문자도 이스케이프할 필요가 없다.
줄바꿈을 표현하는 아무 문자열이나 그대로 들어간다.
#6. 로컬 함수
함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user $id: empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave()
// Save user to the database
}
한 객체만을 다루면서 객체의 비공개 데이터를 다룰 필요는 없는 함수는 확장 함수로 만들면 객체.멤버
처럼 수신 객체를 지정하지 않고도 공개된 멤버 프로퍼티나 메서드에 접근할 수 있다.
Comments