📚 함수(1)


📔 함수 정의

함수를 정의할 때, 머리부(header)와 몸체부(body)로 구분

  • 머리부(header)
    • 함수 시그니처인 함수 이름과 매개변수 개수에 맞게 자료형과 반환 자료형으로 구성
    • fun 키워드로 사용할 함수 정의
    • fun 키워드와 매개변수 정의 사이에 함수 이름 작성
    • 함수 내부에서 변수로 사용되는 매개변수(parameter)를 정의. 매개변수는 곧 함수 내 지역변수를 의미
    • 함수 반환 자료형을 매개변수 다음 콜론(:) 후에 작성. 몸체부 실행 후 해당 반환 자료형의 객체로 반환값 처리
  • 몸체부(body)
    • 함수가 실행되는 영역(코드 블록)
    • 함수가 호출되었을 때 실행되는 코드는 코드 블록인 중괄호 내에 작성
    • 지역변수(local variable) : 함수 내부에 정의되는 변수. 함수가 실행되었을 때 생성되고 함수가 종료되면 사라짐
    • 반환값 처리 : 함수의 결과를 반환할 때는 최종 처리결과를 return 다음에 처리. 반환값이 없다는 Unit 자체도 반환된 값의 자료형

📖 함수 몸체부 내 정의 가능한 대상

  • 지역 함수
  • 지역 클래스
  • object 표현식을 통한 객체 정의


📔 함수 실행

정의한 함수를 작동시키는 것. 함수 이름과 호출 연산자인 괄호를 사용하여 매개변수가 정의되어 있다면 동일한 개수의 인자를 전달해야 함

  • 반환값이 있는 경우
fun 함수명(매개변수명1: String, 매개변수명2: String) : Pair<String, String> { // 함수 정의
    
    val 지역변수1 = 100
    val 지역변수2 = 300
    
    fun 지역함수명(매개변수명: String) : String {
        return "매개변수명"
    }
    
    class 지역클래스명 { }
    
    println(" $지역변수1 $지역변수2")
    
    return Pair(매개변수명1, 매개변수명2) // 반환값 자료형으로 반환
}

val 결과값 = 함수명("함수", "호출")
println(결과값)
 100 300
(함수, 호출)


  • 반환값이 없는 경우
fun func() : Unit {
    println("특정 처리 기능이 없음")
}

func()

fun funcNoReturn() {
    println("반환값 생략 가능")
}

funcNoReturn()
특정 처리 기능이 없음
반환값 생략 가능


📔 함수 몸체부(블록) 처리

📖 단일 표현식으로 대체

  • 간단한 함수일 경우 코드 블록 대신 단일 표현식으로 정의할 수 있음
  • 단일 표현식은 “=” 연산자 다음에 짧은 표현식을 작성
  • 보통 단일 표현식으로 함수 코드 블록을 구성하면 단일 표현식을 추론해 반환 자료형을 추론할 수 있으므로 반환 자료형을 생략
fun add(x: Int, y: Int) : Int { // 코드 블록이 한 줄로 정리
    return x + y
}

fun add1(x: Int, y: Int) : Int = x + y // 코드 블록에 작성하는 대신 "=" 연산자와 표현식으로 정리, return을 사용하지 않음

fun add2(x: Int, y: Int) = x + y // 계산 결과가 반환 자료형을 추론할 수 있으므로 반환 자료형 생략

println(add(10, 20))
println(add1(10, 20))
println(add2(10, 20))
30
30
30


복잡한 형태로 정의될 수 있는 조건문, object 표현식 등과 같은 경우도 단일 표현식으로 작성할 수 있다.

fun comSingle(x: Int, y: Int) = if (x>y) x else y

val r = comSingle(20, 10)
println(r)

open class People {
    fun hello() = println("Hello World")
}

fun comSingle1() = object : People() {}

val p = comSingle1()
p.hello()
20
Hello World


📔 함수의 매개변수와 인자

함수는 동일한 이름으로 여러 개의 함수를 정의할 수 있다.

