[Kotlin in Action] 03장 - 함수 정의와 호출
3장 함수 정의와 호출
3.1 코틀린에서 컬렉션 만들기
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 = (“first”, “second”)
- strings.last()
3.2 함수를 호출하기 쉽게 만들기
3.2.1 이름 붙인 인자
joinToString(collection, separator= " ", prefix = " ", postfix = ".")
- 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
3.2.2 디폴트 파라미터 값
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String
// 함수 호출 시 모든 인자 쓸 수도 있고, 일부 생략도 가능
joinToString(list, ", ", "", "")
joinToString(list)
joinToString(list, "; ")
// 이름 붙은 인자를 사용하는 경우 지정하고 싶은 인자를 이름을 붙여서 순서와 관계없이 지정할 수 있다.
joinToString(list, postfix = ";", prefix = "# ")
fun max(a: Int, b:Int): Int {
return if (a > b) a else b
}
3.2.3 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티
최상위 함수
- 한 클래스에 포함시키기 어려운 코드
- 비슷하게 중요한 역할을 하는 클래스가 둘 이상
- 최상위 함수가 들어있던 코틀린 소스 파일의 이름과 대응
// join.kt 파일 작성
package strings
fun joinToString(...): String { ... }
JoinKt.joinToString(list, ", ", "", "")
최상위 프로퍼티
- 최상위 프로퍼티의 값은 정적 필드에 저장
- 코드에 상수 추가
var opCount = 0
fun performOperation() {
opCount++ // 최상위 프로퍼티 값 변경
}
fun reportOperationCount() {
println("Operation performed $opCount times") // 최상위 프로퍼티 값을 읽는다.
}
- 상수로 사용
- val UNIX_LINE_SEPARATOR = “\n”
- 게터 생성 됨
- const val UNIX_LINE_SEPARATOR = “\n”
- public static final 필드로 컴파일 됨
- 원시타입과 String 타입의 프로퍼티만 const 지정 가능
- val UNIX_LINE_SEPARATOR = “\n”
3.3 메소드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티
- 확장 함수
- 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수
package strings
fun String.lastChar():Char = this.get(this.length - 1)
println("Kotlin".lastChar())
- String 클래스에 새로운 메서드를 추가하는 것과 같음
3.3.1 임포트와 확장 함수
- 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 한다.
import strings.lastChar
val c = "Kotlin".lastChar()
// *를 사용한 임포트
import stirngs.*
// as 키워드를 사용하면 다른 이름으로 변경 가능
import strings.lastChar as last
val c = "Kotlin".last()
3.3.2 자바에서 확장 함수 호출
- 정적 메소드를 호출하면서 첫 번째 인자로 수신 객체를 넘기면 됨
- char c = StringUtilKt.lastChar(“Java”)
3.3.3 확장 함수로 유틸리티 함수 정의
- 확장 함수는 정적 메소드 호출에 대한 문법적인 편의일 뿐
- 클래스가 아닌 구체적인 타입을 수신 객체 타입으로 지정 가능
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
listOf("one", "two", "eight").join(" ")
listOf(1, 2, 8).join(" ")
=> Error: Type mismatch: inferred type is List<Int> but Collection<String>
확장 함수는 오버라이드할 수 없다
멤버 함수 오버라이드
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
val view: View = Button()
view.click()
==> Button clicked
확장 함수는 오버라이드 할 수 없다
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
val view: View = Button()
view.showOff()
==> I'm a view!
- view의 타입이 View이기 때문에 무조건 View의 확장 함수가 호출된다.
- 호출될 확장 함수를 정적으로 결정 되기 때문
3.3.5 확장 프로퍼티
val String.lastChar: Char
get() = get(length -1) // 프로퍼티 게터
set(value: Char) {
this.setCharAt(length - 1, value) // 프로퍼티 세터
}
"Kotlin".lastChar
val sb = StringBuilder("Kotlin")
sb.lastChar = '!'
3.4 컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
- 코틀린 언어 특성
- vararg 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수 정의 가능
- 중위(infix) 함수 호출 구문 사용하면 인자가 하나뿐인 메소드를 간편하게 호출 가능
- 구조 분해 선언(destructuring declaration)을 사용하면 복합적인 값을 분해해서 여러 번수에 나눠 담을 수 있다.
3.4.1 자바 컬렉션 API 확장
val strings: List<String> = listOf("first", "second")
strings.last()
fun <T> List<T>.last(): T { /* 마지막 원소를 반환함 */ }
- last는 확장 함수로 작성 됨
3.4.2 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
val list = listOf(2, 3, 5, 7, 11)
fun listOf<T>(vararg values: T) : List<T> { ... }
배열에 들어있는 원소를 가변 길이 인자로 넘길 때
fun main(args: Array<String>) {
val list = listOf("args: ", *args) // 스프레드 연산자가 배열의 내용을 펼쳐준다.
}
3.4.3 값의 쌍 다루기: 중위 호출과 구조 분해 선언
중위 호출
- 중위 호출 시에는 수신 객체와 유일한 메서드 인자 사이에 메소드 이름을 넣는다.
- val map = mapOf(1 to “one”, 7 to “seven)
- 다음 두 호출은 동일
- 1.to(“one”) // 일반적인 방식으로 호출
- 1 to “one” // 중위 호출 방식으로 호출
- 인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 함수에 중위 호출 사용 가능
- 함수를 중위 호출에 사용하게 허용하고 싶으면 infix 변경자를 함수 선언 앞에 추가해야 한다
- to 함수 정의
- infix fun Any.to(other: Any) = Pair(this, other)
- to 함수 정의
구조 분해 선언
- Pair -> 두 원소로 이뤄진 순서쌍 표현
- Pair 의 내용으로 두 변수 즉시 초기화 가능
- val (number, name) = 1 to “one”
- 컬렉션의 구조 분해 선언과 조합
- for ((index, element) in collection.withIndex()) {
- println(“$index: $element”)
- }
- for ((index, element) in collection.withIndex()) {
3.5 문자열과 정규식 다루기
3.5.1 문자열 나누기
"12.345-6.A".split("\\.|-".toRegex()) // 정규식을 명시적으로 만든다.
"12.345-6.A".split(".", "-") // 여러 구분 문자열을 지정한다.
- 코틀린 확장 함수는 자바에 있는 단 하나의 문자만 받을 수 있는 메소드를 대신한다.
3.5.2 정규식과 3중 따옴표로 묶은 문자열
경로 파싱에 정규식 사용하기
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")
}
}
parsePath("/Users/yole/kotlin-book/chapter.adoc")
=> Dir: /Users/yole/kotlin-book, name: chapter, ext:adoc
- 3중 따옴표 문자열에서는 역슬래시()를 포함한 어떤 문자도 이스케이프할 필요가 없다.
3.5.3 여러 줄 3중 따옴표 문자열
- 3중 따옴표 문자열에는 줄 바꿈을 표현하는 아무 문자열이나 그대로 들어간다.
ASCII 아트(글자만 사용해 그린 그림) 출력
val kotlinLogo = """| //
.| //
.|/ \"""
println(kotlinLogo.trimMargin("."))
=>
| //
| //
| / \
3.6 코드 다듬기: 로컬 함수와 확장
코드 중복을 보여주는 예제
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if(user.name.isEmpty()) {
throw IllegalArgumentException(
"Cant't save user ${user.id}: empty Name")
)
}
if(user.address.isEmpty()) {
throw IllegalArgumentException(
"Cant't save user ${user.id}: empty Address")
)
}
}
로컬 함수에서 바깥 함수의 파라미터 접근하기
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fileName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
"Cant't save user ${user.id}: empty $fieldName") // 바깥 함수의 파라미터에 직접 접근 가능
)
}
}
validate(user.name, "Name")
validate(user.address, "Address")
}
검증 로직을 확장 함수로 추출하기
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fileName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
"Cant't save user ${user.id}: empty $fieldName") // 바깥 함수의 파라미터에 직접 접근 가능
)
}
}
validate(user.name, "Name")
validate(user.address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave() // 확장 함수 호출
}
- 검증 로직은 User를 사용하는 다른 곳에서는 쓰이지 않는 기능이기 때문에 User에 포함시키지 않는다.