Kotlin IN ACTION 책을 공부하면서 정리한 내용입니다.
http://www.acornpub.co.kr/book/kotlin-in-action
Kotlin in Action
이 책은 코틀린 언어를 개발한 젯브레인의 코틀린 컴파일러 개발자들이 직접 쓴 일종의 공식 서적이라 할 수 있다.
www.acornpub.co.kr
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")
// javaClass는 자바 getClass()에 해당하는 코틀린 코드다.
>> println(set.javaClass) // class java.util.HashSet
>> println(list.javaClass) // class java.util.ArrayList
>> println(map.javaClass) // class java.util.HashMap
하지만 코틀린에서는 자바보다 더 많은 기능을 쓸 수 있다.
예를 들어 리스트의 마지막 원소를 가져오거나 수로 이루어진 컬렉션에서 최댓값을 찾을 수 있다.
val strings = listOf("first", "second", "fourteenth")
println(strings.last()) // fourteenth
val numbers = setOf(1, 14, 2)
println(numbers.max()) // 14
2. 함수를 호출하기 쉽게 만들기
fun<T> joinToString(
collection: Collection<T>,
seperator: String,
prefix: String,
postfix: String
): String {
val result = StringBuilder(prefix)
for((index, element) in collection.withIndex()){
if(index > 0) result.append(seperator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
2.1. 디폴트 파라미터 값
코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있다.
fun<T> joinToString(
collection: Collection<T>,
seperator: String = ", ",
prefix: String = "",
postfix: String = ""
)
joinToString(list, ", ", "", "")
joinToString(list, "; ") // seperator를 ";"로 지정. prefix와 postfix 생략
joinToString(list, postfix="; ", prefix="# ") // #1, 2, 3;
이제 함수를 호출할 때 모든 인자를 쓸 수도 있고, 일부를 생략할 수도 있다.
3. 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티
자바는 모든 코드를 클래스의 메소드로 작성해야 한다. 그 결과 다양한 정적 메소드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메소드가 없는 클래스도 생겨난다. JDK의 collections 클래스나 Util이 이름에 들어있는 클래스가 그 예이다.
코틀린에서는 이런 무의미한 클래스가 필요없다. 대신 함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시키면 된다. 그런 함수들은 여전히 그 파일의 맨 앞에 정의된 패키지의 멤버함수이므로 다른 패키지에서 그 함수를 사용하고 싶을 때는 그 함수가 정의된 패키지를 import 해야한다.
// join.kt
package strings
fun joinToString(...): String {...}
JVM은 클래스 안에 들어있는 코드만을 컴파일 할 수 있으므로 위 파일을 컴파일하면 새로운 클래스를 정의해준다.
join.kt 파일을 컴파일한 결과와 같은 자바 클래스 파일은 아래와 같다.
/* 자바 */
package strings;
puglic class JoinKt {
public static String joinToString(...) {...}
}
최상위 프로퍼티
함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다. 이런 프로퍼티의 값은 정적 필드에 저장된다.
최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.
val UNIX_LINE_SEPERATOR ="\n"
기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메소드를 통해 자바 코드에 노출된다. (val는 게터, var는 게터와 세터가 생김) 겉으로는 상수처럼 보이는데 실제로 게터를 사용해야 한다면 자연스럽지 못하므로 더 자연스럽게 사용하려면 public static final 필드로 컴파일 해야 한다. 이를 위해 const 변경자를 추가한다.
const val UNIX_LINE_SEPERATOR = "\n"
// 자바
public static final UNIX_LINE_SEPERATOR = "\n";
3.1. 메소드를 다른 클래스에 추가: 확장 함수, 확장 프로퍼티
확장 함수(extension function)는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다.
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
fun String.lastChar(): Char = get(length - 1) // this 생략 가능
위 함수는 아래와 같이 호출한다.
println("Kotlin".lastChar()) // n
확장함수는 캡슐화를 깨지 않는다. 그러므로 클래스 내부에서만 사용할 수 있는 private이나 protected 멤버를 사용할 수는 없다.
3.2. 자바에서 확장 함수 호출
확장함수를 StringUtil.kt 파일에 정의했다면 자바에서는 다음과 같이 호출한다.
char c = StringUtilKt.lastChar("Java");
3.3. 확장함수로 유틸리티 함수 정의
fun<T> Collection<T>.joinToString( // Collectio<T>에 대한 확장함수
seperator: String = ", ", // 파라미터 디폴트 값 설정
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for((index, element) in this.withIndex()){
if(index > 0) reulst.append(seperator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
이제 joinToString을 마치 클래스의 멤버인 것처럼 호출할 수 있다.
val list = arrayListOf(1,2,3)
println(list.joinToString(" ")) // 1 2 3
만약 문자열 타입의 컬렉션에 대해서만 호출하고 싶다면 다음과 같이 작성하면 된다.
fun Collection<String>.join(
seperator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(seperator, prefix, postfix)
3.4. 확장 함수는 오버라이드 할 수 없다.
// 멤버 함수 오버라이드 하기
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!")
val view: View = Button()
view.showOff() // I'm a View!
view가 가리키는 실제 타입은 Button이지만 이 경우 view의 타입이 View이므로 무조건 View의 확장 함수가 호출된다.
3.5. 확장 프로퍼티
앞의 lastChar() 함수를 프로퍼티로 정의해보자.
val String.lastChar: Char
get() = get(length-1)
StringBuilder와 같은 프로퍼티를 정의한다면 StringBuilder의 맨 마지막 문자는 변경가능하므로 var로 만든다.
var StringBuilder.lastChar: Char
get() = get(length-1)
set(value:Char){
this.setCharAt(length-1, value)
}
println("Kotlin".lastChar) // n
val sb = StringBuilder("Kotlin?")
sb.lastChar = "!"
println(sb) // Kotlin!
4. 컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
컬렉션을 처리할 때 유용하게 사용할 수 있는 코틀린 표준 라이브러리 함수 몇가지를 소개한다.
- vararg 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있다
- 중의(infix) 함수 구문을 사용하면 인자가 하나뿐인 메소드를 간편하게 호출할 수 있다
- 구조 분해 선언(destucturing declaration)을 사용하면 복합적인 값을 분해해서 여러 변수에 나눠 담을 수 있다
4.1. 자바 컬렉션 API 확장
val strings: List<String> = listOf("first", "second", "fourteenth")
println(strings.last()) //fourteenth
val numbers: Collection<Int> = setOf(1, 14, 2)
println(numbers.max()) // 14
이 코드가 가능했던 이유는 last와 max는 모두 확장함수이기 때문이다.
fun<T> List<T>.last(): T { /* 마지막 원소 반환 */ }
fun Collection<Int>.max() : Int { /* 컬렉션의 최대값을 찾음 */ }
4.2. 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
리스트를 생성하는 함수를 호출할 때 원하는 만큼 원소를 전달할 수 있다
var 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)
println(list)
}
4.3. 값의 쌍 다루기: 중위(infix) 호출과 구조 분해 선언
Infix 함수는 두개의 변수 가운데 오는 함수를 말한다. 코틀린에서 기본적으로 정의된 Infix 함수들 중에 Pair를 만드는 to가 있다.
val map1 = mapOf(Pair(1, "one"), Pair(2, "two"))
val map2 = mapOf(1 to "one", 2 to "two")
중위 호출은 인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 함수에서 중위 호출을 사용할 수 있다. 함수를 중위 호출에 사용하고 싶으면 infix 변경자를 함수 선언 앞에 추가해야 한다.
infix fun Any.to(other: Any) = Pair(this, other)
클래스 내 infix 함수는 다음과 같이 정의할 수 있다.
class MyString {
var string = ""
infix fun add(other: String) {
this.string = this.string + other
}
}
val myString = MyString()
myString add "Hello"
myString add "World"
myString add "Kotlin"
println(myString.string) // HelloWorldKotlin
위의 to 중의 호출 메소드는 Pair 쌍의 내용으로 두 변수를 즉시 초기화 할 수 있다. 이런 기능을 구조 분해 선언(destucturing declaration)이라고 부른다.
val(number, name) = 1 to "one"
루프에서도 구조 분해 선언을 사용할 수 있다. joinToString에서 봤듯이 withIndex를 구조 분해 선언과 조합하면 컬렉션 원소의 인덱스와 값을 따로 변수에 담을 수 있다.
for((index, element) in collection.withIndex()){
println("$index: $element")
}
'kotlin > Kotlin In Action' 카테고리의 다른 글
4. 람다로 프로그래밍 (0) | 2021.06.17 |
---|---|
3. 클래스, 객체, 인터페이스 (0) | 2021.06.14 |
1. 코틀린 기초 (0) | 2021.06.10 |
댓글