📖매개변수와 인자 처리 규칙

  • 위치 인자(positional argument) : 매개변수가 작성되면 매개변수를 지정한 위치에 맞춰 함수 호출 인자를 지정. 매개변수의 자료형에 맞는 인자를 전달해야 예외 없이 처리 가능
  • 이름 인자(named argument) : 인자가 위치에 따라 자동으로 매핑되지만 매개변수 이름과 인자를 쌍으로 지정해서 처리하면 함수를 호출할 때도 매개변수가 명확해져 코드를 볼 때 편리함. 이름 인자는 실제 위치 인자와 상관없이 지정 가능
  • 가변 인자(variable argument) : 인자의 개수가 미정일 때 가변 인자를 사용. 가변 인자 작성 시, 매개변수 이름 앞에 vararg 키워드 사용. 가변 인자는 이 매개변수 이름으로 여러 개의 원소를 가지는 배열로 처리
  • 매개변수 초깃값(default value) : 매개변수를 지정할 때 초깃값을 할당해두면 인자에 값을 할당하지 않아도 바로 초깃값으로 인자의 값을 대치할 수 있음. 특히 여러 개의 매개변수에 맞는 초깃값을 설정하면 함수를 편리하게 사용 가능

📖 위치 인자와 이름 인자 처리

fun addVar(x: Int, y: Int, z: Int) = x + y + z

println("위치인자 =" + addVar(10, 20, 30)) // 매개변수 위치에 따라 인자 매핑
println("위치인자 =" + addVar(20, 30, 30))

println("이름인자 =" + addVar(z = 100, x = 20, y = 30)) // 매개변수 이름과 인자를 같이 넣어 처리
println("이름인자 =" + addVar(y = 30, x = 20, z = 30))

println("인자혼합 =" + addVar(30, z = 20, y = 30)) // 위치인자와 이름인자 혼합
위치인자 =60
위치인자 =80
이름인자 =150
이름인자 =80
인자혼합 =80

📖 매개변수에 초깃값 지정

fun defaultArg(x: Int = 100, y: Int = 200) = x + y

println("위치인자 전부 전달 =" + defaultArg(300, 400))
println("이름인자 전부 전달 =" + defaultArg(y = 300, x = 400))

println("인자 전달없음 =" + defaultArg())
println("인자 하나 전달 =" + defaultArg(500))
위치인자 전부 전달 =700
이름인자 전부 전달 =700
인자 전달없음 =300
인자 하나 전달 =700

📖 가변 인자 지정

fun addVarArg(vararg x: Int): Int {
    var result = 0
    for (i in x) {
        result += i
    }
    return result
}

println("가변인자 0 = " + addVarArg())
println("가변인자 4 = " + addVarArg(1, 2, 3, 4))
println("가변인자 6 = " + addVarArg(1, 2, 3, 4, 5, 6))

val ll = intArrayOf(1, 2, 3, 4)
println("스프레드 연산 사용 = " + addVarArg(*ll)) // 배열로 정의된 것을 가변인자로 처리하려면 "*"를 붙여야 함. 이를 스프레드 연산자라고 부름
가변인자 0 = 0
가변인자 4 = 10
가변인자 6 = 21
스프레드 연산 사용 = 10

📖 함수 인자 전달 시 주의 사항

코틀린은 가변 객체와 불변 객체가 있다. 가변 객체를 함수의 인자로 전달하면 가변 객체 내부의 값을 변경할 수 있다. 함수를 정의할 때 매개변수에 가변 매개변수를 지정하면 가변 리스트를 인자로 전달할 경우 함수 밖에 지정한 리스트도 같이 변경된다. 함수 내부에서 외부 값을 변경하지 않으려면 가변 리스트를 복사해서 함수의 인자로 전달해야 한다.

val ll = listOf(1, 2, 3, 4) // 불변 리스트 생성

fun addList(list: List<Int>): List<Int> { // 함수의 매개변수와 반환 자료형의 리스트
    val result = list + listOf(6, 7) // 리스트 원소 추가
    return result // 새로운 리스트 반환
}

val ll2 = addList(ll)
println(ll2 == ll)

val ml = mutableListOf(1, 2, 3, 4) // 가변 리스트 생성

fun addList1(mutableList: MutableList<Int>): MutableList<Int> {
    mutableList.add(5) // 내부 원소 추가
    return mutableList
}

val ml2 = addList1(ml)
println(ml == ml2)
false
true


