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

Sdoubleu·2023년 3월 8일
0

Kotlin in Action

목록 보기
2/9
post-thumbnail

3장에서 다루는 내용

  • 컬렉션, 문자열, 정규식을 다루기 위한 함수
  • 이름 붙인 인자, 디폴트 파라미터 값, 중위 호출 문법 사용
  • 확장 함수와 확장 프로퍼티를 사용해 자바 라이브러리 적용
  • 최상위 및 로컬 함수와 프로퍼티를 사용해 코드 구조화

3.1 코틀린에서 컬렉션 만들기

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

여기서 만든 객체가 어떤 클래스에 속하는지알려면 ?
>>> println(set.javaClass)
class java.util.HashSet

>>> println(list.javaClass)
class java.util.ArrayList

>>> println(map.javaClass)
class java.util.HashMap
  • 코틀린이 자바 컬렉션을 제공하지 않는 이유
    -> 자바에서 코틀린 함수를 호출하거나 코틀린에서 자바 함수를 호출할 때 자바와 코틀린 컬렉션을 서로 변환할 필요가 없다

3.2 함수를 호출하기 쉽게 만들기

디폴트 구현과 달리 (1;2;3) 처럼 원소 사이를 세미콜론으로 구분하고 괄호로 리스트를 둘러싸고 싶다면?

// joinToString() 함수의 초기 구현
fun <T> joinToString (
	collection: Colletion<T>,
    separator: String,
    prefix: String,
    postfix: String
): String {
	val result = StringBuilder(prefix)
    for( (index, element) in collection.withIndex()) {
    	if (index > 0) result.append(separator)
        result.append(element)
    }
    
    result.append(postfix)
    return result.toString()
}

3.2.1 이름 붙인 인자

  • 일부 코딩 스타일에서는 파라미터 이름을 주석에 넣으라고 요구하기도 한다
joinToString(collection, separator = " ", prefix = " ", postfix = ".")
  • 코틀린으로 작성한 함수를 호출할 때는 함수에 전달하는 인자 중 일부의 이름을 명시할 수도 있다

  • 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기위해 그 뒤에 오는 모든 인자는 이름을 명시해야 한다

3.2.2 디폴트 파라미터 값

  • 인자 중 일부가 생략된 오버로드 함수를 호출할 때 어떤 함수가 불릴지 모호한 경우가 생긴다
    -> 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로 이런 오버로드 중 상당수를 피할 수 있다
// 디폴트 파라미터 값을 사용해 joinToString() 정의
fun <T> joinToString (
	collection: Colletion<T>,
    separator: String = ", ",
    prefix: String= "",
    postfix: String = ""
): String {
	...
}

>>> joinToString(list, ", ", "", "")
1, 2, 3

>>> joinToString(list)
1, 2, 3

>>> joinToString(list, ";")
1; 2; 3
  • 이름 붙인 인자를 사용하는 경우에는 인자 목록의 중간에 있는 인자를 생략하고, 지정하고 싶은 인자를 이름을 붙여서 순서에 관계없이 지정 가능하다

  • 함수의 디폴트 파라미터 값은 함수를 호출하는 쪽이 아닌
    함수 선언 쪽에서 지정된다

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

파일에 대응하는 클래스의 이름 변경하기
코틀린 최상위 함수가 포함되는 클래스의 이름을 바꾸고 싶다면 파일에 @JvmName 애노테이션을 추가하라
-> 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다

@file:JvmName("StringFunctions") <- 클래스 이름을 지정하는 애노테이션
package strings
fun joinToString( ... ): String { ... }

호출 예시
ex) 자바
import strings.StringFunctions;
StringFunctions.joinToString(list, ", ", "", "");


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

  • 확장 함수
    어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다

-> 기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있게 하는 역할

// 어떤 문자열의 마지막 문자를 돌려주는 메소드
package strings
fun String.lastChar(): Char = this.get(this.length - 1)

// String <- 수신 객체 타입
// this <- 수신 객체

>>> println("Kotlin".lastChar())
n
// Kotlin <- 수신 객체
  • 확장 함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다

  • 클래스 이름을 수신 객체 타입
    확장 함수가 호출되는 대상이 되는 값(객체)을 수신 객체

  • this를 쓸 수도 생략할 수도 있다

fun String.lastChar(): Char = get(length - 1)
  • 확장 함수 내부에서는 수신 객체의 메소드나 프로퍼티를 바로 사용할 수 있다
    -> 확장 함수가 캡슐화를 깨지는 않는다

  • 확장 함수안에서는 private/protected 멤버를 사용할 수 없다

3.3.1 임포트와 확장 함수

  • 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 한다
1. 기본
import strings.lastChar
val a = "Kotlin".lastChar()

2. *를 사용한 임포트
import strings.*
val a = "Kotlin".lastChar()

3. as 키워드를 사용한 임포트
import strings.lastChar as last
val a = "Kotlin".last()

-> 이름을 바꿔서 임포트하면 이름 충돌을 막을 수 있다

3.3.2 자바에서 확장 함수 호출

  • 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다
    -> 확장 함수를 호출해도 다른 어댑터 객체나 실행 시점 부가 비용이 들지 않는다

3.3.3 확장 함수로 유틸리티 함수 정의

  • 확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의일 뿐
    -> 그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수 있다

3.3.4 확장 함수는 오버라이드할 수 없다

  • 확장 함수는 클래스의 밖에 선언된다
// 확장 함수는 오버라이드할 수 없다
fun View.showOff() = pritnln("I'm a view!")
fun Button.showOff() = pritnln("I'm a button!")

>>> val view: View = Button()
>>> view.showOff() <- 확장 함수는 정적으로 결정된다
I'm a view!

