선생님, 개발을 잘하고 싶어요.

Kotlin in Action - 1부 3장 - 함수 정의와 호출 본문

일상/책 리뷰

Kotlin in Action - 1부 3장 - 함수 정의와 호출

알고싶은 승민 2022. 5. 18. 22:41

다루는 것

  • 함수 정의, 호출
  • 확장 함수, 확장 프로퍼티
  • 컬렉션, 문자열, 정규식 범위에서 다룰 예정

코틀린 컬렉션

코틀린에서 컬렉션을 만드는 건 쉽다. 최상위 함수가 미리 정의되어있다.

listOf(1,2,3) // java.util.ArrayList 타입

setOf("seungmin", "greedy") // java.util.HashSet 타입

hashMapOf(1 to "one", 7 to "seven") // java.util.HashMap 타입

그렇게 만들어진 컬렉션 타입을 자세히 보면 자바의 컬렉션을 그대로 사용한다는 걸 확인할 수 있다.

이로 인해서 얻는 장점은?

  1. 자바 코드와 상호작용이 쉽다. - 자바에서 코틀린 함수 호출하거나 그 반대의 상황에서도 컬렉션을 상호 변환할 필요가 없다. (상호작용을 위한 불필요한 객체 생성이 필요없다는 것)

이름 붙인 인자

함수 호출의 가독성을 높이고 싶다.

fun <T> joinToString(
  collection: Collection<T>,
  separator: String,
  prefix: String,
  postfix: String
) { ... }

이런 함수가 있다고 해보자. 구분자, 접두사, 접미사를 모두 공백으로 하고 호출하고 싶다면 어떻게 될까?

joinToString(collection, "", "", "")

코드만 보고서는 각 공백이 무슨 의미인지 알 수 없다. 함수 시그니처를 보고나서야 이해할 수 있지만 그나마 햇갈릴 가능성이 있다.

코틀린에선 함수 파라미터에 이름을 붙혀서 전달할 수 있다.

joinToString(collection, separator = "", prefix = "", postfix = "")
// 123

아쉽게도 자바로 작성된 코드를 호출할 때는 이름 붙힌 인자를 사용할 수 없는데 그 이유는 코틀린과 호환되는 JDK6 버전에서는 클래스 파일 (.class 파일)에 함수 파라미터 정보가 없기 때문이다.

디폴트 파라미터 값

자바에서는 일부 클래스에서 오버로딩한 메서드가 많아지는 문제가 있다.

코틀린은 함수 선언에서 파라미터 디폴트 값을 지정할 수 있어 이런 오버로드 중 상당수를 피할 수 있다.

이름 붙힌 인자와 함께 사용하면 그 효용이 배가된다.

fun <T> joinToString(
  collection: Collection<T>,
  separator: String = ", ",
  prefix: String = "",
  postfix: String = ""
) { ... }
joinToString(collection, postfix = ";", prefix = "# ")
// # 1, 2, 3;

자바에는 디폴트 파라미터 값이라는 개념이 없으므로 디폴트 값이 있는 코틀린 코드를 사용하기 위해서 코틀린 함수 정의에 @JvmOverloads어노테이션을 붙힌다. 이 어노테이션을 붙히면 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩 자바 메서드를 추가한다. 생략된 파라미터의 기본값은 구현 과정에서 사용된다.

정적 유틸리티 클래스 없애기: 최상위 함수, 최상위 프로퍼티

자바의 제약 때문에 개발을 하다보면 정적 메서드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메서드가 없는 클래스가 생긴다.

이는 무의미한 클래스로, 코틀린에선 이런 클래스를 만들 필요가 없다.

그냥 .kt 파일에 최상위 함수를 정의할 수 있기 때문이다.

join.kt 라는 함수에 정의된 joinToString을 자바에서 사용하려면?

JoinKt.joinToString(...) // kt 파일 이름 기반으로 정적 함수를 가지고 있는 클래스를 만든다.

