231215 TIL #269 Kotlin #4 함수의 정의와 호출

김춘복·2023년 12월 15일
0

TIL : Today I Learned

목록 보기
269/543
post-custom-banner

Today I Learned

오늘은 코틀린 인 액션 3장을 공부했다.


함수

디폴트 파라미터 값

Java에서는 일부 클래스에서 오버로딩한 메서드가 너무 많다는 문제가 있다.
하지만 코틀린에서느 함수 선언에서 파라미터의 디폴트값을 지정할 수 있어 오버로딩을 상당수 줄일 수 있다.

fun <T> joinToString(
	collection: Collection<T>,
    separator: String = ", ", // 디폴트값 지정.
    prefix: String = "",
    postfix: String = ""
): String
/*
joinToString(list, ", ", "", "")
joinToString(list)
joinToString(list, "; ")
맨 위처럼 쓸것을 아래처럼 써도 빈 값들은 기본값들이 채워주게 된다.
*/
  • 단, 자바에서는 디폴트 파라미터 값이라는 개념이 없기 때문에 자바에서 코틀린함수를 호출할 땐, 모든 인자를 명시해야 한다.

최상위 함수 & 최상위 프로퍼티

자바에서는 모든 필드와 메서드가 클래스 안에 작성되어야 하지만, 코틀린에서는 클래스 밖에 함수와 필드를 쓸 수 있다.

  • 아래와 같은 코드를 코틀린의 join.kt 파일로 저장했다고 할 때,
package strings
fun joinToString(...): String{...}
  • 위의 코틀린 코드는 아래의 자바 코드와 일치한다.
    즉, 코틀린 파일의 모든 최상위 함수는 코틀린 소스 파일의 이름을 한 클래스 안에 static 메서드가 된다.
    그러므로 자바에서 위의 코틀린 최상위 함수를 사용하려한다면 JoinKt를 import 해야한다.
package strings;
public class JoinKt {
	public static String joinToString(...) {...}

}
  • 함수 뿐만 아니라 프로퍼티(자바에서 필드)도 같은 방법으로 최상위 프로퍼티로 클래스 밖에 정의가 가능하다. 해당 프로퍼티는 코틀린 소스파일 이름의 클래스 안에 정적 필드로 저장된다.

  • 최상위에 선언한 변수나 클래스의 멤버 변수는 선언과 동시에 초기화를 해야한다.
    함수 내부의 변수는 선언만 해도 된다.


확장함수

확장함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스 밖에 선언된 함수이다.
새로운 함수를 마치 해당 클래스의 멤버인 것처럼 사용할 수 있다.
기존 자바 api를 재작성하지 않고도 코틀린의 기능을 이용할 수 있게 해주며, 기존 자바 프로젝트와의 호환에 기여한다.

fun receiverType.functionName(parameters): ReturnType {
    // 함수 본문
}
// 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다.

fun String.printLength() {
    println("Length of the string : ${this.length}")
}

fun main(){
	"Kotlin".printLength()
    // 출력 : Length of the string : 6
}
  • 수신 객체 타입(receiver type) : 확장할 클래스의 이름.

  • 수신 객체(receiver object) : 확장 함수가 호출되는 대상이 되는 값(객체).
    위의 예시에서 this와 "Kotlin"

  • 메서드 호출은 다른 일반 클래스 멤버 호출방식과 같다.

  • 확장함수 안에서는 클래스 내부에서만 사용할 수 있는 private, protected 멤버를 사용할 수 없다. 캡슐화를 깨지는 못한다.

  • 확장함수를 정의했다해도 자동으로 프로젝트 안의 모든 소스코드에서 사용할 수는 없다.
    당연히 다른 클래스와 함수처럼 import를 해야 사용할 수 있다.

  • import strings.lastChar as last 처럼 as를 사용해 import한 클래스나 함수를 다른 이름으로 부를 수 있다. 이름 충돌을 막기 위함이다.
    코틀린 문법상 확장함수는 짧은 이름을 쓰는걸 권장한다.

char c = StringUtilKt.lastChar("Java");
// StringUtil.Kt 파일에 확장함수를 정의했을 경우. "Java"가 수신 객체.
  • Java에서 위의 확장함수를 사용하기 위해선 최상위 함수처럼 확장함수가 들어있는 파일 이름을 사용하면 된다. 그리고 첫번째 파라미터로 사용하려는 수신 객체를 넘기면 된다.

  • 확장함수는 오버라이드할 수 없다. 클래스의 일부가 아니라 클래스 밖에 선언되기 때문이다.


확장 프로퍼티

