fun main(args: Array<String>) {
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") // 맵
println(set.javaClass) // javaClass = getClass()
println(list.javaClass)
println(map.javaClass)
}
/* <결과>
class java.util.HashSet
class java.util.ArrayList
class java.util.HashMap
*/
val strings = listOf("first", "second", "fourteenth")
println(strings.last()) // fourteenth
val numberes = setOf(1, 14, 2)
println(numbers.max()) // 14
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(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")")) // (1; 2; 3)
}
코틀린으로 작성한 함수를 호출할 때는 함수에 전달하는 인자 중 일부(또는 전부)의 이름 명시 가능
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, separator = "; ", prefix = "(", postfix = ")")) // (1; 2; 3)
}
함수 선언에서 파라미터의 디폴트 값을 지정 가능 -> 함수 호출할 때 인자 일부 생략 가능해짐
fun <T> joinToString (
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) : String
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list)) // 1, 2, 3
println(joinToString(list, "; ")) // 1; 2; 3
}
이름 붙인 인자 사용하면 지정하고 싶은 인자를 이름 붙여서 순서와 관계없이 지정 가능
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, postfix = ";", prefix = "# ")) // # 1, 2, 3;
}
[Join.kt]
package strings
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()
}
[TopLevelFunction.kt]
package ch03
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(strings.joinToString(list))
}
var opCount = 0 // 최상위 프로퍼티 선언
fun performOperation() {
opCount++ // 최상위 프로퍼티의 값을 변경
}
fun reportOperationCount() {
println("Operation performed $opCount times") // 최상위 프로퍼티 값 읽음
}
fun main(args: Array<String>) {
reportOperationCount() // Operation performed 0 times
performOperation()
reportOperationCount() // Operation performed 1 times
}
-> 최상위 프로퍼티로 코드에 상수 추가 가능
val UNIX_LINE_SEPARATOR = "\n" // 게터가 생김
const val UNIX_LINE_SEPARATOR = "\n" // == public static final 필드로 컴파일하게 함 -> 게터 생기지 x
확장 함수 : 클래스 밖에 선언되어있지만 어떤 클래스의 멤버메소드인 것처럼 호출할 수 있는 함수
수신 객체 타입 : 확장 함수를 만들기 위해 함수 이름 앞에 덧붙이는 함수가 확장할 클래스 이름 / 확장이 정의될 클래스의 타입
수신 객체 : 확장 함수가 호출되는 대상이 되는 값(객체) / 클래스에 속한 인스턴스 객체
[Extension.kt]
package strings
fun String.lastChar() : Char = this.get(this.length - 1)
[ExtensionFunction.kt]
package ch03
import strings.lastChar
fun main(args: Array<String>) {
println("Kotlin".lastChar()) // n
}
확장 함수 안에서는 클래스 내부에서만 사용 가능한 private 멤버나 protected 멤버 사용 불가
확장 함수를 사용하기 위해서는 그 함수를 임포트해야함
[ExtensionFunction.kt]
package ch03
//import strings.lastChar
// import string.*
import strings.lastChar as last
fun main(args: Array<String>) {
//println("Kotlin".lastChar()) // n
println("Kotlin".last()) // n
}
정적 메소드를 호출하면서 첫 번째 인자로 수신 객체를 넘겨야함
char c = StringUtilKt.lastChar("Java"); // StringUtil.kt 파일에 정의된 확장 함수 호출
[JoinToStringExtensionFunction.kt]
package ch03
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 Collection<String>.join (
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(list.joinToString(separator = "; ", prefix = "(", postfix = ")"))
println(listOf("one", "two", "eight").join(" ")) // one two eight
// listOf(1, 2, 8).join() : 오류. 객체의 리스트에 대해 호출할 수 x
}
확장 함수를 하위 클래스에서 오버라이드 할 수 x -> 정적 메소드와 같은 특징, 호출될 확장 함수를 정적으로 결정하기 때문에
확장 함수를 첫 번째 인자가 수신 객체인 정적 자바 메소드로 컴파일하기 때문에
[OverrideExtensionFunctionTest.kt]
package ch03
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
fun main(args: Array<String>) {
val view: View = Button()
view.click() // Button clicked
val view2: View = Button()
view2.showOff() // I'm a view! => 확장 함수는 정적으로 결정됨
}
확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API 추가 가능 -> 더 짧게 코드 작성 가능해짐
[확장 프로퍼티 선언하기]
val String.lastChar: Char
get() = get(length - 1)
[변경 가능한 확장 프로퍼티 선언하기]
var StringBuilder.lastChar: Char
get() = get(length - 1) // 프로퍼티 게터
set(value: Char) {
this.setCharAt(length - 1, value) // 프로퍼티 세터
}
[Extension.kt]
package strings
// 확장 프로퍼티 선언
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1) // 프로퍼티 게터
set(value: Char) {
this.setCharAt(length - 1, value) // 프로퍼티 세터
}
[ExtensionPeroperty.kt]
package ch03
import strings.lastChar
fun main(args: Array<String>) {
println("Kotlin".lastChar) // n
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb) // Kotlin!
}
코틀린 컬렉션이 더 확장된 API를 제공하는 방법 -> 확장 함수
[last, max 함수]
fun <T> List<T>.last() : T {
/* 마지막 원소를 반환함 */
}
fun Collection<Int>.max() : Int {
/* 컬렉션의 최댓값을 찾음 */
}
가변 길이 인자(varargs) : 메소드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 컴파일러가 배열에 그 값들을 넣어주는 기능
fun listOf<T>(vararg values: T) : List<T> { ... }
스프레드 연산자 - 배열에 들어있는 원소를 가변 길이 인자로 넘길 때 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 함
fun main(args: Array<String>) {
val list = listOf("args: ", *args)
println(list)
}
중위 호출 : 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는 것
1.to("one") // "to" 메소드를 일반적인 방식으로 호출
1 to "one" // "to" 메소드를 중위 호출 방식으로 호출
[to 함수의 정의 요약한 코드]
infix fun Any.to(other: Any) = Pair(this, other) // Pair는 두 원소로 이뤄진 순서쌍을 표현
구조 분해 선언 : 구조를 해제하여 그 값을 각각 변수에 할당하는 것
val (number, name) = 1 to "one"
for ((index, element) in collection.withIndex()) {
println("$index: $element")
}
println("12.345-6.A".split("\\.|-".toRegex())) // [12, 345, 6, A]
println("12.345-6.A".split(".", "-")) // [12, 345, 6, A]
// String 확장 함수를 사용해 경로 파싱
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")
// Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc
}
[경로를 디렉터리, 파일 이름, 확장자로 분리하는 정규식]
"""(.+)/(.+)\.(.+)"""
/ : 마지막 슬래시
\. : 마지막 점(.)
첫번째 (.+) : 마지막 슬래시까지 모든 문자
두번째 (.+) : 마지막 마침표 전까지 모든 문자
세번째 (.+) : 마지막 마치푬 후의 모든 문자
// 정규식을 사용해 경로 파싱
fun parsePath2(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path) // 정규식을 인자로 받은 path에 매치시킴
if (matchResult != null) { // 매치에 성공하면
// 매치 결과를 의미하는 destructured 프로퍼티를 구조 분해
val (directory, filename, extension) = matchResult.destructured
println("Dir: $directory, name: $filename, ext: $extension")
}
}
fun main(args: Array<String>) {
parsePath2("/Users/yole/kotlin-book/chapter.adoc")
// Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc
}
3중 따옴표 문자열 - 이스케이프를 피하기 위해서만 사용하지 x -> 줄 바꿈을 표현하는 아무 문자열이나 이스케이프 없이 그대로 들어감
[3중 템플릿 안에 문자열 템플릿 사용]
val price = """${'$'}99.9"""
println(price) // $99.9
로컬 함수로 분리하여 중복을 없애는 동시에 코드 구조를 깔끔하게 유지 가능
로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용 가능
[로컬 함수를 사용해 코드 중복 줄이기]
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException (
"Can't save user ${user.id} : $fieldName" // user.id 바깥 함수의 파라미터에 직접 접근
)
}
}
/*
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"
)
}
*/
// 로컬 함수를 호출해서 각 필드를 검증한다.
validate(user.name, "Name")
validate(user.address, "Address")
// user를 데이터베이스에 저장한다.
}
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
[로컬 함수로 구현한 검증 로직을 확장 함수로 추출]
class User2(val id: Int, val name: String, val address: String)
fun User2.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException (
"Can't save user $id : empty $fieldName" // User의 프로퍼티 직접 사용
)
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser2(user: User2) {
user.validateBeforeSave() // 확장 함수를 호출
// user를 데이터베이스에 저장한다.
}
fun main(args: Array<String>) {
saveUser2(User2(1, "", ""))
}