> [1,2,3] ``` - 디폴드 구현과 달리 (1;2;3) 처럼 원소"> > [1,2,3] ``` - 디폴드 구현과 달리 (1;2;3) 처럼 원소"> > [1,2,3] ``` - 디폴드 구현과 달리 (1;2;3) 처럼 원소">
# 3장 함수 정의와 호출

### 이번장에서는...

#### 배운점, 느낀점

1. 컬렉션, 문자열, 정규식을 다루기 위한 함수
2. 이름을 붙인 인자, 디폴트 파라미터 값, 중위 호출 문법 사용
3. 확장 함수와 확장 프로퍼티를 사용해 자바 라이브러리 적용
4. 최상위 및 로컬 함수와 프로퍼티를 사용해 코드 구조화

---

### 코틀린에서 컬렉션 만들기

```kotlin
package part3

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") // to가 언어가 제공하는 특별한 키워드가 아니라 일반 함수라는 점에 유의

val strings = listOf("first", "second", "fourteenth")

fun main() {
    println(set.javaClass)
    println(list.javaClass)
    println(map.javaClass)

    println(strings.last())
    println(set.max())
}

함수를 호출하기 쉽게 만들

val list = listOf(1, 2, 3)
println(list)
// >> [1,2,3]
package part3

import java.lang.StringBuilder

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()
}

fun main() {
    val list = listOf(1, 2, 3)
    println(joinToString(list, ";", "(", ")"))
}
이름을 붙인 인자
fun main() {
    val list = listOf(1, 2, 3)
    println(joinToString(collection = list, separator = ";", prefix = "(", postfix = ")"))
}

디폴트 파라미터 값

fun <T> joinToString(  // 이 함수는 제네릭 하다 즉, 이 함수는 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다.
    collection: Collection<T>,
    separator: String = ",",
    prefix: String = "",
    postfix: String = ""
): String
디폴트 값과 자바
package part3;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import kotlin.Metadata;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.JvmOverloads;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
        mv = {1, 7, 1},
        k = 2,
        d1 = {"\\u0000\\u0018\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0002\\b\\u0002\\n\\u0002\\u0010\\u001e\\n\\u0002\\b\\u0004\\n\\u0002\\u0010\\u0002\\n\\u0000\\u001a:\\u0010\\u0000\\u001a\\u00020\\u0001\\"\\u0004\\b\\u0000\\u0010\\u00022\\f\\u0010\\u0003\\u001a\\b\\u0012\\u0004\\u0012\\u0002H\\u00020\\u00042\\b\\b\\u0002\\u0010\\u0005\\u001a\\u00020\\u00012\\b\\b\\u0002\\u0010\\u0006\\u001a\\u00020\\u00012\\b\\b\\u0002\\u0010\\u0007\\u001a\\u00020\\u0001H\\u0007\\u001a\\u0006\\u0010\\b\\u001a\\u00020\\t¨\\u0006\\n"},
        d2 = {"joinToString", "", "T", "collection", "", "separator", "prefix", "postfix", "main", "", "playground.main"}
)
public final class TwoKt {
    @JvmOverloads
    @NotNull
    public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix, @NotNull String postfix) {
        Intrinsics.checkNotNullParameter(collection, "collection");
        Intrinsics.checkNotNullParameter(separator, "separator");
        Intrinsics.checkNotNullParameter(prefix, "prefix");
        Intrinsics.checkNotNullParameter(postfix, "postfix");
        StringBuilder result = new StringBuilder(prefix);
        int index = 0;

        for (Iterator var7 = ((Iterable) collection).iterator(); var7.hasNext(); ++index) {
            Object element = var7.next();
            if (index > 0) {
                result.append(separator);
            }

            result.append(element);
        }

        result.append(postfix);
        String var10000 = result.toString();
        Intrinsics.checkNotNullExpressionValue(var10000, "result.toString()");
        return var10000;
    }

    // $FF: synthetic method
    public static String joinToString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
        if ((var4 & 2) != 0) {
            var1 = ",";
        }

        if ((var4 & 4) != 0) {
            var2 = "";
        }

        if ((var4 & 8) != 0) {
            var3 = "";
        }

        return joinToString(var0, var1, var2, var3);
    }

    @JvmOverloads
    @NotNull
    public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) {
        return joinToString$default(collection, separator, prefix, (String) null, 8, (Object) null);
    }

    @JvmOverloads
    @NotNull
    public static final String joinToString(@NotNull Collection collection, @NotNull String separator) {
        return joinToString$default(collection, separator, (String) null, (String) null, 12, (Object) null);
    }

    @JvmOverloads
    @NotNull
    public static final String joinToString(@NotNull Collection collection) {
        return joinToString$default(collection, (String) null, (String) null, (String) null, 14, (Object) null);
    }

    public static final void main() {
        List list = CollectionsKt.listOf(new Integer[]{1, 2, 3});
        String var1 = joinToString((Collection) list, ";", "(", ")");
        System.out.println(var1);
        var1 = joinToString((Collection) list, ",", "", "");
        System.out.println(var1);
        var1 = joinToString$default((Collection) list, (String) null, (String) null, (String) null, 14, (Object) null);
        System.out.println(var1);
        var1 = joinToString$default((Collection) list, ";", (String) null, (String) null, 12, (Object) null);
        System.out.println(var1);
        Collection var10000 = (Collection) list;
        var1 = "#";
        String var2 = ";";
        var1 = joinToString$default(var10000, (String) null, var1, var2, 2, (Object) null);
        System.out.println(var1);
    }

    // $FF: synthetic method
    public static void main(String[] var0) {
        main();
    }
}

정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티

// 파일 이름 join
package part3.strings

import java.lang.StringBuilder

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()
}

이를 자바로 변환하면

package part3.strings;

import java.util.Collection;
import java.util.Iterator;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
        mv = {1, 7, 1},
        k = 2,
        d1 = {"\\u0000\\u0012\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0002\\b\\u0002\\n\\u0002\\u0010\\u001e\\n\\u0002\\b\\u0004\\u001a8\\u0010\\u0000\\u001a\\u00020\\u0001\\"\\u0004\\b\\u0000\\u0010\\u00022\\f\\u0010\\u0003\\u001a\\b\\u0012\\u0004\\u0012\\u0002H\\u00020\\u00042\\b\\b\\u0002\\u0010\\u0005\\u001a\\u00020\\u00012\\b\\b\\u0002\\u0010\\u0006\\u001a\\u00020\\u00012\\b\\b\\u0002\\u0010\\u0007\\u001a\\u00020\\u0001¨\\u0006\\b"},
        d2 = {"joinToString", "", "T", "collection", "", "separator", "prefix", "postfix", "playground.main"}
)
public final class JoinKt {

}
package part3;

import part3.strings.JoinKt;

import java.util.Arrays;
import java.util.List;

public class CallJoin {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        System.out.println(JoinKt.joinToString(list, ",", "", ""));
    }
}
@JvmName
@file:JvmName("StringFunctions")

package part3.strings

import java.lang.StringBuilder

fun <T> joinToString(  // 이 함수는 제네릭 하다 즉, 이 함수는 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다.
    collection: Collection<T>,
    separator: String = ",",
    prefix: String = "",
    postfix: String = ""
): String
최상위 프로퍼티
const val UNIX_LUNE_SEPARATOR = "\\n" // 최상위 프로퍼티
public static final String UNIX_LUNE_SEPARATOR="\\n";

메서드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티

package part3

fun String.lastChar(): Char = this.get(this.length - 1) // this -> 수신 객체
// String -> 수신 객체 타입

println("Kotlin".lastChar())
fun String.lastChar(): Char = get(length - 1) // 수신 객체 멤버에 this 없이 접근할 수 있다.
임포트와 확장 함수
import strings.lastChar

import strings.*

import strings.lastChar as last

val c = "Kotlin".last()
자바에서 확장 함수 호출
/* 자바 */
char c=StringUtilKt.lastChar("Java");
확장 함수 유틸리티 함수 정의
package part3.strings

fun <T> Collection<T>.joinToString(  // Collection<T>에 대한 확장 함수를 선언한다.
    separator: String = ",", // 파라미터의 디폴트 값을 지정한다.
    prefix: String = "",
    postfix: String = ""
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) { //this 는 수신 객체를 가리킨다. 여기서는 T 타입의 원소로 이뤄진 컬렉션이다.
        if (index > 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

fun Collection<String>.join(
    separator: String = ",",
    prefix: String = "",
    postfix: String = ""
) = joinToString(separator, prefix, postfix)

fun main() {
    val list = listOf(1, 2, 3)
    println(list.joinToString(separator = ";", prefix = "(", postfix = ")"))

    println(listOf("one", "two", "eight").join(" "))
}
확장 함수는 오버리아드 할 수 없다.
package part3

open class View {
    open fun click() = println("View clicked")
}

class Button : View() {
    override fun click() = println("Button clicked")
}

fun main() {
    val view: View = Button()
    view.click() // view에 저장된 값의 실제 타입에 따라 호출할 메서드가 결정된다.
}

// >> Button clicked
package part3

fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")

fun main() {
    val view: View = Button()
    println(view.showOff())
}

// >>I'm a view!

확장 프로퍼티

val String.lastChar: Char
    get() = get(length - 1)

var StringBuilder.last: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }

컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원

자바 컬렉션 API 확장
package part3

fun main() {
    val strings: List<String> = listOf("first", "second", "fourteenth")
    val numbers: Collection<Int> = setOf(1, 14, 2)

    println(strings.last())
    println(numbers.max())
}
가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
val list = listOf(2, 3, 5, 7, 11)

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("args: ", *args) // 스프레드 연산자가 배열의 내용을 펼쳐준다
    println(list)
}
값의 쌍 다루기: 중위 호출과 구조 분해 선언
package part3

fun main(args: Array<String>) {
    val map = mapOf(1 to "one", 2 to "two", 3 to "three")
}
1.to("one")  // to 메서드를 일반적인 방식으로 호출함
1 to "one"  // to 메서드를 중위 호풀 방식으로 호출함

val list = listOf(1, 2, 3)
mapOf(1 to "one", "one" to 1, list to list.size())
package part3

infix fun Any.to(other: Any) = Pair(this, other)
// 이 to 함수는 Pair의 인스턴스를 반환한다.
// Pair는 코틀린 표준 라이브러리 클래스로, 그 이름대로 두 원소로 이뤄진 순서쌍을 표현한다
// 실제로 to는 제네릭 함수

fun main() {
    val (number, name) = 1 to "one"

    println("$number : $name")
}
package part3

fun main() {
    val collection = listOf(1, 2, 3, 4, 5)
    for ((index, element) in collection.withIndex()) {
        println("$index : $element")
    }
}

문자열과 정규식 다루기

문자열 나누기
package part3

fun main(args: Array<String>) {
    println("12.345-6.A".split("\\\\.|-".toRegex()))
    println("12.345-6.A".split(".", "-"))
}
정규식과 3중 따옴표로 묶은 문자열, 여러 줄 3중 따옴표 문자열
package part3

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 parsePathByRegex(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 main(args: Array<String>) {
    parsePath("/User/yole/kotlin-book/chapter.adoc")
    parsePathByRegex("/User/yole/kotlin-book/chapter.adoc")

    val kotlinLogo = """| //
                       .| //
                       .|/ \\ """

    println(kotlinLogo.trimMargin("."))
}

val price = """${'$'}99.9"""

코드 다듬기: 로컬 함수와 확장

코드 중복을 보여주는 예제
package part3

class User(
    val id: Int,
    val name: String,
    val address: String
)

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.address}: empty Address")
    }
    // user를 데이터베이스에 저장한다.
}

fun main(args: Array<String>) {
    saveUser(User(1, "", ""))
}
로컬 함수를 사용해 코드 중복 줄이기
package part3

class User(
    val id: Int,
    val name: String,
    val address: String
)

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")
    // user를 데이터베이스에 저장한다.
}

fun main(args: Array<String>) {
    saveUser(User(1, "", ""))
}
로컬 함수에서 바깥 함수의 파라미터 접근하기
package part3

class User(
    val id: Int,
    val name: String,
    val address: String
)

fun saveUser(user: User) {

    fun validate(
        value: String, // 이제 saveUser 함수의 user 파라미터를 중복 사용하지않는다
        fieldName: String
    ) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty $fieldName") // 바깥 함수의 파라미터에 접근할 수 있다.
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
    // user를 데이터베이스에 저장한다.
}

fun main(args: Array<String>) {
    saveUser(User(1, "", ""))
}
검증 로직을 확장 함수로 추출하기
package part3

class User(
    val id: Int,
    val name: String,
    val address: String
)

fun User.validateBeforeSave() {

    fun validate(
        value: String, // 이제 saveUser 함수의 user 파라미터를 중복 사용하지않는다
        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()
    // user를 데이터베이스에 저장한다.
}

fun main(args: Array<String>) {
    saveUser(User(1, "", ""))
}

[출처] Kotlin IN ACTION