책을 읽겠습니다!/Kotlin in Action

[Kotlin in Action] 3장 함수 정의와 호출

Unagi_zoso 2023. 9. 25. 14:42

코틀린에서 컬렉션 만들기

val set = hashSetOf(1, 7, 53)
val list = arrayListOf(1, 7, 53)
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

to가 키우드가 아닌 일반 함수다.

println(set.javaClass) // getClass()에 해당

상호운용을 위해 자바의 콜렉션을 사용하는데 하지만 코틀린은 더 많은 기능이 가능. 예를 들어 리스트의 마지막 원소를 가져오거나 수로 이뤄진 컬렉션에서 최댓값을 찾을 수 있다.

그래도 코틀린에서 볼 수 있는 콜렉션을 쓰는게 코틀린답다한다.

val strings = listOf("first", "second", "fourteenth")
println(strings.last())
val numbers = setOf(1, 14, 2)
println(numbers.max())

코틀린엔 함수 인자 담을 때 파라미터 명도 넣어서 할 수 있다. 이건 jdk 8 이후 추가된 선택 특징, 코틀린 JDK 6와 호환되서 코틀린 컴파일러는 인식할 수 없다. 자바로 작성한 코드에는 파라미터 이름을 붙이지 못한다.

중요하다 인수만 보면 뭘 의미하는지 몰라. 시그니쳐를 봐야하는데 IDE에서 지원할 수 있지만 함수호출코드 자체는 모호하다. 불리언 플래그 전달할 때 더 부각되는데 자바에서는 불리언 댇신 이넘을 쓰라한다. 아니면 주석으로 파라미터 값을 앞에 적기도 한다.

하나 명시하면 나머지도 다 명시해야해.

joinToString(collection, separator = " ", prefix = " ", postfix = ".")

불리언이나 디폴트 패러미터에 유리하다.

디폴트 파라미터 값

자바에서는 일부 클래스에서 오버로딩한 메소드가 너무 많아진단 문제 있다. 디폴트 값을 지정해 개선 가능

일반 호출 문법을 사용하려면 선언시 같은 순서로 인자 지정. 이름 붙은 인자를 사용할 경우 목록 중간 인자 생략하고 지정하고 싶은 인자 붙이면 순서 무시 가능.

자바에는 디폴트 파라미터가 없어 코틀린 함수를 자바가 호출하면 모든 인자 명시해야 한다. 코틀ㄹ니 코드에 @JvmOverloads 애노테이션 함수에 추가하면 파라미터 경우 다 고려해 만들어준다.

이름 적은거 없이 일부 생략하면 뒤에껀 디폴트로 채워준다. 이름붙여서 사용하면 걍 순서 짬뽕으로 해도 ㄱㅊ
함수호출하는 쪽이 아닌 정의한 쪽에서 정하기에 재컴파일 시 디폴트값을 사용한 함수들은 다 바뀐다. 눈뜨고 코벵이기 가능하다고

코틀린은 클래스 안에 함수 선언할 필요 없다

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

어느 한 클래스에 포함하기 어려운 코드 같은거 위해 따로 정적 메소드 모아두는 클래스 생긴다. Collections 클래스처럼
코틀린에선 함수를 직접 소스파일의 최상위 수준, 모든 다른 클래스의 밖에 위치 시키면 된다. 그럼 패키지의 멤버함수이니 사용할 수 있따. 패키지만 임포트하면된다. 소스파일의 이름. 유틸리티 클래스의 이름이 추가로 들어갈 필요는 없다. 이게 어떻게 구현된걸까. 내부에서

public final class ps.NyaKt {
  public static final void how();
}

컴파일하니 이렇게 뜨네. java로 치면 기냥 소스파일이름으로 클래스 만들어서 정적메서드을 만드는게 아닌가?

뭐야 책에 다 적혀져 있네
파일 대응하는 클래스이름을 바꾸려면 파일의 맨 앞에 @file:JvmName("이름")을 적어라.
프로퍼티도 가능하다.
val의 경우 게터, var의 경우 게터 세터가 생긴다. 뭔가 접근자를 써야한느게 이상하다. const 변경자를 추가하면 프로퍼티를 public static final 필드로 컴파일하게 만든다. 원시타입과 String 타입의 프로퍼티만 const 지정가능

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