틀린에서 기존 클래스에 새로운 프로퍼티를 추가하기 위한 메커니즘
해당 클래스의 인스턴스에 마치 기존에 정의된 것처럼 새로운 프로퍼티를 사용할 수 있다.

  • 선언
    확장하려는 클래스의 이름 다음에 프로퍼티의 이름을 작성하고 타입을 지정한다.
    기본 게터 구현은 제공할 수 없으므로 최소한 게터는 정의를 따로 해야한다.(var은 세터도 정의 가능)
val String.customLength: Int
    get() = length + 10
  • 확장 프로퍼티는 실제로는 클래스에 새로운 멤버를 추가하는 것이 아니라, 해당 클래스의 인스턴스에 대해 정적인 함수로 호출된다. 확장 프로퍼티에서는 backing field(프로퍼티의 실제 값을 저장하는 데 사용되는 내부 변수)를 직접 정의할 수 없다.

컬렉션

코틀린은 자체적인 컬렉션 라이브러리는 제공하지 않는다.
대신 자바의 표준 컬렉션을 활용하는데, 자바와 상호작용하기 쉽게 하기 위해서이다.

선언

구현체Of(요소)로 간단하게 만들 수 있다. map은 key to value로 만들 수 있다.

    val set = hashSetOf(1,7,42)
    val list = arrayListOf(1,7,42)
    val map = hashMapOf(1 to "one", 7 to "seven", 42 to "fourty-two")

컬렉션 처리

  • Java 컬렉션 API 확장
    java에는 없던 max(), last()같은 함수는 List클래스에서 코틀린의 확장 함수로 구현한 것이다.
    println(list.max()) // 42, 컬렉션의 최대값을 반환해주는 메서드
    println(list.last()) // 7. index 가장 마지막 원소를 반환하는 메서드
    println(list) // [1, 42, 7]. toString()을 명시하지 않아도 호출해준다.

가변 인자 함수(vararg function)

함수에 임의 개수의 인자를 전달할 수 있게 해준다.
함수를 선언할 때 정확한 매개변수의 개수를 지정하지 않고 여러 개의 인자를 전달할 수 있다.
함수를 호출할 때 원하는 개수 만큼 값을 인자로 넘기면 컴파일러가 그 값을 넣어준다.

fun printNumbers(vararg numbers: Int) {
    for (number in numbers) {
        print(number)
    }
}

fun main() {
    printNumbers(1, 2, 3, 4, 5)
}
  • 위의 함수에서 파라미터 앞에 vararg 변경자를 붙여 가변 인자 함수를 작성한다.

  • 스프레드 연산자 *

    코틀린에서 배열이나 리스트 등의 컬렉션을 풀어서 각각의 요소로 전개하거나, 가변 인자 함수에 값을 전달할 때 사용되는 연산자

val array = arrayOf(1, 2, 3)
val expandedArray = arrayOf(*array, 4, 5)
// *array는 1,2,3을 풀어서 전개한 것

printNumbers(*array) // array의 요소를 풀어서 가변인자함수에 전달.

중위호출(infix call)

중위 호출을 사용하면 함수를 일반적인 점 표기법(infix notation)으로 호출할 수 있다.

  • 규칙
  1. 함수는 멤버 함수 또는 확장 함수여야 한다.
  2. 함수는 하나의 매개변수를 가져야 한다.
  3. 함수 이름은 연산자와 같아야 한다.
  • 대표적 예시 to
    Pair 인스턴스를 반환하는 함수. 두 원소로 이루어진 순서쌍. map에서 사용
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// 일반적 사용
val pair1 = 1.to("one")
// 중위 호출 사용. to를 연산자처럼 사용 가능.
val pair2 = 2 to "two"

로컬 함수

다른 함수 내부에서 정의되고 사용되는 함수. 함수 안에 또 다른 함수를 정의한 것

  • 해당 함수 내에서만 유효한 함수로서, 외부에서 직접 접근할 수 없다.
fun calculateAverage(numbers: List<Double>): Double {
    // 로컬 함수 정의
    fun sum(): Double {
        var total = 0.0
        for (number in numbers) {
            total += number
        }
        return total
    }

    fun count() = numbers.size

    // 로컬 함수 호출하여 평균 계산
    return sum() / count()
}

fun main() {
    val numberList = listOf(1.0, 2.0, 3.0, 4.0, 5.0)
    val average = calculateAverage(numberList)
    println("Average: $average")
}
  • 위의 sum()과 count()는 calculateAverage() 안에서만 사용할 수 있다.
    외부에서는 사용할 수 없으므로 main에서는 호출할 수 없다.

  • 로컬함수는 소속된 바깥 함수의 파라미터에 직접 접근할 수 있다.

  • 너무 중첩이 많아지면 가독성이 안좋아지므로 일반적으로는 한단계 이하의 로컬함수가 권장된다.

profile
Backend Dev / Data Engineer
post-custom-banner

0개의 댓글