educative - kotlin - 3

Sung Jun Jin·2021년 3월 6일
0

Working with Functions

Creating Functions

코틀린에서 함수는 fun 키워드로 정의한다. 뒤이어 함수의 이름과 매개변수(parameter) 리스트를 정의해준다.

fun greet() = "hello"

위와 같은 간단한 단일표현 함수 (single-expression function) 이라면 함수의 body 부분의 중괄호({})를 = 연산자로 대체 가능하다. 또한 return 문도 사용하지 않는다. 이런 단일표현 함수들은 코틀린에서 자동으로 컴파일 레벨에서 타입추론(type inference)을 통해 반환타입을 지정해주기 때문에 return 타입을 직접 명시해주지 않는다. 따라서 아래와 같은 코드는 컴파일 에러가 발생한다.

fun greet() = "hello"

var message: Int = greet() // type mismatch error

타입 추론을 통해 코틀린은 greet() 함수의 리턴타입을 String으로 정의했다. 따라서 Int형 message 변수에 greet()의 리턴값을 담으려고 한다면 type mismatch 컴파일 에러가 발생할 것이다.

코틀린에서 Unit 타입은 자바의 void와 같은 역할을 한다. 따라서 반환값이 없는 함수들에 한해서 리턴 타입을 Unit으로 명시해줄수 있다 (생략가능).

fun sayHello(): Unit = println("Well, hello")

val message: Unit = sayHello()

println("The result of sayHello is $message")
"""
[실행결과]
Well, hello
The result of sayHello is kotlin.Unit
"""

리턴 타입은 위처럼 매개변수를 정의해준 다음에 콜론(:)과 함께 정의해준다. 코틀린 Unit이 자바의 void와 다른점이 있다면, 자바에서 void는 존재하지 않음을 뜻하지만 코틀린에서 Unit 클래스로 정의되어 있으며, 아무것도 반환하지 않는 타입 을 뜻한다. Unit 타입은 toString(), equals(), hashCode와 같은 메소드를 가지고 있다. sayHello() 함수에서 println()은 내부적으로 Unit 객체의 toString() 메소드를 호출하기 때문에 위와 같은 실행결과가 나타난다.

매개변수를 정의할때는 파이썬과 똑같이 이름: 타입 으로 정의해준다. 2개이상일 경우 콤마(,)로 구분해준다.

fun greet(name: String) = println("Hello $name")
println(greet("Eve")) // Hello Eve

매개변수를 정의할때는 var, val 키워드도 사용하지 않는다. 이유는 코틀린에서 암묵적으로 매개변수는 val로 처리를 해준다. 그래서 실험을 해봤다

fun greet(name: String): String = {name = "sungjun"}
// error: val cannot be reassigned

매개변수로 넣어주는 name의 값을 수정하려고 시도해보니 위와 같은 에러가 나타났다. 따라서 코틀린에서 매개변수는 val로 지정된다!

함수가 단일표현 함수(single-expression function)이 아닌경우는 자바와 동일하게 중괄호를 통해서 함수의 body를 정의해준다. 리턴타입 또한 명시해줘야 한다.

fun max(numbers: IntArray): Int {
	var large = Int.MIN_VALUE
    
    for (number in numbers) {
    	large = if (number > large) number else large
    }
    
    return large
}

println(max(intArrayOf(1,5,2,12,7,3))) // 12

Default and Named Arguments

디폴트 인자(default argument) 는 입력 변수의 기본 값을 정해줄 수 있다. 함수를 사용할 때 해당 입력 변수에 값을 입력하지 않아도 정의된 기본 값이 사용된다. 즉, 기본 값이 있는 매개변수들은 함수를 사용할때 입력하지 않으면 기본 값이 사용되고, 입력하면 입력한 값이 사용된다.

fun greet(name: String, msg: String = "Hello"): String = "$msg, $name"

println(greet("Eve")) // Hello, Eve
println(greet("Eve", "Howdy")) // Howdy, Eve

디폴트인자와 일반인자를 함께 사용할때 주의할점이 있다. 일반 매개변수는 순서대로 들어오는 값이 필요하기 때문에 변수의 값을 입력하지 않아도 되는 디폴트 인자는 일반변수 뒤에 사용해준다.

