-
코틀린 - 함수의 정의와 호출Kotiln 2019. 7. 24. 11:23
코틀린에서 컬렉션 만들기
setOf
,arrayListOf
,hashMapOf
등의 함수를 이용하여 컬렉션을 생성할 수 있다.
코틀린은 자신만의 컬렉션을 기능을 제공하지 않는다. 기존 자바의 컬렉션을 활용할 수 있다.함수를 호출하기 쉽게 만들기
이름 붙인 인자
함수 호출 부분의 가독성을 늘리기 위해 인자에 이름을 붙일 수 있다.
joinToString(collection, separator = " ", prefix = " ", postfix = ".")
호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
디폴트 파라미터 값
fun <T> joinToString( collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = "" ): String joinToString(list, postfix=";", prefix="# ")
디폴트 파라미터를 설정하면 함수를 호출할 때 모든 인자를 쓸 수도 있고 일부를 생략할 수도 있다.
코틀린 함수를 자바에서 호출하는 경우에는 그 코틀린 함수가 디폴트 파라미터 값을 제공하더라도 모든 인자를 명시해야 한다. 자바에서 코틀린 함수를 자주 호출해야 한다면 자바 쪽에서 @JvmOverloads 애노테이션을 함수에 추가하면 코틀린 컴파일러가 자동으로 맨 만지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 자바 메소드를 추가해준다.
최상위 함수와 프로퍼티
코틀린에서는 함수를 최상위 수준에 위치시킬 수 있다.
코틀린 파일의 모든 최상위 함수는 이 클래스의 정적인 메소드가 된다.코틀린 최상위 함수가 포함되는 클래스의 이름을 바꾸고 싶다면 파일에
@JvmName
애노테이션을 추가한다.@JvmName
애노테이션은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.@file:JvmName("StringFunctions") package strings fun joinToString(...): String { ... }
자바에서는 다음과 같이 호출할 수 있다.
import strings.StringFunctions; Stringfunctions.joinToString(list, ",", "","");
함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다.
기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메소드를 통해 자바 코드에 노출된다. (val
의 경우getter
,var
의 경우getter
와setter
가 생성된다. )const
변경자를 추가하면 프로퍼티를public static final
필드로 컴파일 하게 만들 수 있다. (단, 원시타입과String
타입의 프로퍼티만const
로 지정할 수 있다.확장 함수와 확장 프로퍼티
확장 함수extension function는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수이다.
확장 함수를 만들려면 춤가하는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다. 클래스의 이름을 객체 타입receiver type 이라 부르며 확장 함수가 호출되는 대상이 되는 값을 수신 객체 receiver object 라고 부른다.fun String.lastChar() : Char = this.get(this.length - 1 ) ------ ---- ---- 수신 객체 타입 수신 객체
일반 메소드의 본문에서
this
를 사용할 때와 마찬가지로 확장 함수 본문에도this
를 쓸 수 있다. 마찬가지로 생략할 수 있다.
클래스 안에서 정의한 메소드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할수 있는private
멤버나protected
멤버를 사용할 수 없다.임포트와 확장 함수
확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야 한다.
import strings.lastChar
as
키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있다.import strings.lastChar as last val c = "Kotlin".last()
자바에서 확장 함수 호출
내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다. 자바에서 호출할 때는 첫번째 인자로 수신 객체를 넘기기만 하면 된다. 확장 함수를
StringUtil.kt
파일에 정의했다면 다음과 같이 호출할 수 있다.char c = StringUtilKt.lastChar("Java");
확장 함수로 유틸리티 함수 정의
fun <T> Collection<T>.joinToString{ separator: String = ", ", prefix: String = "", postfix: String = "" ): String { val result = StringBuilder(prefix) for ((index, element) in this.withIndex()) if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() } val list =. istOf(1, 2, 3) println(list.joinToSTring(separator = "; ", prefix= "(", postfix = ")")) > (1;2;3)
joinToString
메소드를Collection<T>
의 멤버인 것처럼 호출할 수 있다.확장 함수는 오버라이드할 수 없다.
확장 함수는 클래스의 일부가 아니며 클래스 밖에 선언된다.
확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되지, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않는다.정적
은 컴파일 시점을 의미하고동적
은 실행 시점을 의미한다.open class View { open fun click() = println("View clicked") } class Button: View() { override fun click() = println("Button clicked") } fun View.showOff() = println("I'm a view!") fun Button.showOff() = println("I'm a button!") val view: View = Button() view.showOff() > I'm a view!
view
가 가리키는 객체의 실제 타입이Button
이지만, 이 경우view
의 타입이View
이기 때문에 무조건View
의 확장 함수가 호출된다.어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출된다.
확장 프로퍼티
확장 프로퍼티 를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있다.
확장 프로퍼티는 실제로 아무 상태도 가질 수 없다.val String.lastChar: Char get () = get(length - 1) println("abcd".lastChar) > d
확장 함수는 뒷받침하는 필드가 없어서
getter
는 꼭 정의해야 하며 초기화 코드는 쓸 수 없다.var StringBuilder.lastChar: Char get() = get(length - 1) set(value: Char) { this.setCharAt(length - 1, value) } val sb = StringBuilder("Kotlin?") sb.lastChar = "!" println(sb) > Kotlin!
자바에서 확장 프로퍼티를 사용하고 싶다면 항상
StringUtilKt.getLastChar(“Java”)
처럼getter
나setter
를 명시적으로 호출해야 한다.컬렉션 처리
자바 컬렉션 API 확장
val strings: List<String> = listOf("first", "second", "fourteenth") strings.last() > fourteenth val numbers: Collction<Int> = setOf(1, 14, 2) numbers.max() > 14
last
와max
는 확장 함수이다.가변 인자 함수
자바의 가변 길이 인자 는 메소드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 자바 컴파일러가 배열에 그 값들을 넣어주는 기능이다.
자바에서는 타입 뒤에...
을 붙였지만 코틀린에서는 파라미터 앞에vararg
변경자를 붙인다.fun listOf<T>(vararg values: T) : List<T> { ... }
이미 배열에 들어있는 원소를 가변 길이 인자로 넘길 때 자바에서는 배열을 그냥 넘기면 되지만 코틀린에서는 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 해야 한다. 스프레드spread 연산자가 그런 작업을 해준다.
전달하려는 배열 앞에*
를 붙이기만 하면 된다.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
라는 일반 메소드를 호출한 것이다.
중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다. (이때 객체, 메소드 이름, 유일한 인자 사이에는 공백이 들어가야 한다.)다음 두 호출은 동일하다.
1.to("one") 1 to "one"
인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 함수에 중위 호출을 사용할 수 있다.
함수를 중위 호출에 사용하게 허용하고 싶으면infix
변경자를 함수 선언 앞에 추가해야 한다.infix fun Any.to(other: Any) = Pair(this, other)
Pair
는 코틀린 표 준 라이브러리 클래스로 두 원소로 이뤄진 순서쌍을 표현한다.Pair
의 내용으로 두 변수를 즉시 초기화할 수 있다.val (number, name) = 1 to "one"
이런 기능을 구조 분해 선언restructuring declaration 라고 한다.
문자열과 정규식 다루기
문자열 나누기
자바
split
메소드의 구분 문자열은 실제로는 정규식이기 때문에.
을 사용해 문자열을 분리할 수 없다.
코틀린에서는 자바의split
대신에 여러 가지 조합의 파라미터를 받는split
확장 함수를 제공함으로써 혼동을 야기하는 메소드를 감춘다.
정규식을 파라미터로 받는 함수는String
이 아닌Regex
타입의 값을 받는다.println("12.345-6.A".split("\\.|-".toRegex())) > [12, 345, 6, A]
간단한 경우는 정규식을 사용하지 않아도 된다.
println("12.345-6.A".split('.', '-') > [12, 345, 6, A]
정규식과 3중 따옴표로 묶은 문자열
코틀린에서는 정규식을 사용하지 않고도 문자열을 쉽게 파싱할 수 있다.
"/Users/yole/kotlin-book/chapter.adoc"
경로를 파싱해보자fun parsePath(path: String){ val directory = path.substringBeforeLast("/") val fullName = path.substringAfterLast("/") val fileName = fullName.substringBeforeLast(".") val extension = fullName.substringAfterLast(".") println("Dir: $directory, name: $fileName, ext: $extension") } parsePath("/Users/yole/kotlin-book/chapter.adoc") > Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc
같은 작업을 정규식을 활용해서 해보자
fun parsePath(path: String){ val regex = """(.+)/(.+)\.(.+)""".toRegex() val matchResult = regex.matchEntire(path) if(matchResult != null){ val (directory, filename, extension) = matchResult.destructured println("Dir: $directory, name: $filename, ext: $extension") } }
3중 따옴표 문자열을 사용해 정규식을 썼다. 3중 따옴표 문자열에서는
\
를 포함한 어떤 문자도 이스케이프할 필요가 없다.여러 줄 3중 따옴표 문자열
3중 따옴표 문자열에는 줄 바꿈을 표현하는 아무 문자열이나 이스케이프 없이 그대로 들어간다.
val kotlinLogo = """| // .| // .|/ \""" println(kotlinLogo.trimMargin(".")) >| // >| // >|/ \
들여쓰기의 끝부분을 특별한 문자열로 표시하고
trimMargin
을 사용해 그 문자열과 그 직전의 공백을 제거한다.
3중 따옴표 문자열 안에$
를 넣어야 한다면var price = “””${‘$’}99.9”””
처럼 문자열 템플릿 안에'$'
문자를 넣어야 한다.로컬 함수와 확장
코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
class User(val id: Int, val name: String, val address: String) fun saveUser(user: User){ if (user.name.isEmpty()) { throw IllegalArgumentException( "Can't save user ${user.id}: empty Name") } if (user.address.isEmpty()) { throw IllegalArgumentException( "Can't save user ${user.id}: empty Address") }
위의 코드에서 검증하는 부분이 중복된다. 로컬 함수로 분리하여 중복 코드를 없애면 다음 코드와 같다
class User(val id: Int, val name: String, val address: String) fun saveUser(user: User){ fun validate(user: User, value: String, fieldName: String){ if (value.isEmpty()){ throw IllegalArgumentException( "Can't save user ${user.id}: empty $fieldName") } } validate(user, user.name, "Name") validate(user, user.address, "Address") }
로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.
따라서 아래의 코드로 바꿀 수 있다.class User(val id: Int, val name: String, val address: String) fun saveUser(user: User){ fun validate(value: String, fieldName: String){ if (value.isEmpty()){ throw IllegalArgumentException( "Can't save user ${user.id}: empty $fieldName") } } validate(user.name, "Name") validate(user.address, "Address") }
또는 검증 로직을
User
클래스를 확장한 함수로 만들 수도 있다.class User(val id: Int, val name: String, val address: String) fun User.validateBeforeSave(){ fun validate(value: String, fieldName: String){ if (value.isEmpty()){ throw IllegalArgumentException( "Can't save user $id: empty $fieldName") } } validate(user.name, "Name") validate(user.address, "Address") } fun saveUser(user: User){ user.validateBeforeSave() }
반응형'Kotiln' 카테고리의 다른 글
[Kotlin] 엔티티로 테이블 이름 가져오기 (0) 2020.04.13 자바 Class 로 KClass 생성하기 (0) 2019.12.19 코틀린 기초 (0) 2019.07.23 코틀린 - 연산자 오버로딩과 기타 관례 (0) 2019.07.23 코틀린 타입 시스템 - 컬렉션과 배열 (0) 2019.07.22