Kotlin(코틀린) 심화문법 2. 고차 함수, 코틀린 표준 함수, Generic

차선호·2023년 4월 13일
0

Kotlin

목록 보기
8/8
post-thumbnail

고차 함수


정의

  • 매개 변수나 함수의 반환 값으로 함수가 사용되는 함수

  • 코틀린에서는 람다나 함수 참조를 사용해 함수를 변수에 넘길 수도 있고 그 자체가 값이 되기도 함


예시 1

  • 다른 함수를 인자로 사용하거나 함수를 결과값으로 반환하는 함수
private fun highOrderFunction(sum: (Int, Int) -> Int, a: Int, b: Int): Int = sum(a, b)

fun main() {
    println(highOrderFunction({ x, y -> x + y }, 20, 30))
}
<--출력 결과-->
50
  • highOrderFunction

    • 파라미터(Int, Int) → Int인 함수(sum)와 Int, Int 총 3개를 받는 함수

    • 함수의 body에서는 전달받은 함수 sum을 실행하며 두 개의 변수 a, b를 넘겨줌


예시 2

  • 일반 함수를 인자나 반환 값으로 사용하는 함수
private fun sum(a: Int, b: Int): Int = a + b
private fun sumFunction(): Int = sum(40, 2)

fun main(){
	println($sumFunction()
}
<--출력 결과-->
42

예시 3

  • 람다식을 인자나 반환 값으로 사용하는 고차 함수
val multiply = {x: Int, y: Int -> x*y}

println(${multiply(8,8)})
<--출력 결과-->
64

예시 4

  • 변환 자료형이 아예 없거나 매개변수가 하나만 있을 때의 람다식 함수
val helloWorld: () -> {println("Hello World!")}
val outSelfSum: (Int) -> Int = {a -> a+a}
helloWorld()
println(ourSelfSum(10))
<--출력 결과-->
Hello World!
20

예시 5

  • 람다식 안에 람다식을 넣는 람다식 함수
val nestedLambda: () -> () -> Unit = {{print("nested")}}

예시 6

  • 인자와 반환 값이 없는 람다식 함수
val print: () -> Unit = {print("print")}
val print2 = {println("print")}
print()
print2()
<--출력 결과-->
print
print

예시 7

  • 파라미터 1개의 람다식인 경우는 괄호 밖으로 빼서 표현 가능
fun main() {
    callFunction({println("hello")} )

    callFunction(){
        println("hello")
    }
    callFunction {
        println("hello")
    }
}

private fun callFunction( call:() -> Unit )  = call()
<--출력 결과-->
hello
hello
hello

예시 8

  • 파라미터가 1개 이상인 람다식의 경우, 람다식을 제일 뒤로 이동해서 괄호 밖으로 빼서 표현 가능

  • 제일 처음 살펴 본 highOrderFunction의 경우처럼 여러 개의 파라미터를 받는 경우, 람다식을 제일 마직막으로 이동시키면 메서드 밖으로 이동 가능

private fun highOrderFunction(a: Int, b: Int, sum: (Int, Int) -> Int): Int = sum(a, b)

fun main() {
    println(highOrderFunction(20, 30, {x, y -> x + y}))

    var result = highOrderFunction(20, 30){
            x, y -> x + y
    }

    println(result)
}
<--출력 결과-->
50
50



코틀린 표준 함수


표준 함수

  • 코틀린에서 유틸리티성으로 제공하며 람다를 인자로 받아 동작하는 함수

  • 확장함수의 형태로 모든 객체에서 호출 가능

  • 객체를 초기화 할 때 실행할 코드가 있는 경우 등에서 자주 사용됨

  • java에서 객체의 내용을 참조하려면 object를 reference에 변수에 저장한 후 변수에 ‘ . ‘으로 접근했으나 코틀린에서는 표준함수를 사용하여 바로 접근 가능

  • let( ), apply( ), run( ), with( ), also( )

let( )

  • let( )을 호출하는 객체의 람다식 안에 파라미터로 넘김

  • ‘it’을 통해 호출 객체에 접근

  • { } 의 결과값을 반환

    • 제일 마지막 줄을 return
fun main() {
    letTest()
}

fun letTest() {
    var arr = arrayOf(1, 2, 3)
    // let은 젤 마지막 줄이 리턴
    var result = arr.let {
        println("${it[1] + it[2]}")
        it[1] + it[2]
    } //let 람다식 계산 결과를 리턴

    println(result)

    //let 람다식에서 계산한 arr[1] + arr[2] 를 반환하고 arr[0]을 더해서 출력
    val result2 = arr.let { it[1] + it[2] }.plus(arr[0])
    println("더하기 결과: $result2")
}
<--출력 결과-->
5
5
더하기 결과: 6

also( )

  • also( )를 호출하는 객체의 람다식 안에 파라미터로 넘김
  • ‘it’을 통해 호출 객체에 접근
  • 호출 객체 자체를 반환
    • 받은 객체의 값을 바꿔서 그 객체 자체를 return
fun main() {
    alsoTest()
}

fun alsoTest(){
        var student = Student("Park",11)

		    // also는 return x
		    var student2 = student.also {
        it.age = 15
        it.name = "kim"
    }

    println(student) //Student(name=kim, age=15)
    println(student2) //Student(name=kim, age=15)
}

apply( )

  • apply( )를 호출하는 객체의 람다식 안에 파라미터로 넘김
  • ‘this’를 통해 호출 객체에 접근
  • this는 생략 가능
  • 호출 객체 자체를 반환
    • 받은 객체의 값을 바꿔서 그 객체 자체를 return
data class Student2(var name : String, var age : Int)
fun main() {
    applyTest()
}
fun applyTest() {
    var student = Student2("Park", 11)

    var student2 = student.apply {
        age = 15 //this 생략
        name = "kim" //this 생략
    }
    println(student) //Student2(name=kim, age=15)
    println(student2) //Student2(name=kim, age=15)
}

run( )

  • 호출 객체를 파라미터로 넘기는 방식과 객체 없이 사용하는 방식 모두 가능

  • { }의 결과값을 반환

    • 제일 마지막 줄을 return
fun main() {
    runTest()
}
fun runTest() {
    var a = 10
    var b = 15

    //객체없이 run 단독 사용
    var result = run {
        var c = a + b
        println(c) //25
        c
    }//더하기 작업 수행 후 결과 c 반환

    //객체에 run 사용.
    result = result.run {
        plus(5)
    }
    println(result) //30
}

with( )

  • 인자로 받는 객체를 블럭의 파라미터로 전달

  • run( ) 함수와 기능은 거의 동일

  • Safe Call 지원하지 않음 → 종종 let과 함께 사용됨

  • with은 ‘ . ‘ 없이 ( )로 인자를 받는다.

    • 변수?. 뒤에 함수가 나오면 변수가 null일 때 뒤에 함수를 실행하지 않아서 safe call이 가능한데 with은 위와 같이 호출하지 않기 때문에 Safe Call을 하지 못한다.
      var newName = people?.let{
      		with(it){
      				age = 20
      				name
      		}
      }
data class People(var name : String, var age : Int)

fun main() {
    withTest()
}
fun withTest(){
    var people = People("Park", 15)

    var newAge = with(people){
        age = 20
        age
    }

    println(newAge) //20

}



Generic


정의

  • 클래스나 함수를 정의할 때 타입을 정하지 않고 포괄적으로 받아 들일 수 있는 상태로 선언

  • 타입 매개 변수를 (< >)를 클래스나 인터페이스 이름 뒤에 붙이면 제네릭하게 만들 수 있음

class Box<T>(t: T){
		var value = t
}

예시 1

  • 제네릭 클래스 / 인터페이스 정의
// kotlin
class Car2 {
}

// 항목을 담거나 뺄 수 있는 제네릭 인터페이스 Container 정의
interface Container<T> {
    fun put(item: T)
    fun take(index: Int) : T
}

// 자동차(Car)를 담거나 뺄 수 있는 클래스 Garage 정의
class Garage : Container<Car2> {
    private val items:MutableList<Car2> = arrayListOf();
    override fun put(item: Car2) {
        items.add(item)
    }

    override fun take(index:Int): Car2 {
        return items.get(index)
    }
}

예시 2

  • 제네릭을 통한 Todo list 작성
data class ToDo(val title: String,
                val content: String,
                var idx: Int = todoIdx++,
                var completed: Boolean = false)

interface ToDoContainer<T> {
		companion object Todo{
				var todoIdx = 1
		}
    fun addNewItem(item: T)
    fun markCompleted(idx: Int)
}

예시 3

  • 제네릭을 통한 Todo list 작성

    • ToDoContainer에 제네릭을 사용하고 매개변수로 Todo 클래스를 대입

    • filter 함수는 조건에 알맞은 리스트를 반환

class ToDoContainerImpl : ToDoContainer<ToDo> {
    var todoItems: MutableList<ToDo> = arrayListOf()
    override fun addNewItem(item: ToDo) {
        println("addNewItem : $item")
        todoItems.add(item)
    }
    override fun markCompleted(idx: Int) {
        todoItems.filter { it.idx == idx }[0].let {
            it.completed = true
            println("$idx is marked as completed")
            println("$it")
        }
    }
}

예시 4

  • 제네릭을 통한 Todo list 작성

    • 아이템을 추가하고 완료한 일을 체크하는 메서드 markCompleted의 인자로 1을 넣고 호출
fun main(){
    var todayTodoList = ToDoContainerImpl()
    todayTodoList.addNewItem(ToDo("자바공부", "자바 기본은 마스터 했음."))
    todayTodoList.addNewItem(ToDo("코틀린공부", "코틀린 공부를 시작하자!!!!"))
    todayTodoList.markCompleted(1)
}
<--출력 결과-->
addNewItem : ToDo(title=자바공부, content=자바 기본은 마스터 했음., idx=1, completed=false)
addNewItem : ToDo(title=코틀린공부, content=코틀린 공부를 시작하자!!!!, idx=2, completed=false)
1 is marked as completed
ToDo(title=자바공부, content=자바 기본은 마스터 했음., idx=1, completed=true)

in / out

  • 타입 매개변수의 업 혹은 다운 캐스팅을 할 수 있도록 지원되는 키워드

    • out : B<Sub 타입>을 B<Super 타입>에 대입할 수 있도록 지원

    • in : C<Super 타입>을 C<Sub 타입>에 대입할 수 있도록 지원

      • : A<*>은 타입 인수가 무엇이든 상관 없이 대입할 수 있도록 지원
class A<T>
class B<out T>
class C<in T>

fun main() {
    //T 로 선언된 경우
    val a: A<Int> = A<Int>() // 양쪽 자료형 일치해야 함.

    //out T 로 선언된 경우
    val b1: B<Number> = B<Number>(); // 양쪽 자료형 일치 -> OK
    val b2: B<Number> = B<Int>(); // 왼쪽이 Super 면 -> OK
//    val b3: B<Int> = B<Number>(); // 왼쪽이 Sub 면 -> 컴파일 오류

    //in T 로 선언된 경우
    val c1: C<Number> = C<Number>() // 양쪽 자료형 일치 -> OK
    val c2: C<Int> = C<Number>() // 오른쪽이 Super면 -> OK
//    val c3: C<Number> = C<Int>() // 오른쪽이 Sub 면 -> 컴파일 오류

    /* <*> 은 상관없이 대응 */
    val star1: A<*> = A<Number>()
    val star2: A<*> = A<Int>()
    val star3: A<*> = A<String>()
}
profile
dkssud!

0개의 댓글