[Kotlin] Function 함수

코랑·2023년 5월 1일
0

android

목록 보기
10/16

코틀린 함수 정의는 fun으로 함

fun double(x: Int): Int {
    return 2 * x
}

// 사용은 요렇게
val result = double(2)

Stream().read() // 클래스 생성 후 사용

사용법

Default arguments

override할 때 default value 없애는것도 가능

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}

class B : A() {
    override fun foo(i: Int) { /*...*/ }  // No default value is allowed.
}

//named argument로 baz 사용
fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used 

// default parameter뒤에 마지막 변수가 람다면 named argument 혹은 괄호를 통해 마지막 변수를 전달함
fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // baz = 1, 괄호 밖
foo(qux = { println("hello") }) // bar = 0 and baz = 1
foo { println("hello") }        // bar = 0 and baz = 1, qux는 괄호 밖

Named arguments

// 정의
fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }
// 사용
reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)
// default 값을 모두 사용한 경우
reformat("This is a long String!")
// 특정 값들만 default 값을 사용한 경우
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

// spread(*) 를 사용해서 vararg 변수 넘기기(이름 사용)
fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))

JVM에서 함수가 실행되면 Named argument를 사용 못할 수 있음. 자바 bytecode가 함수 parameters의 이름을 항상 보존하지만은 않기 때문에~~

void 리턴 타입

함수가 유용한 값을 반환하지 않을 때 반환 타입은 Unit임. 그리고
Unit일 경우 반환타입을 정의 하지 않아도 됨

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // 명시적으로 무언가를 반환하지 않아도 됨.
}

// return type Unit
fun printHello(name: String?) { ... }

Single Expression functions

// 이렇게 함수를 정의할 수도 있음 
// 이 경우에는 컴파일러가 타입을 유추할 수 있어서 리턴타입을 명시적으로 정의하지 않아도 됨.
fun double(x: Int): Int = x * 2

Explicit return types

Unit일 경우를 제외하고는 명시적으로 리턴타입이 있어야함.
싱글이 아닌경우 코드블록에서 어떤짓을 할지 몰라서 컴파일러가 반환타입을 유추헐 수가 없음 그래서 명시적으로 적어줘야함.

Variable number of arguments(varargs)가변인수

보통 함수의 마지막 변수로 vararg 수정자를 사용 변수를 정의함.

// 정의
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}
// 사용
val list = asList(1, 2, 3)

한 함수에서 오직 한개의 파라미터만 vararg가 될 수 있음.
vararg가 마지막 변수가 아니면, 뒤에 오는 변수들은 이름을 붙여서 넘겨줘야함.
함수타입이면 괄호 밖에 람다로 전달 할 수 있음.

vararg 함수를 사용할 때, 이미 가지고있는 array를 사용하고 싶을 때 앞에 *(spread) 를 붙이면됨.
원시타입 array를 넘기고 싶으면 toTypedArray()를 사용하면 됨

// array 사용
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
// primative 사용
val a = intArrayOf(1, 2, 3) // IntArray is a primitive type array
val list = asList(-1, 0, *a.toTypedArray(), 4)

Infix notation

infix 키워드 사용법
제한 사항

  • 멤버 함수 혹은 확장 함수만 가능함
  • 하나의 변수만 가질 수 있음.
  • 가변인수(varargs)는 불가능하고 default value도 가질 수 없음.
infix fun Int.shl(x: Int): Int { ... }

// 표기법 1
1 shl 2

// 표기법 2
1.shl(2)

아래 묶인 예시들은 같은 의미임 infix 함수는 산술 기호, 타입 캐스팅, rangeTo 보다 더 우선순위가 낮음.
1 shl 2 + 3 == 1 shl (2 + 3)
0 until n * 2 == 0 until (n * 2)
xs union ys as Set<*> == xs union (ys as Set<*>)
아래예시는 operators && and ||, is- and in-checks는 infix함수보다 우선순위가 낮음
a && b xor c == a && (b xor c)
a xor b in c == (a xor b) in c

+,-,rnageTo,as > infix > &&, ||, is, in

infix 함수는 receiver랑 변수 둘 다 필요함.

현재 receiver에서 infix 표기법을 사용해 메소드를 호출할 때 this를 명시적으로 사용해야함.
class MyStringCollection {
    infix fun add(s: String) { /*...*/ }

    fun build() {
        this add "abc"   // Correct
        add("abc")       // Correct
        //add "abc"      // Incorrect: the receiver must be specified
    }
}

Function scope

코틀림 함수는 파일의 탑레벨에 선언될 수 있음. (= 함수를 가진 클래스를 만들 필요가 없다. Java, C#, Scala(3부터는 된다고함) 처럼)
그 외에도 멤버 함수나 확장함수도 locally하게 선언할 수 있음.

Local functions

// 함수 내부에 함수
// 내부 함수는 바깥 함수에 정의된 로컬 변수에만 접근 가능
fun dfs(graph: Graph) {
//
    fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

// 위 함수랑 동일 visited가 로컬 변수로 사용한거
fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

Member functions

클래스나 오브젝트 내무에 정의되는 멤버 함수 .을 통해 호출됨

Tail recursive functions

코틀린은 꼬리 재귀라고 불려지는 함수형 프로그래밍 스타일을 지원하고있음.
일반적인 반복문을 사용하는 일부 알고리즘을 짤 때, 스택 오버플로우의 리스크 없이 재귀함수를 구현할 수 있게해줌. tailrec이라는 수정자를 함수 앞에 붙여주면 됨

val eps = 1E-10 // "good enough", could be 10^-15

// 
tailrec fun findFixPoint(x: Double = 1.0): Double =
    if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

위 코드는 수학적 상수인 코사인의 fixPoint를 찾는 코드.
결과가 더 이상 변하지 않을 때까지 1.0부터 Math.cos를 반복적으로 호출해서 대해 0.7390851332151611라는 결과를 지정된 eps 정밀도에 사용하면 됩니다.
아래 전통 스타일로 해도 결과 코드는 동일... 하다고,,,

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

tailrec 수정자를 사용하려면 함수가 자기자신을 호출하는게 마지막이어야함.
재귀 호출 이후에 더많은 코드가 있으거나, try/catch/finally 코드 블락 내에서나 open functions에서는 꼬리 재귀를 사용할 수 없음. 현재는 JVM과 Kotlin,Native에서 사용할 수 있음.

0개의 댓글