코틀린 & Java

[Android] 코틀린 확장 함수(kotlin extention)에 대해 알아보자

코딩하는후운 2022. 10. 5. 09:30
반응형

[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"]
    }
}

현재 이러한 제약이 있는 이유에 대해서는 아래 링크를 참조하실 수 있습니다.

 

마치며

기존 코루틴이 rxjava에 비해 이벤트 흐름 처리에 대한 부분이 상세하게 다루기 어려웠는데 runCatching을 사용하면 이제 코루틴만으로도 별도의 플러그인 없이 아주 쉽게 이벤트 처리를 할 수 있을 것 같습니다.

 

 

참조 : 스터디 그룹

반응형