코틀린 공부 3일차
* 컬렉션, 문자열, 정규식을 다루기 위함 함수
* 이름 붙인 인자, 디폴트 파라미터 값, 중위 호출 문법
* 확장 함수와 확장 프로퍼티를 사용하여 자바 라이브러리 사용
* 최상위 및 로컬 함수와 프로퍼티를 사용해 코드 구조화
3-1에서 코틀린은 자바의 컬렉션보다 더 많은 기능을 제공한다고 언급했다.
str.last()
numbers.max()
코틀린에서만 제공하는 last
와 max
함수는 모두 확장함수였던 것이다.
가변인자 함수를 알기위해서 우리는 코틀린에서의 listOf함수에 관심을 기울일 필요가 있다.
val list = listOf(2, 3, 5, 7, 11)
리스트를 생성하는 함수로 원하는 만큼 인자를 전달하여 생성할 수 있다. 여기서 중요한 것은 "원하는 만큼" 이다. 라이브러리에서 정의한 listOf함수를 보면 다음과 같다.
fun listOf<T>(vararg values: T) : List<T> { ... }
자바에서는 타입 뒤에 ...
를 붙이는 대신 코틀린에서는 파라미터 앞에 vararg
변경자를 붙인다.
스프레드 연산자에 대한 내용은 추후 필요시 작성하도록 하겠다.
중위 호출이라는 특별한 방식으로 호출한 사례를 소개한다.
val map = mapOf(1 to "one", 2 to "two")
1.to("one") -> to 메소드를 일반 방식으로 호출
1 to "one" -> to 메소드를 중위 호출 방식으로 호출
파라미터가 하나뿐인 일반, 확장함수에서 중위 호출을 사용할 수 있다. 중위 호출을 허용하고 싶으면 infix
변경자를 함수 선언 앞에 추가해야 한다. 이 부분도 자세한 내용은 추후 제대로 이해한 다음 작성하도록 하겠다.
코드의 중복을 피하는 것은 개발자의 숙명이다. 코드의 중복을 피하기 위해 함수로 쪼개지만 클래스 내부에 작은 함수들이 너무 많아지고 오히려 코드를 이해하는데 어려움이 발생하기도 한다.
코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있는 기능인 로컬함수가 있다. 잘 와 닿지 않을 것 같아서 예시코드를 준비했다. 아래 코드를 보자.
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) { --- (1)
throw IllegalArgumentException("Can't save user ${user.id} is empty Name")
}
if (user.address.isEmpty()) { --- (2)
throw IllegalArgumentException("Can't save user ${user.id} is empty Address")
}
// save user
}
(1)과 (2)의 코드가 중복된다는 것을 볼 수 있고 이를 함수화 해야한다. 이를 로컬함수로 바꾸어 보자.
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} is "+
"empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// save User
}
바꾸어보니 라인 수는 비슷할 지 몰라도 깔끔해졌을 뿐더러 추후 검증할 요소가 추가되더라도 쉽게 추가할 수 있다. (요소가 추가되면 될 수록 라인 수의 차이는 극명해질 것이다.)
그런데 validate
를 호출할 때마다 user
객체를 넘겨야하는 것은 불필요하다. → 로컬함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.
fun saveUser(user: User) {
... 생략 ...
validate(user.name, "Name")
validate(user.address, "Address")
// save User
}
그리고 이 예제를 개선하고 싶다면 User
의 확장함수를 validateBeforeSave
라는 이름으로 호출하면 더욱 깔끔해질 수 있다.
하지만, 일반적으로 한 단계만 함수를 중첩시키라고 권장한다. 함수의 깊이가 깊어지면 코드를 읽는데 상당히 어려워진다.