Kotlin in action: 함수의 정의와 호출
코틀린에서 컬렉션 만들기
코틀린에서는 컬렉션을 다음과 같은 방법으로 만들 수 있다.
val set = hashSetOf(1, 7, 53)
val list = arrayListOf(1, 7, 53)
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
val strings = listOf("first", "second", "fourteenth")
println(strings.last()) // 리스트의 마지막 원소를 가져온다.
val numbers = setOf(1, 14, 2)
println(numbers.max()) // 컬렉션에서 최댓값을 가져온다.
함수를 호출하기 쉽게 만들기
자바 컬렉션에는 컬렉션에는 디폴트 toString 구현이 들어있다.
val list = listOf(1, 2, 3)
println(list)
// [1, 2, 3]
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()
}
//join to string 사용 방법
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
println(joinToString(collection = list, separator = ";", prefix = "(", postfix = ")"))
println(joinToString(list))
println(joinToString(list, "; "))
}
이 함수는 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다. 이런 함수를 우리는 제네릭 함수라고 한다.
디폴트 값과 자바
자바에는 디폴트 파라미터 값이라는 개념이 없어서 코틀린 함수를 자바에서 호출하는 경우에는 그 코틀린 함수가 디폴트 파라미터 값을 제공하더라도 모든 인자를 명시해야 한다.
자바에서 코틀린 함수를 자주 호출해야 한다면 자바 쪽에서 좀 더 편하게 코틀린 함수를 호출하고 싶을 것이다. 그럴 때 @JvmOverloads 애노테이션을 함수에 추가할 수 있다.
이 애노테이션은 함수에 추가하면 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로 부터 파라미터를 하나씩 생략한 오버로딩한 자바 메소드를 추가해준다.
각 오버로딩한 함수는 시그니처에서 생략된 파라미터에 대해 코틀린 함수의 디폴트 파라미터 값을 사용한다.
정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티
자바에서는 모든 코드를 클래스 기반에서 작성해야 한다. 하지만 특정 도메인 영역에는 포함되기 어려운 변수나 메소드를 생성해야 할 경우가 있는데, 클래스를 하나 생성하고 그 클래스에 static 멤버로 선언하여 사용하는 것이 보통이다.
코틀린에서는 코틀린파일의 최상위 수준, 모든 다른 클래스의 밖에 선언된 메소드와 프로퍼티는 static멤버로 처리한다.
한 패키지 안에서 파일에 함수를 선언해본다고 가정하자.
코틀린도 JVM에서 실행되기 때문에, 코틀린 컴파일러는 이 파일을 컴파일할 때 코틀린 파일명으로 새로운 클래스를 정의해준다.
최상위 함수는 같은 Package에 들어있다면 따로 import된 값이 없이 마치 자신 파일에 함수 선언한 듯이 사용이 가능하다.
함수와 마찬가지로 프로퍼티도 최상위 수준에 놓일 수 있다.
함수와 마찬가지로 최상위 프로퍼티도 정적 필드에 저장된다. 기본적으로 프로퍼티도 일반적인 프로퍼티처럼 접근자 ㅁ소드를 통해 자바코드에 노출된다.
메소드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
println("Kotlin".lastChar())
// String = 수신 객체 타입
// "Kotlin" = 수신 객체
임포트와 확장 함수
import strings.lastChar // 명시적으로 사용
import strings.* // * 사용 가능
import strings.lastChar as last // as 키워드를 사용 가능
자바에서 확장 함수 호출
char c = StringUtilKt.lastChar("java");
확장 함수로 유틸리티 함수 정의
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val list = arrayListOf(1, 2, 3)
println(list.joinToString(" "))
}
확장 함수 팁
val String.lastChar: Char
get() = get(length -1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
fun main(args: Array<String>) {
println("Kotlin".lastChar)
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)
}
컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
자바 컬렉션 API 확장
코틀린 컬렉션은 자바와 같은 클래스를 사용하지만 더 확장된 API를 제공한다.
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
fun main(args: Array<String>) {
val list = listOf("one", "two", "eight")
}
값의 쌍 다루기: 중위 호출과 구조 분해 선언
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
1.to("one") // "to" 메소드를 일반적인 방식으로 호출함
1 to "one" // "to" 메소드를 중위 호출 방식으로 호출함
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
for ((index, element) in collection.withIndex()) {
println("$index: $element")
}
문자열과 정규식 다루기
문자열 나누기
println("12.345-6.A".split(".","-")) // 여러 구분 문자열을 지정한다.
[12, 345, 6, A]
정규식과 3중 따옴표로 묶은 문자열
fun parsePath(path: String) {
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")
}
fun main(args: Array<String>) {
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
fun parsePath(path: String) {
val regex = """(.+)/(.+)\\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir: $directory, name: $filename, ext: $extension")
}
}
코드 다듬기: 로컬 함수와 확장
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty Name")
}
if (user.address.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty Address")
}
// Save user to the database
}
fun saveUser(user: User) {
fun validate(user: User,
value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}
fun saveUser(user: User) {
fun validate(value: String, fieldName: String) { // user 파라미터를 중복 사용하지 않는다.
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: " + // 바깥 함수의 파라미터에 직접 접근할 수 있다.
"empty $fieldName")
}
}
validate(user.name, "Name")
validate(user.address, "Address")
// Save user to the database
}
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
}