유용한 코틀린 표준 라이브러리에 대해 알아본다.
이전 글)
Pair 클래스
Pair 클래스는 두 개의 변수를 하나로 묶는다.
data class Pair<out A, out B>(
val first: A,
val second: B
) : Serializable
fun divide(a: Int, b: Int) : Pair<Int, Int> = Pair(a / b, a % b)
fun main(args: Array<String>) {
val (q, r) = divide(10, 3)
println("몫: $q, 나머지: $r") //몫: 3, 나머지: 1
}
Triple 클래스
Triple 클래스는 세 개의 변수를 하나로 묶는다.
data class Triple<out A, out B, out C>(
val first: A,
val second: B,
val third: C
) : Serializable
fun calculateCircle(radius: Int) : Triple<Int, Double, Double> =
Triple(radius * 2, radius * 2 * 3.14, radius * radius * 3.14)
fun main(args: Array<String>) {
val (diameter, _, area) = calculateCircle(5)
println("지름: $diameter, 넓이: $area")
}
모든 타입의 to 확장 함수
infix fun <A, B> A.to(that: B) : Pair<A, B> = Pair(this, that)
fun main(args: Array<String>) {
val test1: Pair<Int, Double> = 10.to(3.5)
val test2: Pair<Int, Double> = 10 to 3.5
}
infix로 선언되어 연산자처럼 사용할 수 있다.
Comparable 인터페이스
Comparable 인터페이스를 구현하는 클래스는 비교 연산자를 지원한다. (>, <, >=, <=)
interface Comparable<in T> {
operator fun compareTo(other: T) : Int
}
operator fun <T: Comparable<T>> T.rangeTo(that: T) : ClosedRange<T>
rangeTo 확장함수가 정의되어 있어 .. 연산자를 지원한다.
ClosedRange 인터페이스
닫힌 구간을 표현하는 인터페이스다.
interface ClosedRange<T: Comparable<T>> {
val start: T
val endInclusive: T
operator fun contains(value: T) : Boolean = value >= start && value <= endInclusice
fun isEmpty() : Boolean = start > endInclusive
}
contains는 in 연산자를 지원하는 역할을 한다.
원래는 IntRange, LongRange, CharRange 클래스만 있고 실수 타입에 대한 Range 클래스는 없었으나 코틀린 1.1부터 제공하고 있다. 그러나 정수 타입에 대한 Range 클래스와 다르게 iterator 연산자 멤버 함수를 가지고 있지 않기 때문에 for 문의 in 연산자에 사용할 수는 없다.
Interable 인터페이스, Progression 인터페이스
for문의 in 연산자에 쓸 수 있도록 하는 인터페이스다.
interface Iterable<out T> {
operator fun iterator() : Iterator<T>
}
IntRange는 IntProgression을 구현하고, IntProgression은 Iterable<Int>를 상속한다. 이 덕에 IntRange의 인스턴스를 for 문의 in 연산자에 사용할 수 있다.
open class IntProgression {
val first: Int //진행의 시작값
val last: Int //진행의 끝값
val step: Int //next를 호출할 때 몇 칸씩 건너뛸 것인지
}
Progression은 큰 수에서 작은 수로 진행 할 수 있다.
Progression 함수
코틀린에서 제공하는 확장함수를 이용하여 Progression 타입들을 제대로 활용해보자.
fun main(args: Array<String>) {
val p1: IntProgression = 7 downTo 3
// 7 6 5 4 3
val p2: IntProgression = (3..7).reversed()
// 7 6 5 4 3
val p3: IntProgression = (1..10) step 3
// 1 4 7 10
val p4: IntProgression = 10 downTo 2 step 3
// 10 7 4
val p5: IntProgression = 2 until 5
// 2 3 4
}
7 downTo 3 대신에 7..3을 사용하면 되지 않을까? 안 된다.
.. 연산자는 작은값..큰값 으로밖에 사용하지 못한다. 왼쪽 피연산자가 오른쪽 피연산자보다 크면 비어있는 구간으로 인식된다. (주의!)
step 확장함수는 원래의 Progression에 step 프로퍼티값만 바꿔주는 역할을 한다.
점점 감소하는 진행을 나타낸다고 step에 -3을 지정하면 안 된다. step 값은 항상 양수여야 한다. 0이나 음수를 전달하면 IllegalArgumentException이 발생한다.
컬렉션 Collection
val list: Collection<Int> = listOf(1, 2, 3)
val set: Collection<Int> = setOf(1, 2, 3, 1, 2)
val map: Map<String, String> = mapOf("Apple" to "사과", "Banana" to "바나나")
ListIterator 인터페이스
List 인터페이스의 listIterator() 멤버함수는 List에 특화된 Iterator인 ListIterator를 반환한다.
- hasPrevious() : Boolean
- previous() : T - 이전 원소를 반환하고, ListIterator의 커서를 뒤로 한 칸 옮긴다.
- nextIndex() : Int
- previousIndex() : Int
변경할 수 있는 컬렉션 MutableCollection
코틀린의 컬렉션은 기본적으로 수정이 불가능하다. 덕분에 Thread-Safe한 코드를 만들 수 있다.
컬렉션의 원소를 수정하려면 MutableCollection 인터페이스를 구현하는 컬렉션을 이용해야 한다.
- removeAll(elements: Collection<E>) : Boolean
elements 컬렉션의 원소들과 일치하는 모든 원소를 제거한다. 하나라도 제거되면 true를 반환한다. - retainAll(elemetns: Collection<E>) : Boolean
elements 컬렉션의 원소들과 일치하는 원소만 남기도 모두 제거한다. 하나라도 제거되면 true를 반환한다.
MutableList 인터페이스
val list: MutableList<Char> = mutableListOf('a', 'b', 'c')
list.set(2, 'd')
list[3] = 'e'
println(list) //[a, b, d, e]
Sequence 인터페이스
Sequence도 List처럼 일련의 데이터를 표현하는 인터페이스지만, Sequence는 각 데이터를 Lazy하게 계산하여(필요한 순간에 계산하여) 잠재적으로 무한개의 데이터를 다룰 수 있다. 따라서 크기가 정해져 있지 않다.
컬렉션의 원소 타입 변환하기 map
코틀린의 map 계열 확장함수를 이용하면 Collection<T> 타입을 Collection<R> 타입으로 변환할 수 있다.
fun main(args: Array<String>) {
val origin = listOf(65, 66, 67, 68, 69)
val m = origin.map { it.toChar() }
val mi = origin.mapIndexed { index, element ->
"$(element.toChar())$index"
}
val mnn = origin.mapNotNull {
if (it % 2 == 1) it
else null
}
println(origin) //[65, 66, 67, 68, 69]
println(m) //[A, B, C, D, E]
println(mi) //[A0, B1, C2, D3, E4]
println(mnn) //[65, 67, 69]
}
컬렉션 중 원하는 원소만 걸러내기 filter
fun main(args: Array<String>) {
val origin = 1..10
val f = origin.filter { it % 2 == 1 }
val fn = origin.filterNot { it % 2 == 1 }
val fnn = origin.filterNotNull()
val fi = origin.filterIndexed { index, element -> element + index > 5 }
val fii = origin.filterIsInstance<Long>()
println(f) //[1, 3, 5, 7, 9]
println(fn) //[2, 4, 6, 8, 10]
println(fnn) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
println(fi) //[4, 5, 6, 7, 8, 9, 10]
println(fii) //[]
}
컬렉션 정렬하기 sorted
fun main(args: Array<String>) {
val origin = listOf(5, 10, 2, 4, 3, 1, 7, 6, 8, 9)
println(origin.sorted()) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
println(origin.sortedDescending()) //[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
}
그 외 컬렉션 확장 함수
구분 | 확장함수 |
컬렉션 내의 모든 원소 검사하기 | all, any, none |
원소 타입 변환하기 | map, mapIndexed, mapIndexedNotNull, mapIndexedNotNullTo, mapIndexedTo, mapNotNull, mapNotNullTo, mapTo |
원소 중 최대/최소값 찾기 | max, maxBy, maxWith, min, minBy, minWith |
컬렉션 내의 원소들 통합하기 | sum, sumBy, average, count, joinToString |
컬렉션 가공하기 | distinct, distinctBy, drop, dropWhile, take, takeWhile, groupBy |
컬렉션에 필터 적용하기 | filter, filterIndexed, filterIndexedTo, filterIsInstance, filterIsInstanceTo, filterNot, filterNotNull, filterNotNullTo, filterNotTo, filterTo |
컬렉션 정렬하기 | sorted, sortedDescending |
특정 원소 가져오기 | find, findLast, first, firstOrNull, last, lastOrNull, elementAtOrElse, elementAtOrNull, singleOrNull |
원소의 인덱스 가져오기 | indexOf, indexOfFirst, indexOfLast, lastIndexOf |
각 원소에 특정 작업 수행하기 | forEach, forEachIndexed, onEach |
컬렉션 연산자 확장함수 | plus, minus |
CharSequence 인터페이스
CharSequence는 문자열과 관련된 클래스가 구현하는 뼈대 인터페이스다.
참고로 CharSequence는 Sequence를 상속하지 않는다.
fun main(args: Array<String>) {
val seq: CharSequence = "Hello"
println(seq.length) //5
println(seq[2]) //l
println(seq.subSequence(1, 4)) //ell
}
CharSequence를 리시버로 하는 수 많은 확장멤버가 존재하기 때문에 활용도가 높다.
예를 들면, startsWith, endsWith, removePrefix, removeSuffix, removeSurrounding, isEmpty, isBlank, removeRange, padStart, trimStart, slice, subSequence, substring, reversed 등이 있다.
정규식 다루기 Regex
fun main(args: Array<String>) {
val regex = Regex("[0-9]+")
val str1 = "123456789"
val str2 = "123 456 789"
println(regex matches str1) //true
println(regex matches str2) //false
println(regex.replace(str2, "aaa")) //aaa aaa aaa
}
코드 중복 줄이기 - run, let, with
if (a * b - 2 * a > 0) {
println(a * b - 2 * a)
}
위와 같이 중복되는 코드를 run과 let을 이용하여 줄일 수 있다.
run, let, with는 표기법만 다르지 완전히 똑같은 역할을 하므로 취향껏 골라 쓰면 된다.
run 확장함수
(a * b - 2 * a).run {
if (this > 0)
println(this)
}
let 확장함수
(a * b - 2 * a).let { result: Int ->
if (result > 0)
println(result)
}
with 확장함수
with(a * b - 2 * a) {
if (this > 0)
println(this)
}
객체 생성과 초기화를 한 번에 표현하기 - apply, also
apply 확장함수
Person().apply {
this.name = "Noah"
this.money = 700
}
also 확장함수
Person().also { person: Person ->
person.name = "Noah"
person.money = 70
}
프로퍼티 나중에 초기화하기 lazy
지난 글에서 프로퍼티의 Getter/Setter 구현을 다른 객체에 맡길 수 있음을 확인했다. (Delegated Property)
lazy 함수를 이용하면 프로퍼티의 값이 필요한 시점에 값 계산을 시작하게 할 수 있다.
class AAA {
var num1: Int = 1
val num2: Int by lazy { num1 * 5 }
}
fun main(args: Array<String>) {
val obj = AAA()
obj.num1 = 5
println("${obj.num1}, ${obj.num2}") //5, 25 - num2에 처음으로 접근한다. num2가 25로 초기화된다.
obj.num1 = 10
println("${obj.num1}, ${obj.num2}") //10, 25 - num1 * 5가 실행되지 않고 저장된 25를 반환한다.
}
val로 선언하면 선언과 동시에 초기화해야 하지만, lazy를 적용하면 초기화를 미룰 수 있다.
자바 숫자 표현
다음 숫자 클래스들은 자바 표준 라이브러리에 속해 JVM 의존성이 있다.
- BigInteger : Long보다 큰 정수를 다룰 때 사용한다.
- BigDecimal : Double의 표현 범위를 넘는 실수를 다룰 때 사용한다. 소수점 자리가 길어도 실수값을 정확히 표현한다.
숫자 계산 함수
설명 | 함수 |
최대값 | max(3, 7) //7 |
최소값 | min(3, 7) //3 |
실수 올림 | ceil(3.2) //4.0 |
실수 내림 | ceil(3.2) //3.0 |
실수 반올림 | round(3.2) //3.0 |
실수 Int로 반올림 | rountToInt(3.28) //3 |
실수 Long로 반올림 | roundToLong(3.28) //3 |
절대값 | abs(-17) //17 |
수학 상수 E | E //3.141592653589793 |
수학 상수 PI | PI //2.718281828459045 |
빗변의 길이 | hypot(3.0, 4.0) //5.0 |
제곱근 | sqrt(2.0) //1.4142135623730951 |
Double
Double 타입에는 NaN(Not a Number) 또는 Infinite(무한) 이라는 특수 값이 들어갈 수 있다.
fun main(args: Array<String>) {
val a: Double = 0.0/0.0
val b: Double = 7 / 0.0
val c: Double = 3.2
println("$a") //NaN
println(a.isNan()) //true
println("$b") //Infinite
println(a.isInfinite()) //true
println("$c") //3.2
println(a.isFinite()) //true
}
인수 검증하기
fun main(args: Array<String>) {
f1(10)
f2(0.0 / 0.0)
}
fun f1(num: Int?) {
checkNotNull(num)
check(num!! > 0)
}
fun f2(num: Double?) {
requireNotNull(num)
requere(num!!.isNaN())
}
- checkNotNull : 인수가 null이면 IllegalStateException, null이 아니면 Not-null 타입으로 캐스팅하여 반환
- check: 인수가 false이면 IllegalStateException
- requireNotNull: 인수가 null이면 IllegalArgumentException, null이 아니면 Not-null 타입으로 캐스팅하여 반환
- require: 인수가 false이면 IllegalArgumentException
프로그램 종료하기 exitProcess
프로그램을 종료하려면 일반적으로 main 함수에서 return을 하거나 main 함수까지 예외를 던져야 한다.
exitProcess 함수를 이용하면 실행 흐름 어디에서나 프로그램을 바로 종료시킬 수 있다.
exitProcess(0)은 정상 종료, 0이 아닌 수를 전달하면 비정상 종료를 나타낸다.
코드 속도 측정하기
- measureTimeMills
- measureNanoTime
'Kotlin' 카테고리의 다른 글
Kotlin으로 JPA Entity 정의하기 (0) | 2024.09.04 |
---|---|
Kotlin과 Java 함께 사용하기 (1) | 2022.10.08 |
Kotlin 문법 - 중수편 (1) | 2022.10.03 |
Kotlin 문법 - 초보편 (0) | 2022.09.29 |
Kotlin 문법 - 입문편 (0) | 2022.09.28 |