[Android] 코틀린 익스텐션(kotlin extention)에 대해 알아보자
유용한 함수
🟩 sort()
collection의 각 요소들을 정렬해줍니다.
fun main(args: Array<String>) {
val a: MutableList<Int> = mutableListOf(3, 2, 1)
a.sort() //a.sorted() 는 새로운 collection을 반환
println(a)
val sorted = a.sortedByDescending { it } // 내림차순
println(sorted)
//sortBy() : Object 의 특정 Property 들을 기준으로 정렬
val list = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
list.sortBy { it.second }
println(list)
}
결과
🟩 forEach
collection의 내부 인덱스 끝까지 알아서 순환하는 방법입니다. 보통 it이라는 키워드로 각각의 요소에 접근하여 사용합니다.
fun main(args: Array<String>) {
var list = arrayOf(1, 2, 3, 4)
list.forEach {
println(it +1)
}
}
결과
🟩 filter
조건에 맞는 요소만 collection으로 다시 묶어 반환해주는 함수입니다.
fun main(args: Array<String>) {
var list = arrayOf(1, 2, 3, 4)
var list2 = list.filter {
it > 1
}
println(list2)
}
결과
🟩 map
collection의 각 요소들을 변형을 거쳐서 다른 collection으로 쉽게 복사할 수 있는 함수
fun main(args: Array<String>) {
var a: List<Int> = listOf(1, 2, 3)
var b = a.map {
it * 2
}
println(b)
}
결과
🟩 find
조건에 맞는 첫 번째 요소를 반환한다. 없다면 null을 반환합니다. 이와 반대로 조건에 맞는 마지막 원소를 반환하는 것은 findLast가 있습니다.
fun main(args: Array<String>) {
var list = arrayOf(1, 2, 3, 4)
var list2 = list.find {
it > 1
}
println(list2)
}
결과
🟩 any, all, none
collection의 요소를 모두 검사하여 해당 조건에 따라 Boolean을 반환하는 함수입니다.
fun main(args: Array<String>) {
var a: List<Int> = listOf(1, 2, 3)
if (a.any{ it==2 }) {
println("any: 하나라도 조건에 맞으면 true를 반환")
}
if (a.all{ it is Int }) {
println("all: 모두 조건에 맞으면 true를 반환")
}
if (a.none{ it > 4 }) {
println("none: 하나도 조건에 맞지 않으면 true를 반환")
}
}
결과
🟩 flatMap
아이템마다 원하는 컬렉션을 만들고 합쳐서 반환하는 함수입니다.
fun main(args: Array<String>) {
var nums: List<Int> = listOf(1, 3, 5)
var flat = nums.flatMap {
listOf(it * 1, it + 5, it*9)
}
println(flat)
}
결과
🟩 partition
아이템에 조건을 걸어 true인지 flase인지에 따라 2개의 collection으로 나누어주는 함수 (true : first로 참조, false : second로 참조)
fun main(args: Array<String>) {
var people: List<Person> = listOf(Person("hun", 12),
Person("jae", 24),
Person("jin", 36))
var pair = people.partition{ it.age > 20 }
println("* first")
pair.first.forEach { println("${it.age} ${it.name}") }
println("* second")
pair.second.forEach { println("${it.age} ${it.name}") }
}
class Person(val name: String, var age: Int)
결과
🟩 getOrElse
collection안에서 해당 index에 값이 존재할 경우, 해당 값을 반환하고 없을 경우 중괄호 값 반환
fun main(args: Array<String>) {
var a : List<Int> = listOf(1, 3, 5)
println(a.getOrElse(2) {10}) //a[2] 존재하므로 5 반환
println(a.getOrElse(5) {10}) //a[5] 존재하지 않으므로 10 반환
}
결과
🟩 reduce & fold
collection 내의 데이터를 모두 모으는(accumulate) 함수입니다. reduce와 fold의 차이는 초기값이 있냐 없냐입니다. fold의 경우 초기값을 설정할 수 있습니다. reduce는 첫 번째 요소를 acc로 사용되고 두 번째 요소 이후 계속 연산을 합니다.
fun main(args: Array<String>) {
var nums: List<Int> = listOf(1, 3, 5)
println("reduce : " + nums.reduce { acc, i -> acc+i *2 })
println("fold : " + nums.fold(0){acc,i -> acc+i *2})
}
결과
- reduce의 경우 {acc : 1, i : 3} => 1 + 3 * 2 = 7 , {acc : 7 , i : 5 } => 7 + 5 * 2 = 17 이 되고
- flod의 경우 {acc : 0 , i : 1} => 0 + 1 * 2 = 2 , {acc : 2 , i :3 } => 2 + 3* 2 = 8 , {acc : 8 , i : 5}=> 8 + 5 *2 = 18이 됩니다.
runCatching과 Result 타입
runCatching은 코틀린 1.3버전부터 도입된 캡슐화 블록입니다. runCatching 블록 안에서 성공/실패 여부가 캡슐화된 Result<T> 형태로 리턴합니다. 스위프트의 Result, 자바스크립트의 Promise와 유사하며 runCatching을 이용하면 코루틴 블록등을 RxJava에서 사용했던 것과 같이 유연한 이벤트스트림으로 처리할 수 있습니다.
runCatching 예시
val colorName: Result<String> = runCatching {
when (color) {
Color.BLUE -> "파란색"
Color.RED -> "빨간색"
Color.YELLOW -> "노란색"
Color.ARMARNTH -> throw Error("처음 들어보는 색")
}
}.onSuccess { it:String ->
//성공시만 실행
}.onFailure { it:Throwable ->
//실패시만 실행 (try - catch문의 catch와 유사)
}
프로퍼티
Result<T>타입은 isSuccess와 isFailure를 프로퍼티로 갖습니다.
if(colorName.isSuccess){ //성공시 호출 }
if(colorName.isFailure){ // 실패시 호출}
feature("Repository Test") {
val repository = MockRepository()
val dummyUser = SampleHleper.dummyUser()
scenario("should be able to insert a user In Repo") {
val result = runCatching {
runBlocking { repository.insert(dummyUser) }
}
result.isSuccess shouldBe true
}
}
또한 테스트코드 작성시 가독성이 더 좋게 작성할 수 있습니다.
Result 타입에서 값 가져오기
- getOrThrow()
colorName.getOrThrow()
// runBlocking문에서 에러가 발생한경우 해당에러를 리턴합니다.
- getOrDefault
colorName.getOrDefault(defaultValue = "미상")
//runBlocking문에서 에러가 발생한경우 defaultValue 파라미터를 리턴합니다.
- getOrNull
colorName.getOrNull()
// runBlock문에서 에러가 발생한경우 null값을 리턴합니다.
- getOrElse
colorName.getOrElse{ exception: Throwable -> }
// runBlocking문에서 에러가 발생한경우 exception을 인자로받아
// 블록안의 값을 리턴합니다.캡슐화된 타입값과 같아야하기때문에 다른타입을
// 리턴하고싶다면 아래 mapCatching을 사용해야합니다.
map, mapCatching
val firstUserAge: Result<Int> = runCatching {
"123"
}.map { it: String ->
it.toInt()
}
val secondUserAge: Result<Int> = runCatching {
"123"
}.mapCatching { it: String ->
it.toInt()
}
위 코드에서는 firstUserAge와 secondUserAge모두 getOrNull() 인스턴스 호출 시 123 값을 리턴합니다. 어떻게 보면 동일해 보이지만 블록 안의 에러가 발생한다면 다르게 처리됩니다.
map
map은 블록 안의 에러가 발생할경우 바깥으로 에러를 보냅니다.
try {
runCatching {
database.getUser(id)
}.map { user: User? ->
//강제로 에러를 발생시킵니다.
throw Error("유저정보를 가져올 수 없습니다.")
}.onSuccess {
//map 블록에서 에러 발생시 실행되지않습니다.
}.onFailure {
//map 블록에서 에러 발생시 실행되지 않습니다.
}
} catch (e:Exception) {
//map 블록에서 발생한 에러를 인자로받아 호출됩니다.
}
mapCatching
mapCatching은 블록 안의 에러를 내부에서 처리하며 onFailure로 받을 수 있습니다.
runCatching {
database.getUser(id)
}.mapCatching { user: User? ->
//강제로 에러를 발생시킵니다.
throw Error("유저정보를 가져올 수 없습니다.")
}.onSuccess {
//mapCatching 블록에서 에러 발생시 실행되지않습니다.
}.onFailure {
//mapCatching 블록에서 에러 발생시 호출됩니다.
}
recover, recoverCatching
map이 runCatching이 성공할 경우 호출되었다면 recover은 실패했을 경우 호출됩니다. 만약 runCaching문에서 에러가 발생할 경우 recover, recoverCatching문이 호출된 후 리턴 값을 onSuccess로 전달합니다. 하지만 map과 마찬가지로 블록 내에서 에러가 발생했을 경우 다르게 처리합니다.
recover
try {
runCatching {
throw Error("runBlock문 에러발생")
}.recover { it: String ->
throw Error("recover문 에러발생")
}.onFailure {
//에러가 전달되지않습니다.
}
} catch (e: Exception) {
//recover에서 발생한 에러가 받아집니다.
}
recoverCatching
runCatching {
throw Error("runBlock문 에러발생")
}.recoverCatching { it: String ->
throw Error("recover문 에러발생")
}.onFailure {
//이곳에서 에러를 받습니다.
}
주의사항
현재 kotlin.Result를 함수의 리턴타입으로 사용할 수 없으며 이를 해결하기 위해서는 -Xallow-result-return-type를 추가해주어야 합니다. gradle에서 아래 라인을 추가함으로 해결할 수 있습니다.
android {
kotlinOptions {
freeCompilerArgs = ["-Xallow-result-return-type"]
}
}
현재 이러한 제약이 있는 이유에 대해서는 아래 링크를 참조하실 수 있습니다.
- https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md#limitations
- https://stackoverflow.com/questions/52631827/why-cant-kotlin-result-be-used-as-a-return-type
마치며
기존 코루틴이 rxjava에 비해 이벤트 흐름 처리에 대한 부분이 상세하게 다루기 어려웠는데 runCatching을 사용하면 이제 코루틴만으로도 별도의 플러그인 없이 아주 쉽게 이벤트 처리를 할 수 있을 것 같습니다.
참조 : 스터디 그룹
'코틀린 & Java' 카테고리의 다른 글
[Android] Looper에 대해 알아보자 (0) | 2022.10.24 |
---|---|
[Android] Java Stream에 대해 알아보자 (0) | 2022.10.24 |
[Android] 가비지 컬렉터(Garbage Collector), 참조(Reference)에 대해 알아보자 (0) | 2022.09.21 |
lateinit 초기화 확인하기 (0) | 2022.01.11 |
LiveData와 StateFlow의 차이 (0) | 2021.12.27 |