기존 자바 API 재작성 없이 코틀린 제공 기능 사용할 수 있다면?
확장 함수로 가자. 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수.
확장 함수는 추가하려는 함수 이름 앞에 그 함수가 확장할 이름을 덧붙이면 된다. 클래스 이름을 수신 객체 타입, 확장 함수가 호출되는 대상이 되는 값을 수신 객체라고 부른다. 인스턴스인 셈

확장 함수에서도 this를 생략할 수 있다.

확장 함수를 깨지도 않는다. 클래스 내부에서만 사용할 수 있는 프라이빗, 프로텍티드 멤버를 사용할 수 없다.

확장 함수 쓰려면 iport 해야한다. 막쓸 수 있으면 한 클래스에 같은 이름의 확장 함수가 둘 이상 있어서 이름이 충돌하는 경우 자주 생긴다.

일반적인 클래스나 함수라면 Fully Qualified Name을 써서 충돌 피할 수 있는데. 코틀린 문법상 확장 함수는 반드시 짧은 이름을 써야한다.

따라서 임포트시 이름을 바꾸는 것이 확장함수 충돌 막는 유일한 방법

import strings.lastChar as last

내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다. 그래서 확장 함수를 호출해도 다른 어댑터객체나 실행 시점 부가 비용이 들지 않는다. 그러니 자바에서 사용할 때 첫 인자로 수신 객체를 넘기면 된다.

이걸 사옥한 앧 쓸 수 있고, 마치 클래스의 멤버처럼 호출 가능

확장 함수는 정적 메소드 호출에 대한 문법적 편의 신텍틱 슈가. 클래스가 아닌 더 구체적 객체타입 지정원할 수 있다.

fun Collection<String>.join(인수) = joinToString(인수)

오버라이드 못한다.

실행 시점에 객체 타입에 따라 동적으로 호ㅜㄹ될 대상 메소드를 결정하는 방식을 동적 디스패치, 컴파일 시점에 알려진 변수 타입에 의해 정해진 메소드 호출 방식을 정적 디스패치

확장 함수는 클래스의 일부가 아니다. 클래스 밖에 선언된다. 오버라이드 안되니까 상위 클, 하위 클한테 달아도 정적인 타이밍에 수신 객체로 지정한 변수의 정적 타입에 의해 디스패치된다.

확장함수와 멤버함수의 이름이 같다면 (충돌 안생겨?) 멤버함수 손든다. 왜 이 둘이 겹칠 때 못잡아내냐.. 의문

확장 프로퍼티를 사용하면 상태를 저장할 적절한 방법이 없다. 기존 클래스의 인스턴스 객체에 필드 추가 불가.
하지만 프로퍼티 문법으로 더 짧게 코드 작성 가능

val String.lastChar: Char
    get() = get(length - 1)

좋네. lastIndex도 이런건가? 잘 만들었다.

컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원

컬렉션 처리 시 사용 코틀린 표준 라이브러리 함수.

  • vararg 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있다.
  • 중위infix 함수 호출 구문을 사용하면 인자가 하나뿐인 메소드를 간편하게 호출할 수 있다.
  • 구조 분해 선언 destructuring declaration 을 사용하면 복합적인 값을 분해해서 여러 변수에 나눠 담을 수 있다.

확장 함수가 같이 쓰이는 것이다.

fun <T> List<T>.last() : T { /* 마지막 원소 */ }
fun Collection<Int>.max() : Int { /* 컬렉션의 최댓값을 찾음 */ }

컬렉션 만들어내는 함수 몇 가지 있는데 특징이 인자의 개수가 그때그때 달라질 수 있다.

가변 인자 함수: 인자의 개수

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

자바는 타입 뒤 ... 코틀린은 파라미터 앞 vararg

이미 배열에 있는 원소를 가변길이인자로 넘길 때 방식 다르다. 자바 배열 걍 넘김. 코틀린은 배열을 풀어서 던진다. 기술적으로는 스프레드 연산자가 한다.

fun main (args: Array<String>) {
    val list = listOf("args: ", *args)
    println(list)
}

스프레드 쓰면 배열 안의 값과 다른 여러 값 함께 써서 함수 호출 가능.

값의 쌍 다루기 : 중위 호출과 구조 분해 선언

val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

to는 코틀린 키워드가 아닌 중위 호출 infix call 이란 특별한 방식으로 to라는 일반 메소드를 호출한 것.