📔 함수 내부에 위치한 변수, 함수, 클래스 정의

  • 지역변수: 함수 내부에서만 사용 가능한 변수로, 초깃값을 지정하지 않고 정의해도 컴파일 에러는 발생하지 않음. 하지만 초기화는 반드시 해야 함
  • 지역 함수: 보통 클로저 환경을 구성하거나 세부 기능을 분리해서 처리할 때 사용
  • 지역 클래스: 함수 내부에서 클래스를 활용해서 사용할 때 정의. 보통 함수 기능이 커지면 클래스로 변환하여 정의해서 사용하므로 실제 사용할 일은 별로 없음

📖 지역 변수 정의

fun localVar(): Int {
    val x: Int = 100 // 지역변수 정의
    val y: Int = 200 // 지역변수 정의
    return x + y
}

println("지역변수의 합 = " + localVar())
지역변수의 합 = 300

📖 지역 함수 정의

fun outerFunc(x: Int): Int {
    val y = 100
    fun localFunc() = x + y // 지역함수 정의
    
    return localFunc() // 지역함수 실행
}

println("지역함수 실행 = " + outerFunc(100))
지역함수 실행 = 200


📔 변수 스코프(variable scope)

패키지, 함수 등은 내부에서 변수를 정의할 때 변수를 관리하는 영역인 변수 스코프가 생긴다.

  • 전역 함수(global function) : 패키지 레벨에 정의된 함수.
  • 지역 함수(local function, nested function) : 전역 함수 내부에 정의된 함수. 지역 함수 내부에서 다시 함수를 정의할 수 있음
  • 지역 함수 내 지역 함수 : 함수도 계층을 구성해서 정의 가능. 이때 상위에 정의된 함수의 지역 변수를 내포된 함수에서 사용 가능
  • 렉시컬 스코프(lexical scope): 함수가 계층 구조로 정의되면 현재 지역 영역에 없는 변수를 상위 레벨에서 검색해서 사용하는 것을 의미

📖 변수 스코프(전역, 지역)

var outVar = 300
var outVarR = 999

fun outerFunc1(x: Int): Int {
    val y = 100
    fun localFunc() = x + y + outVarR // 지역함수 정의
    return localFunc() // 지역함수 실행
}

fun outerFunc2(x: Int): Int {
    val y = 100
    fun localFunc(): Int { // 지역함수 정의
        outVar += x // 전역변수 갱신
        return x + y + outVar
    }
    
    return localFunc()
}

println("전역변수 참조 = " + outerFunc1(100))
println("전역변수 갱신 = " + outerFunc2(100))
println("전역변수 = " + outVar)
전역변수 참조 = 1199
전역변수 갱신 = 600
전역변수 = 400

📖 변수 스코프(전역, 지역, 지역의 지역)

var outVar = 300 // 전역변수 정의

fun outerFunc(x: Int): Int { // 전역함수 정의
    val y = 100
    fun innerFunc(): Int { // 지역함수 정의
        var z = 777
        z += outVar
        fun localFunc() = x + y + z // 지역함수 내 지역함수 정의
        return localFunc() // 지역함수 내 지역함수 실행
    }
    return innerFunc() // 지역함수 실행
}

println(outerFunc(100))
1277


📔 불변 객체와 가변 객체

  • 가변 객체(mutable object) : 내부의 원소를 삽입, 삭제, 변경할 수 있는 객체. 보통 List, Set, Map 등에 변경 가능한 객체가 별도로 존재하고(MutableList, MutableSet, MutableMap) 사용자 정의 클래스로 만든 객체는 대부분 변경 가능하다.
  • 불변 객체(immutable object) : 객체로 만들어지면 내부의 원소를 변경할 수 없는 객체. 대신 새로운 객체로 만들 수 있음. 보통 List, Set, Map 등에 변경할 수 없는 객체를 지정 가능. 사용자 정의 클래스일 때 변경할 수 없도록 만들어야 함.

📖 불변 인자 전달

val ll = listOf(1, 2, 3, 4)
println(ll)

fun addList(list: List<Int>): List<Int> { // 불변 리스트
    val result = list + listOf(6, 7) // 새로운 리스트 객체 생성
    return result // 결과 반환
}