지명 인자(named argument) 를 사용하면 함수를 호출할때 매개변수의 이름과 값을 동시에 전달해줄 수 있다.

fun createPerson(name: String, age: Int = 1, height: Int, weight: Int) {
	println("$name $age $height $weight")
}

위 함수를 두 가지 방법으로 호출해보자

createPerson("Jake, 12, 152, 43)

위 같은 방식으로 호출하면 12, 152, 43과 같은 Int형 매개변수가 어떤 값을 의미하는지 문맥상 추론이 어렵다. 이를 named argument를 통해 아래와 같이 가독성을 개선시킬 수 있다. 또한 named argument를 사용하면 매개변수가 정의된 순서와 무관하게 값을 전달시킬 수 있다.

createPerson(name="Jake", age=12, height=152, weight=43)
// parameter 순서 바꿔보기 
createPerson(name="Jake", height=152, age=12, weight=43)

vararg and Spread

함수를 정의할 때 매개변수에 vararg(variable arguments, 가변인자) 키워드를 사용하면 호출 시 인자의 개수를 가변적으로 전달할 수 있다.

fun max(vararg numbers: Int): Int {
	var large = Int.MIN_VALUE
    
    for (number in numbers) {
    	large = if number > large number else large
    }
    
    return large
}

println(max(1,5,2)) // 5
println(max(1,5,2,12,7,3)) // 12

매개변수로 넘어가는 Int형 타입의 numbers 앞에 vararg 키워드를 사용하면 실제로 매개변수는 Int타입의 가변 길이의 배열로 넘어간다.

만약 varargs로 할당된 numbers 매개변수에 고정된 길이의 Int형 배열을 넣으면 어떻게 될까?

val values = intArrayOf(1, 21, 3)

println(max(values)) // type mistmach: inferred type is IntArray but Int was expected

type mismatch 에러가 발생하는 이유는 numbers 매개변수는 IntArray 타입이 아닌, Int 타입만 허용하기 때문이다. vararg는 가변 인자를 단일 인자(single parameter) 형식으로 전달받기를 강제한다 따라서 배열을 전달하게 되면 에러가 발생하는 것이다. 아래와 같이 극단적인 코드를 작성해 해결할 수는 있다...

println(max(values[0], values[1], values[2])) //SMELLY, don't

vararg로 명시된 가변인자에 배열을 전달하고자 할때는 * 연산자를 prefix로 사용하면 된다.

println(max(*values)) //21
println(max(*listOf(1, 4, 18, 12).toIntArray()))

2개 이상의 매개변수를 정의한 함수에서 가변인자는 가장 뒤에 정의하는것이 좋다.

fun greetMany(vararg names: String, msg: String) {
	println("$msg ${names.joinToString(", ")}")
}

greetMany("Hello", "Tom", "Jerry", "Spike")

만약 위와 같은 식으로 정의하면 컴파일러가 어디서부터 어디까지가 names 인자에 해당하는지 헷갈려 명확한 구분을 위해 뒤에 있는 msg 매개변수에 named parameter를 사용하도록 강제할 것이다.

Destructuring

Python의 unpacking과 비슷한 개념인 것 같다.

fun getFullName() = Triple("John", "Quincy", "Adams")

val result = getFullName()
val firstName = result.first
val middleName = result.second
val lastName = result.third

println("$firstName $middleName $lastName") // John Quincy Adams

이런식의 긴 코드를

val (firstName, middleName, lastName) = getFullName()

println("$firstName $middleName $lastName") // John Quincy Adams

이렇게 간결하게 작성할 수 있다.

참고로 Triple은 코틀린에서 3개의 객체를 저장할 수 있는 라이브러리이다. first, second, thirtd 프로퍼티를 사용해 해당 순서의 값을 가져올 수 있다. 비슷한 개념으로 Pair가 있다.

파이썬과 똑같이 _ 연산자를 사용해 값을 skip 할 수 있다 (단, destructuring되는 순서는 지켜야 한다)

val (first, _, last) = getFullName() 

println("$first $last") //John Adams

val (_, _, last) = getFullName() 

println(last) //Adams

val (_, middle) = getFullName() 

println(middle) //Quincy
profile
주니어 개발쟈🤦‍♂️

0개의 댓글