↪ view가 가리키는 객체의 실제 타입이 Button이지만 이 경우 view의 타입이 View이기 때문에 무조건 View의 확장 함수가 호출된다

확장 함수를 첫 번째 인자가 수신 객체인 정적 자바 메소드로 컴파일한다는 사실을 기억하면 이런 동작을 쉽게 이해 가능

3.3.5 확장 프로퍼티

  • 확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문을 사용할 수 있는 API를 추가할 수 있다

  • 확장 프로퍼티는 아무 상태도 가질 수 없다

// 확장 프로퍼티 선언하기
val String.lastChar: Char
	get() = get(length - 1)
    
// 변경 가능한 확장 프로퍼티 선언하기
var StringBuilder.lastChar: Char
	get() = get(length -1) <- 프로퍼티 게터
    set(value: Char) {
    	this.setCharAt(length - 1, value) <- 프로퍼티 세터
    }

>>>println("Kotlin".lastChar)
n
>>>val sb = StringBuilder("Kotlin?")
>>>sb.lastChar = '!'
>>>println(sb)
Kotlin!
  • 확장 함수의 경우와 마찬가지로 확장 프로퍼티도 일반적인 프로퍼티와 같은데, 단지 수신 객체 클래스가 추가됐을 뿐이다
    -> 뒷받침하는 필드가 없어서 기본 게터 구현을 제공할 수 없으므로 최소한 게터는 꼭 정의해야 한다

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

코틀린 언어 특성

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

3.4.2 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의

  • 배열에 들어있는 원소를 가변 길이 인자로 넘길 때 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달하게 해야하는데
    -> 스프레드 연산자가 그런 작업을 도와준다
fun main(args:Array<String>) {
	val list = listOf("args: *args) 
    // 스프레드 연산자가 배열의 내용을 펼쳐준다
}

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

  • 중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다
val map = mapOf(1 to "one", 2 to "two")

// 두 호출은 동일하다
1.to("one") <- "to" 메소드를 일반적인 방식으로 호출
1 to "one" <- "to" 메소드를 중위 호출 방식으로 호출
  • 인자가 하나뿐인 일반 메소드/확장 함수에 중위 호출을 사용할 수 있다

  • 함수를 중위 호출하고 싶으면 infix 변경자를 함수 앞에 선언해야 한다

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

val (number, name) = 1 to "one"
  • Pair의 내용으로 두 변수를 즉시 초기화할 수 있다
    -> 이런 기능을 구조 분해 선언이라 한다

  • to 함수는 확장 함수
  • to를 사용하면 타입과 관계없이 임의의 순서쌍을 만들 수 있다
    -> to의 수신 객체가 제네릭하다는 뜻

3.5 문자열과 정규식 다루기

  • 코틀린 코드가 만들어낸 문자열을 아무 자바 메소드에 넘겨도 되고, 자바 코드에서 받은 문자열을 아무 코틀린 표준 라이브러리 함수에 전달해도 전혀 문제 ❌

3.5.1 문자열 나누기

  • 코틀린에서는 자바의 split 대신에 여러 가지 다른 조합의 파라미터를 받는 split 확장 함수를 제공함으로써 혼동을 야기하는 메소드를 감춘다

  • 정규식을 파라미터로 받는 함수는 String이 아닌
    Regex 타입의 값을 받는다

// 마침표(.) , 대시(-)로 문자열을 분리하는 예시
println("12.345-6.A".split("\\.|-".toRegex())) 
                              ↪ 정규식을 명시적으로 만듦
. 을 \\ 로 이스케이프 하고 | 는 or의 의미를 나타낸다
그러므로 .- 를 분리시킨다
[12, 345, 6, A]

정규식 안에서 마침표가 와일드카드 문자와 아닌 문자 자체로 쓰이게 하기 위해서 마침표를 이스케이스 시켰다

// 간단한 경우에는 꼭 정규식 쓸 필요 X
// 두 가지 모두 동일한 결과
>>>println("12.345.6.A".split(".","-"))
>>>println("12.345.6.A".split('.','-'))

3.5.2 정규식과 3중 따옴표로 묶은 문자열

  • 3중 따옴표 문자열을 이용하여 정규식을 사용하면
    역슬래시()를 포함한 어떤 문자도 이스케이프할 필요가 없다
    -> 3중 따옴표 문자열에서는 . 라고 쓰면 된다
ex)
val regex = """(.+)/(.+)\.(.+)""".toRegex()

첫 번째 그룹인 (.+)는 마지막 슬래시까지 모든 문자와 매치
마지막 슬래시를 제외한 모든 슬래시가 들어간다

두 번째 그룹은 마침표 전까지 모든 문자와 매치

세 번째 그룹은 나머지 모든 문자와 매치

3.5.3 여러 줄 3중 따옴표 문자열

  • 3중 따옴표 문자열을 문자열 이스케이프를 피하기 위해서만 사용하지 않는다

  • 줄 바꿈을 표현하는 아무 문자열이나 그대로 들어간다

  • 여러 줄 문자열을 코드에서 더 보기 좋게 표현하고 싶으면
    -> trimMargin을 사용해서 그 문자열과 그 직전의 공백을 제거

  • 3중 따옴표 문자열 안에 문자열 템플릿을 사용할 수도 있다
    -> 3중 따옴표 문자열 안에서는 이스케이프를 할 수 없기 때문에
    $를 넣어야 한다면

val price = """${'$'}99.9""" 처럼 템플릿 안에 '$'를 써야함

3.6 코드 다듬기: 로컬 함수와 확장

  • 로컬 함수를 써서 코드를 더 깔금하게 유지하면서 중복을 제거할 수 있다

📌참고자료

정규 표현식 정리1
정규 표현식 정리2

profile
개발자희망자

0개의 댓글