중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다. (이때 객체, 메소드 이름, 유일한 인자 사이에 공백 필요)

.l.to("one")
l to "one"

인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 ㅎ마수에 중위 호출을 사용할 수 있다. 인자가 하나여야한다.
함수를 종위 호출에 사용하게 허가하려면 infix 변경잘ㄹ 함수 선언 앞에 추가해야 한다.

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

이 to 함수는 Pair의 인스턴스를 반환한다. Pair는 코틀린 표준 라이브러리 클래스다. to는 제네릭 함수지만 페어 반환한다.

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

이런 기능을 구조 분해 선언이라고 부른다.

코틀린이 맵에 대해 제공하는 새로운 문법이 아닌 일반적인 함수를 더 간결한 구문으로 호출하는 것

문자열과 정규식 다루기

자바의 split 메소드는 .으로 처리할 수 없다. 실은 정규식으로 해석되고 .는 모든 문자를 나타낸다는 의미다.

정규식 들어가는건 정규식만 받게 만들었다 코틀린 스트링을 toRegex 해서 정규식을 명시적으로 만들 수 있다.
자바와 다르게 split(".", "-")으로 구분 문자열을 여러 개 받을 수 있다.

정규식과 삼 중 따옴표

""" """ 하면 이스케이프 문자 필요없다

정규식이 어떻게 찾는지도 알아보면 좋다. (.+)로 한정짓지 않으면 참되는 가장 큰 케이스 고려한다. 레이지 연산마냥 다 잘라놓은 다음에 결정하는건가.

여러 줄 3 중 따옴표 문자열

아스키 아트도 뚝딱
"""ㅁㄴㅇㄹ
"""
줄 바꿈, 들여쓰기 다 알아듣나봐.

좋은 코드에는 반복이 적다. Don't Repeat Yoursef

코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다. 문법적 부가 비용 없이 깔끔하게 코드 조직.
함수 안에 함수를 또 넣어버리네

중복 줄이기 가능. 로컬 ㅎ마수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.

코드를 확장 함수로 뽑아내는 기법 도가능하다.

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty*(( {
        }
    }
}

user를 사용하는 다른 곳에서는 사용하지 않을 기능이라 User에 포함시키고 싶지는 않지만 User를 간결하게 유지하면 생각해야 할 내용이 줄어들고 더 쉽게 코드 파악 가능하다. 한 객체만 다루면서 객체의 비공개 데이터를 다룰 필요는 없는 함수는 확장 함수로 만들어 객체.멤버처럼 수신 객체를 지정하지 않고도 공개된 멤버 프로퍼티나 메소드에 접근 가능

너무 중첩 시키면 읽기 힘들어서 한 단계만 함수를 중첩시켜라한다.

요약

  • 코틀린은 자체 컬렉션 클래스를 정의하지 않지만 자바 클래스를 확장해서 더 풍부한 API를 제공한다.
  • 함수 파라미터의 디폴트 값을 정의하면 오버로딩한 함수를 ㄹㄹ정의할 필요성이 줄어든다. 이름붙인 인자를 사용하면 함수의 인자가 많을 때 함수 호출의 가독성을 더 향상 시킬 수 있다.
  • 코틀린 파일에서 클래스 멤버가 아닌 최상위 함수와 프로퍼티를 직접 선언할 수 있다. 이를 활용하면 코드 구조를 더 유연하게 만들 수 있따.
  • 확장 함수와 프로퍼티를 사용하면 외부 라이브러리에 정의된 클래스를 포함해 모든 클래스의 API를 그 클래스의 소스코드를 바꿀 필요 없이 확장할 수 있다. 확장 함수를 사용해도 실행 시점에 부가 비용이 들지 않는다.
  • 중위 호출을 통해 인자가 하나 밖에 없는 메소드나 확자아 함수를 더 깔끔한 구문으로 호출할 수 있다.
  • 코틀린은 정규식과 일반 문자열을 처리할 때 유용한 다양한 문자열 처리 함수를 제공한다.
  • 자바 문자열로 표현하려면 수많은 이스케이프가 필요한 문자열의 경우 3중 따오묘 문자열을 사용하면 더 깔끔하게 표현할 수 있다. API로 던지면 어떻게 전송되려나
  • 로컬 함수를 써서 코드를 더 깔끔하게 유지하면서 중복을 제거할 수 있다.