문제 6 휴대폰 번호에서 숫자만 추출하는 예제를 교재 p.141에서 소개했습니다. 이 코드를 참고하여 주민등록번호 13자리 중 앞 6 숫자와 뒤 7 숫자를 추출하는 프로그램을 만들어 보세요. 단, 아래 조건을 모두 만족해야 합니다.
(1) String 클래스의 확장 함수를 선언해야 합니다.
(2) 람다 식을 함수 타입의 형식 인자로 전달해야 합니다.
■ 확장 함수의 서명(signature) 선언
(1)번 조건에 맞게 extractFirst()를 String 클래스의 확장 함수로 추가하려면 "fun String.extractFirst( ) "와 같이 선언하면 됩니다. 이 함수의 반환 타입은 부분 문자열을 추출한 결과이기 때문에 String입니다. 이어서 이 확장 함수의 형식 인자를 "op:String.( ) -> String" 처럼 함수 타입을 선언하면 됩니다. 함수 타입의 타입.() -> 에서 타입은 수신자 객체(receiver)를 가리킵니다.
fun String.extractFirst(op: String.() -> String): String { /* 함수의 몸체 */ }
■ 확장 함수 구현 : 정답 1
확장 함수 extractFirst(또는 extractLast)에 람다 식을 인자로 전달하면 됩니다. "주민등록번호 13자리 중 앞 6 숫자 ... "와 같은 문제는 String 클래스의 메소드 substring()을 사용하면 좋습니다. 신용카드, 운전면허증, 여권처럼 정해진 숫자열을 갖는 응용에도 적용할 수 있겠죠! 주의할 점은 substring(startIndex, endIndex)에서 endIndex는 추출하려는 부분 문자열의 범위에 포함되지 않습니다. 또, 람다 식 블록에서 this는 수신자 객체를 가리킵니다.
"이렇게 코딩하면 되는구나" 하는 감(느낌 → 이해 → 개념 정립 → conceptualization)을 잡았으면 여러 가지 다른 형태의 정답도 알아봐야죠.
fun main() {
println("012345-9876543".extractFirst ())
println("012345-9876543".extractLast ())
}
fun String.extractFirst(op: String.() -> String = { this.substring(0, 6) } ): String {
return op(this)
}
fun String.extractLast(op: String.() -> String = { this.substring(7, this.length) } ): String {
return op(this)
}
■ 정답 2: 함수의 실 인자로 람다 식 전달
우선 확장 함수부터 식(expression) 형태를 사용해 줄일 수 있습니다. 그리고, 확장 함수에서 람다 식을 직접 지정하지 않고, 확장 함수를 호출할 때 람다 식을 전달할 수 있습니다. 람다 식이 마지막 인자이면 함수 괄호 밖으로 옮길 수 있습니다(책 p.135의 설명을 참고하세요).
이렇게 바꾸면 확장 함수 extractFirst와 extractLast가 다른 점이 있나요? 비슷한데요... 합치면 안되나요?
fun main() {
// "012345-9876543".extractFirst { this.substring(0, 6) }
println("012345-9876543".extractFirst { this.substring(0, 6) })
println("012345-9876543".extractLast { this.substring(7, this.length) })
}
fun String.extractFirst(op: String.() -> String) = op(this)
fun String.extractLast(op: String.() -> String) = op(this)
아래처럼 1개의 확장 함수로 합칠 수 있습니다. 잠깐! this를 사용하지 않았어요. 람다 식 블록에서 this는 언제든 생략할 수 있습니다. 함수 타입에서 수신자 객체를 강조하기 위해 this를 사용했을 뿐입니다.
fun main() {
println("012345-9876543".extract { substring(0, 6) })
println("012345-9876543".extract { substring(7, this.length) })
}
fun String.extract(op: String.() -> String) = op(this)
■ 정답 3: 확장 함수에서 수신자 객체를 지정하지 않은 함수 타입 선언
확장 함수에서 함수 타입을 아래처럼 String.() -> String에서 (String) -> String으로 바꾸면 코드는 어떻게 달라질까요? 수신자 객체가 아니라 인자(argument)를 전달받게 됩니다. 이렇게 바꾸면 람다 식 블록에서 this를 사용할 수 없습니다. 대신 형식 인자 한 개를 전달받기 때문에, 인자 이름 대신 it를 사용할 수 있습니다(책 p.136의 내용을 참고하세요).
fun main() {
println("012345-9876543".extract { it.substring(0, 6) })
println("012345-9876543".extract { it.substring(7, it.length) })
}
fun String.extract(op: (String) -> String) = op(this)
// fun String.extract(op: String.() -> String) = op(this)
■ 정답 4: 람다 식을 사용
늘 그렇듯 앞에 설명한 정답처럼 어렵게 풀 필요가 없었습니다. 람다 식만 사용해도 String 클래스의 확장 멤버 함수로 extractFirst()를 추가할 수 있습니다(IDEA에서 "012..-...543", 까지 입력하고, 점(.)을 눌러 팝-업 창에 당당히 extractFirst()가 나타나는 걸 확인해보세요). 단, 람다 식의 함수 타입을 선언할 때 수신자 객체 지정 방식을 사용해야 합니다.
정답 4를 가장 먼저 생각했나요? 그것보다는 정답 1부터 차례대로 한 단계씩 이해하면서 코딩 스타일(coding style)을 이해하는 게 더 좋은 방법입니다. A long long journey begins with a single step...
fun main() {
val extractFirst: String.() -> String = { this.substring(0, 6) }
// extractFirst("012345-9876543")
// "012345-9876543".extractFirst()
println("012345-9876543".extractFirst ())
// println("012345-9876543".substring (0, 6))
}
'코틀린' 카테고리의 다른 글
코틀린: 확장 함수와 수신 객체를 갖는 함수 타입 (0) | 2024.10.05 |
---|---|
코틀린: 3장 테스트에 도전해 보세요 (4번 정답 해설) (0) | 2024.10.05 |
코틀린: 3장 테스트에 도전해 보세요 (0) | 2024.09.30 |
코틀린: 인라인(inline) 함수와 noinline (0) | 2024.09.30 |
코틀린: 함수형 프로그래밍과 일급 객체 (0) | 2024.09.30 |