코틀린에서 컬렉션 만들기
- 코틀린은 자신만의 컬렉션 기능을 제공하지 않는다.
- 표준 자바 컬렉션을 활용하면 자바 코드와 상호작용하기가 훨씬 쉽다.
- 코틀린에서는 자바보다 더 많은 기능을 쓸 수 있다.
함수를 호출하기 쉽게 만들기
이름 붙인 인자
- 코틀린으로 작성한 함수를 호출할 때는 함수에 전달하는 인자 중 일부의 이름을 명시할 수 있다.
- 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
- 자바로 작성한 코드를 호출할 때는 이름 붙인 인자를 사용할 수 없다.
- 클래프 파일에 함수 파라미터정보를 넣은 것은 자바 8 이후 추가된 선택적 특징인데, 코틀린은 JDK 6와 호환된다.
디폴트 파라미터 값
- 코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로 자바의 오버로딩이 많아지는 문제를 피할 수 있다.
- 자바에는 디폴트 파라미터 값이라는 개념이 없어서 코틀린 함수를 자바에서 호출하는 경우에는 코틀린 함수가 디폴트 파라미터 값을 제공하더라도 모든 인자를 명시해야 한다.
- 코틀린 함수에
@JvmOverloads
애노테이션을 추가하면 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 자바 메소드를 추가해준다.
- 코틀린 함수에
정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티
- 코틀린에서는 함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시킬 수 있다.
- 코틀린 컴파일러는 컴파일할 때, 새로운 클래스를 정의해준다. 최상위 함수는 이 클래스의 정적인 메소드가 된다.
- 코틀린 최상위 함수가 포함되는 클래스의 이름을 바꾸고 싶다면 파일에
@JvmName
애노테이션을 추가하면 된다.@JvmName
애노테이션은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.
- 함수와 마찬가지로 프로퍼티도 파일의 최상위에 놓을 수 있다.
- 이런 프로퍼티 값은 정적 필드에 저장된다.
- 겉으로는 상수처럼 보이는데, 실제로는 getter를 사용해야 한다면 자연스럽지 못하다.
const
변경자를 추가하면 프로퍼티를public static final
필드로 컴파일하게 만들 수 있다.
메소드를 다른 클래스에 추가: 확장 함수와 프로퍼티
확장 함수
- 확장 함수: 어떤 클래스의 멤버 메소드인 것 처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수
- 수신 객체 타입(receiver type): 확장 함수의 클래스 이름
- 수신 객체(receiver object): 확장 함수가 호출이 되는 대상이 되는 객체
- 확장 함수가 캡슐화를 깨지는 않는다. 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 private 멤버와 protected 멤버를 사용할 수 없다.
fun String.middle() = get(this.length / 2)
fun main() {
val name = "junroot"
print(name.middle())
}
임포트와 확장 함수
- 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 한다.
- 한 파일 안에서 다른 여러 패키지에 속해있는 이름이 같은 함수를 가져와 사용해야 하는 경우 이름을 바꿔서 임포트하면 이름 충돌을 막을 수 있다.
import extension.middle as m
fun main() {
val name = "junroot"
name.m()
}
자바에서 확장 함수 호출
- 내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다.
- 그래서 확장 함수를 호출해도 다른 어댑터 객체나 실행 시점 부가 비용이 들지 않는다.
확장 함수로 유틸리티 함수 정의
확장 함수는 단지 정적 메소드 호출에 대한 syntatic sugar일 뿐이다.
그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수도 있다.
아래와 같이 문자열의 컬렉션에 대해서만 호출할 수 있는 확장 함수도 만들 수 있다.
fun Collection<String>.join( separator: String = ", ", prefix: String = "", postfix: String = "" ) = joinToString(separator, prefix, postfix)
확장 함수는 오버라이드할 수 없다
- 확장 함수는 클래스의 일부가 아니다. 확장 함수는 클래스 밖에 선언된다.
- 이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되지, 그 변수가 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않는다.
- 어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출된다.
확장 프로퍼티
- 확장 프로퍼티도 일반적인 프로퍼티와 같은데, 단지 수신 객체 클래스가 추가됐을 뿐이다.
- backing field가 없어서 기본 getter 구현을 제공할 수 없으므로 최소한 getter는 꼭 정의를 해야 한다.
- 마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없다.
컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
가변 인자 함수 정의 방법: 함수 파라미터 앞에
vararg
변경자를 붙인다.public fun <T> listOf(vararg elements: T): kotlin.collections.List<T> { /* compiled code */ }
스프레드 연산자(
*
): 이미 배열에 들어있는 원소를 가변 길이 인자로 넘기는 방법val list = listOf("args: ", *args)
값의 쌍 다루기: 중위 호출과 구조 분해 선언
중위 호출(infix call): 수신 객체와 메소드 인자 사이에 메소드 이름을 넣는 호출 방식. 인자가 하나뿐인 일반 메소드나 확장 함수에 중위 호출을 할 수 있다.
함수를 중위 호출에 사용하게 허용하고 싶으면
infix
변경자를 함수 선언 앞에 추가해야 한다.public infix fun <A, B> A.to(that: B): kotlin.Pair<A, B> { /* compiled code */ }
아래의 예에서
to
가 중위 호출이다.to
함수를 호출하면Pair
타입을 반환한다.mapOf(1 to 2, 2 to 3)
구조 분해 선언:
Pair
인스턴스 뿐만 아니라 여러 가지 객체에서 두 변수를 즉시 초기화 할 수 있다.withIndex
의 경우도 구조 분해 선언과 조합해서 원소의 인덱스와 값을 따로 변수에 담을 수 있다.for ((index, value) in list.withIndex()) { }
문자열과 정규식 다루기
문자열 나누기
자바의
split()
메소드는 정규식으로만 표현이 가능하다.코틀린의
split()
함수는 기본적으로String
으로 문자열을 나누고, 정규식을 파라미터로 받는 함수는Regex
타입을 받는 함수가 따로 있다.문자열을 정규식으로 바꾸기 위해서는
.toRegex()
함수를 사용하면 된다.printin("12.345-6.A".split("\\.|-".toRegex()))
또한
split()
확장 함수를 오버로딩한 버전 중에 구분 문자열을 하나 이상 인자로 받을 수도 있다.printIn("12.345-6.A".split(".", "-"))
정규식과 3중 따옴표로 묶은 문자열
- 3중 따옴표 문자열에서는 역슬래시(
\
)를 포함한 어떤 문자도 이스케이프할 필요가 없다. Regex
타입의matchEntire()
함수를 사용하면 매치가 된다. 매치에 실패하면null
이 반환된다.- 매치된 결과에
destructured
프로퍼티는 구조 분해 선언이 가능하다.
fun parsePath (path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex ()
val matchResult = regex.matchEntire (path)
if (matchResult != null) (
val (directory, filename, extension) = matchResult.destructured
printIn ("Dir: $directory, name: $filename, ext: Sextension")
)
여러 줄 3중 따옴표 문자열
3중 따옴표 문자열에는 줄 바꿈을 표현하는 아무 문자열이나 이스케이프 없이 그대로 들어간다.
여러 줄 문자열을 코드에서 더 보기 좋게 표현하고 싶다면 들여쓰기를 하되 들여쓰기의 끝부분을 특별한 문자열로 표시하고,
trimMargin
을 사용해 그 문자열과 그직전의 공백을 제거한다.3중 따옴표 문자열 안에 문자열 템플릿을 사용할 수도 있다.
3중 따옴표 문자열 안에서는 이스케이프를 할 수 없기 때문에 문자열 템플릿의 시작을 표현하는
$
를 3중 따옴표 문자열 안에 넣어야한다면 아래 처럼 문자열 템플릿 안에 ‘$’ 문자를 넣어야 한다.val price = """${'$'}99.9"""
코드 다듬기: 로컬 함수와 확장
- 로컬 함수: 코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
- 문법적인 부가 비용을 들이지 않고 코드 중복을 제거할 수 있다.
import java.util.*
fun Person.validateBeforeSave() {
fun <T> validateNotNull(value: T) {
if (Objects.isNull(value)) {
throw IllegalArgumentException()
}
}
validateNotNull(this.id)
validateNotNull(this.name)
}
fun save(person: Person) {
person.validateBeforeSave()
// save
}