val ll2 = addList(ll)
println(ll2 == ll) // 원 리스트는 변경되지 않음
println(ll2)
println(ll2 == ll)
[1, 2, 3, 4]
false
[1, 2, 3, 4, 6, 7]
false

📖 가변 인자 전달

val ml = mutableListOf(1, 2, 3, 4)
println(ml)

fun addList1(mutableList: MutableList<Int>): MutableList<Int> { // 가변 리스트
    mutableList.add(5) // 원소를 하나 추가
    return mutableList // 결과를 반환
}

val ml2 = addList1(ml)
println(ml == ml2) // 전달 전 가변 리스트도 변경
println(ml)
println(ml2)
println(ml == ml2)
[1, 2, 3, 4]
true
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
true

📖 가변 인자를 복사해서 전달

val mlc = mutableListOf(1, 2, 3, 4)
println(mlc)

fun addList1(mutableList: MutableList<Int>): MutableList<Int> { // 가변 리스트
    mutableList.add(5) // 원소를 하나 추가
    return mutableList // 결과 반환
}

val ml2 = addList(mlc.toList().toMutableList()) // 가변 리스트를 복사해서 처리
println(mlc == ml2) // 전달 전 가변 리스트도 변경
println(mlc)
println(ml2)
println(mlc == ml2)
[1, 2, 3, 4]
false
[1, 2, 3, 4]
[1, 2, 3, 4, 6, 7]
false


📔 익명 함수

익명 함수는 함수와 동일하지만 함수의 이름을 갖지 않는다. 대신 일회성으로 처리하는 용도로 사용한다.

📖 익명 함수 표기법

함수 정의와 같지만, 함수 이름을 정의할 수 없다. 익명 함수는 함수처럼 정의할 수 없고 정의하면 바로 실행하거나 변수, 매개변수에 할당하거나 바로 반환값으로 할당해야 한다.

  • fun 키워드 사용
  • 함수 이름이 없다는 점에서 일반 함수 정의와 다름
  • 매개변수는 괄호 안에 정의
  • 반환값은 매개변수와 콜론 다음에 정의
  • 함수 블록은 중괄호 내에 정의
  • 함수 블록 내 return 다음 반환값을 정의
println((fun (매개변수1: Int, 매개변수2: Int): Int { // 익명함수를 즉시 실행
    return 매개변수1 + 매개변수2
}) (100, 200))

val 덧셈 = fun (매개변수1: Int, 매개변수2: Int): Int { // 익명함수를 변수에 할당
    return 매개변수1 + 매개변수2
}

val res1 = 덧셈(300 ,200) // 익명함수를 실행
println(res1)

val res2 = (fun (매개변수1: Int, 매개변수2: Int): Int { // 즉시 실행
    return 매개변수1 + 매개변수2
}) (500, 200)

println(res2)
300
500
700

📖 익명 함수 내 지역 익명 함수 정의

  • 익명 함수 내 익명 함수를 지역 함수로 정의 가능
  • 함수의 반환 자료형을 처리할 때 함수를 반환할 수 있음
  • 함수의 매개변수로 함수 자료형을 정의하면 함수 호출 시, 익명 함수를 인자로 전달할 수 있음
  • 함수 자료형 지정은 매개변수는 괄호 안에 표시하고 “->” 다음에 반환값을 지정. 코틀린에서는 함수 자료형을 인터페이스로 제공
val res3 = (fun (x: Int): (Int) -> Int { // 외부 익명함수
    val inner = fun(y: Int): Int { // 내부 익명함수
        return x + y
    }
    return inner // 변수로 반환
}) (10)(20)

val res4 = (fun (x: Int): (Int) -> Int { // 외부 익명함수
    return fun(y: Int): Int { // 바로 반환 내부 익명함수
        return x + y
    }
}) (10)(20)

val res5 = fun(x: Int, y: Int, f: (Int, Int) -> Int): Int { // 함수를 매개변수로 받음
    return f(x, y) // 함수 실행 결과를 전달
}

println(res3)
println(res4)

println(res5(100, 200, fun(x: Int, y: Int): Int { return x + y })) // 익명함수를 인자로 전달
30
30
300