// join.kt 파일 상단에 이렇게 생성되는 클래스 이름을 지정할 수 있다.
@file:JvmName("StringFuntions")

StringFuntions.joinToString(...) 

프로퍼티도 최상위에 둘 수 있다.

val → 최상위 getter 생성

var → 최상위 getter, setter 생성

만약 상수를 최상위에 정의하고 싶다면? 상수인데 getter를 통해서 접근하는 게 어색하다면?

자바의 public static final 필드로 선언하면 된다. 코틀린에선 const val를 사용하자.

메서드를 다른 클래스에 추가: 확장 함수, 확장 프로퍼티

기존 자바 API를 재작성하지 않고도 코틀린이 재공하는 여러 편리한 기능을 사용할 수 있을까? 그렇다! 바로 확장 함수(extension function)가 그런 기능을 제공한다.

확장 함수는 임의의 클래스의 멤버 메서드인 것처럼 호출할 수 있지만, 그 클래스 밖에 선언된 함수다.

확장할 클래스를 수신 객체 타입(receiver type)이라고 부르고, 확장 함수 호출되는 대상을 수신 객체(receiver object)라고 부른다.

 

 

직접 작성한 코드도 아니고, String 클래스의 소스코드를 소유하지도 않았지만, 원하는 메서드를 String에 추가할 수 있다!

자바 클래스로 컴파일한 클래스 파일만 있으면 그 어떤 JVM 언어로 작성된 클래스도 확장 함수를 정의할 수 있다.

확장 함수에서는 private, proected 멤버에 접근할 수 없다. 캡슐화를 깨지 않는다는 뜻

호출하는 쪽에서는 확장 함수나 멤버 메서드를 구분할 필요가 없다.

내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메서드다.

StringUtilKt.lastChar("Java"); // 최상위 함수랑 같은 방식으로 정적 메서드 접근해서 함수 호출

확장 함수는 정적 메서드와 같은 특징을 가지므로, 하위 클래스에서 오버라이드 할 수 없다.

컴파일 타임에 호출될 함수를 결정하는 정적 디스패치가 적용된다.

fun View.showOff() = println("난 뷰")
fun Button.showOff() = println("난 버튼")

val view: View = Button() // 컴파일 시 타입은 View 런타임에서 객체 타입은 Button
view.showOff() // 난 뷰

확장 함수와 멤버 함수의 시그니처가 같다면? → 멤버 함수를 우선한다.

만약 라이브러리에서 우리가 정의한 확장 함수 시그니처와 같은 멤버 함수를 추가했다면 우리 코드는 망가질 것

가변 길이 인자

fun listOf<T>(vararg values: T): List<T> { ... }

호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있음

메서드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 컴파일러가 배열에 그 값을 넣어주는 기능이다.

코틀린에선 스프레드 연산자(spread)가 있어서 배열을 명시적으로 풀어서 vararg에 전달하는 게 가능하다.

val list = listOf("args: ", *args) // 배열을 명시적으로 풀기 때문에 배열 인자 이외에 다른 것과 함께 vararg에 전달 가능

중위 호출, 구조 분해 선언

infix 키워드를 붙히고 와 인자가 하나인 함수여야함.

infix fun Any.to(other: Any) = Pair(this, other)

Pair로부터 두 변수를 동시에 초기화 하고 싶을 때 다음처럼 함.

val (number, name) = 1 to "one"

 

문자열

코틀린 문자열은 자바의 문자열과 같아서 상호운용시 성능상 문제가 없다.

로컬 함수

함수에서 추출한 함수를 원 함수 내부에 중첩할 수 있다.

fun outerFunction(outer: Int) {
  fun innerFunction() {
    println(outer) // 본인을 감싸고 있는 스코프의 변수에 접근 가능
  }

  innerFunction() // 일반 함수처럼 호출 가능
}
Comments