독서관련/Kotlin in Action

CH03 함수 정의와 호출

ColinKang 2021. 1. 1. 23:17
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.

 

1. 함수정의와 호출

2. 확장함수와 확장프로퍼티 사용하여 자바 라이브러리를 코틀린 스타일로 적용

3. 확장을 통해 자바 라이브러리를 사용

 

컬렉션 만들기

 

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

println(list)

list.toString)

 

print(joinToString(list, ";", "(", ")")

(1; 2; 3)

 

코틀린에서는 명시 파라미터 네임, 이름 붙인 인자, named arguments, default arguments

 

jointToString(collection, separator = " ", prefix = " ", postfix = " ")

 

kotlinlang.org/docs/reference/functions.html

 

Functions: infix, vararg, tailrec - Kotlin Programming Language

 

kotlinlang.org

default arguments value

collection: Collection<T>,

separator: String = ",",

prefix: String = "", // default argument value

postfix: String = "" // default argument value

): String

 

최상위 함수와 프로퍼티 : 기존의 util 클래스를 없애준다.

자바로 컴파일된 것을 보면 컴파일한 파일 명의 class의 static으로 생성됨

@file:JvmName("StringFunctions")

 

import strings.StringFunctions;

StrinFunctions.joinToString(list, "", "", ""); 로 java에서 대응됨

 

최상위 프로퍼티 값은 정적 필드에 해당됨.

const val UNIX_LINE_SEPERATOR = "\n"

 

// java

 

public static final String UNIX_LINE_SEPERATOR = "";

 

자바 라이브러리 코드와 공존하는 코드

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

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

확장 함수 만들기 : 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이면 됨

fun String.lastChar(): Char = this.get(this.length -1) // 수신 객체에 this 없이도 접근 가능하다

클래스 이름 : 수신 객체 타입 : receiver type

확장 함수가 오출 되는 대상이 되는 값(객체) : 수신 객체 : receive object

수신 객체 타입은 확장이 정의될 클래스의 타입이며, 수신 객체는 그 클래스에 속한 인스턴스 객체

>>> println("Kotlin".lastChar()) // String이 수신 객체 타입 "Kotlin"이 수신 객체

>>> n

확장 함수가 캡슐화를 깨지는 않는다. 즉 private, protected 멤버는 사용할 수 없다.

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

// java

char c = StringUtilKt.lastChar("Kotlin")

확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의 임.

확장 함수는 오버라이드 할 수 없다. : static 이기 때문에 

확장 함수를 첫 번째 인자가 수신 객체인 정적 자바 메소드로 컴파일 된다. > 오버라이드가 안된다. > static 메소드는 멤버 메소드가 아니기 때문

 

import로 사용

import strings.lastChar

import strings.*

import strings.lastChar as last

 

확장 프로퍼티 

기존 클래스 객체에 대한 프로퍼티 형식 ( get set ) 의 구문으로 사용할 수 있는 API 추가

 

val String.lastChar: Char

get() = get(length-1)

set(cvalue: Char) {

this.setChar(length-1, cvalue)

}

 

>>> println("Kotlin".lastChar)

n

>>> val sb = StringBuilder("Kotlin?")

>>> sb.lastChar = '!'

>>> println(sb)

Kotlin!

 

자바에서 확장 프로퍼티 사용 : StringUtilKt.getLastChar("Java")

 

컬렉션 처리 : 

 

1. 자바 컬렉션 API 확장 :  확장 함수

2. 가변 인자 함수 : fun listOf<T>(vararg value: T) : List<T> { . . . } // java는 타입 뒤에 ... 코틀린은 파라미터 앞에 vararg

스프레드 연산자 *args : 배열에 들어있는 원소를 가변 길이 인자로 넘길 때도 코틀린은 명시적으로 배열을 풀어서 전달함 // java는 그냥 넘기면 됨

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

val map = mapOf( 1 to "one", 7 to "seven")

중위 호출 infix call : to라는 일반 메소드 호출

 

// 스칼라와 동일

1 to "one" 는 이렇게 내부적으로 메소드 호출로 변환됨

1.to("one")

인자가 하나 뿐인 일반 메소드나 인자나 하나 뿐인 확장 함수는 중위 호출 사용 가능

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

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

이것을 구조분해 선언 destructuring declaration

fun <K, V> mapOf(vararg values: Pair<K, V>) : Map<K, V>

 

문자열과 정규식 다루기

자바 : split메소드로는 "."을 이용해 문자열을 분리할수 없음. 왜냐하면 내부적으로 정규식이고 정규식에서 "."는 모든 문자를 나타냄

코틀린에서는 split 확장 함수를 통해서 String이 아닌 Regex 타입의 값을 받는 split

println("12.345-6.A".split("\\.|-".toRegex())

[12, 345, 6, A]

3중 따옴표 문자열

1. 정규식의 어떤 문자도 이스케이프할 필요 없음.

2. 여러 줄 문자열 도 만들 수 있음

3. 3중 따옴표 문자열 안에 $ (문자열 템플릿) 사용안되기 때눙네 ${'$'}  사용

기존 라이브러리를 새 언어에 활용하는 패턴 : Pimp My Library www.artima.com/weblogs/viewpost.jsp?thread=179766

 

 

Pimp my Library

Ideas, Languages, and Programs Pimp my Library by Martin Odersky October 9, 2006 Summary What to do if you are stuck with existing libraries and API's. Advertisement There's a fundamental difference between your own code and libraries of other people: You

www.artima.com

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
	if (user.name.isEmpty())
    	throw IllegalArgumentException("Can't save : name empty : userId => ${user.id} ")
    if (user.address.isEmpty())
    	throw IllegalArgumentException(Can't save : address empty")
	// save to db
}

fun saveUser(user: User) {
	fun validate(user: User, value: String, fieldName: String) {
    	if (value.isEmpty() 
        	throw IllegalArgumentException("Can't save : ${fieldName} empty : userId => ${user.id} ")
    }
    validate(user, user.name, "name")
    validate(user, user.address, "address")

	// save to db
}

fun User.validateBeforeSave() {
	fun validate(value: String, fieldName: String) {
    	if (value.isEmpty() 
        	throw IllegalArgumentException("Can't save : ${fieldName} empty : userId => ${id} ")
    }
    validate(user.name, "name")
    validate(user.address, "address")    
}


// 더 간단해 짐
fun saveUser(user: User) {
	user.validateBeforeSave()
    // save to db
}