-
3장. 함수 정의와 호출 - 2Kotlin 2022. 2. 23. 17:05
컬렉션 처리 : 가별 길이 인자, 중위 함수 호출, 라이브러리 지원
코틀린 언어 특성
1. vararg 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있다.
2. 중위 함수 호출 구문을 사용하면 인자가 하나뿐인 메소드를 간편하게 호출할 수 있다.
3. 구조 분해 선언을 사용하면 복합적인 값을 분해해서 여러 변수에 담을 수 있다.
자바 컬렉션 API 확장
fun main(args:Array<String>){ val strings:List<String> = listOf("first", "second", "Fourteenth") println(strings.last()) // Fourteenth val numbers:Collection<Int> = setOf(1,14,2) println(numbers.maxOrNull()) // 14 }
자바 라이브러리 클래스의 인스턴스인 컬렉션에 대해 코틀린이 새로운 기능을 추가할 수 있었던 이유는?
→ last 와 max는 모두 확장함수였기 때문이다.
fun<T> List<T>.last() : T {/*마지막 원소를 반환함*/} fun collction<Int>.max() : Int {/*컬렉션의 최댓값을 찾음*/}
컬렉션을 만들어내는 함수의 특징
→ 바로 인자의 개수가 그때그때 달라질 수 있다
가변 인자 함수 : 인자의 개수가 달라질 수 있는 함수 정의
val list = listOf(2, 3, 5, 7, 11) fun listOf<T>(vararg values: T): List<T> { ... }
가변 길이 인자
메소드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 자바 컴파일러가 배열에 그 값들을 넣어주는 기능
자바
타입 뒤에 ... 붙인다.
배열을 그냥 넘기면 된다.
코틀린
파라미터 앞에 vararg 변경자를 붙인다.
배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 해야 한다. 기술적으로는 스프레드(spread) 연산자가 그런 작업을 해준다. 실제로는 전달하려는 배열 앞에 *를 붙이기만 하면 된다.
아래 예제는 스프레드 연산자를 통하면 배열에 들어있는 값과 다른 여러 값을 함께 써서 함수를 호출할 수 있음을 보여준다.
//스프레드 연산자 val list2 = listOf("args:", *args)//스프레드 연산자가 배열의 내용을 펼쳐준다. println(list) // [2, 3, 5, 7, 11]
값의 쌍 다루기 : 중위 호출과 구조 분해 선언
맵
mapOf 함수 사용
val map = mapOf(1 to "one" , 7 to "seven", 53 to "fifty-three")
to
코틀린 키워드가 아니다. 이 코드는 중위 호출이라는 특별한 방식으로 to라는 일반 메소드를 호출한 것이다.
중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다.(이때 객체, 메소드 이름, 유일한 인자 사이에는 공백이 들어가야 한다.)
다음 두 호출은 동일하다.
1.to("one") //"to" 메소드를 일반적인 방식으로 호출 1 to "one" //"to" 메소드를 중위 호출 방식으로 호출
인자가 하나뿐인 일반 메소드나 인자가 하나뿐인 확장 함수에 중위 호출을 사용할 수 있다.
함수를 중위 호출에 사용하게 허용하고 싶으면 infix 변경자를 함수 선언 앞에 추가 해야 한다.
이 to 함수는 Pair의 인스턴스를 반환한다. Pair는 코틀린 표준 라이브러리 클래스로, 두 원소로 이뤄진 순서쌍을 표현한다.
Pair의 내용으로 두 변수를 즉시 초기화할 수 있다. 이 기능을 구조 분해 선언(destructuring declaration)이라고 부른다.
infix fun Any.to(other:Any) = Pair(this, other)
val(number, name) = 1 to "one"
구조 분해 선언 예시
1. Pair 인스턴스
2. key와 value라는 두 변수를 맵의 원소를 사용해 초기화 (mapOf - 각 인자가 키와 값으로 이뤄진 순서쌍)
3.루프 - 컬렉션 원소의 인덱스와 값을 따로 변수에 담을 수 있다.
for ((index, element) in collection.withIndex()){ println("$index: $element") } fun<K, V> mapOf(vararg values:Pair<K,V>) : Map<K,V>
to - 확장함수
to를 사용하면 타입과 관계없이 임의의 순서쌍을 만들 수 있다.
이는 to의 수신 객체가 제네릭하다는 뜻이다.
(제네릭 : 일반적인. 데이터 형식에 의존하지 않고, 하나의 값이 여러 데이터 타입들을 가질 수 있도록 하는 방법)
문자열과 정규식 다루기
문자열 나누기
Java
split의 구분 문자열은 실제로는 정규식이기 때문이다. 마침표(.)는 모든 문자를 나타내는 정규식으로 해석된다.
"12.345-6.A".split(".") // 빈 문자열
Kotlin
정규식을 파라미터로 받는 함수는 String이 아닌 Regex 타입의 값을 받는다.
코틀린에서는 split 함수에 전달하는 값의 타입에 따라 정규식이나 일반 텍스트 중 어느 것으로 문자열을 분리하는지 쉽게 알 수 있다.
아래 예제는 마침표나 대시와 매치된다. (정규식 안에서 마침표가 와일드카드 문자가 아닌 문자 자체로 쓰이게 하기 위해 마침표를 이스케이프 시켰다)
toRegex 확장 함수를 사용해 문자열을 정규식으로 변환할 수 있다. 꼭 정규식을 쓸 필요가 없다.
split 확장 함수를 오버로딩한 버전 중에는 구분 문자열을 하나 이상 인자로 받는 함수가 있다.
문자열 대신 문자로 인자를 넘겨도 결과가 같은 이유는 단 하나의 문자만 받을 수 있는 메소드를 코틀린 확장 함수가 대신하기 때문이다.
println("12.345-6.A".split("\\.|-".toRegex()))//정규식을 명시적으로 만든다 //[12, 345, 6, A] println("12.345-6.A".split(".","-"))//여러 구분 문자열을 지정한다. //[12, 345, 6, A] println("12.345-6.A".split('.','-'))//문자열 대신 문자도 가능 //[12, 345, 6, A]
정규식과 3중 따옴표로 묶은 문자열
String 확장 함수를 사용해 경로 파싱하기
디렉터리
path에서 처음부터 마지막 슬래시 직전까지의 부분 문자열
파일 확장자
path에서 마지막 마침표 다음부터 끝까지의 부분 문자열
파일 이름
디렉터리와 파일 확장자 사이에 위치
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
경로 파싱에 정규식 사용하기
3중 따옴표 문자열에서는 역슬래시(\)를 포함한 어떤 문자도 이스케이프 할 필요가 없다.
디렉터리
마지막 슬래시 앞까지의 부분 문자열
파일 확장자
나머지 모든 문자
파일 이름
마지막 마침표 전까지 모든 문자
//경로 파싱에 정규식 사용하기 fun parsePath2(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중 따옴표 문자열이다)에는 들여 쓰기나 줄 바꿈을 포함한 모든 문자가 들어간다.
trimMargin - 문자열과 그 직전의 공백을 제거한다.
val kotlinLogo = """| // .| // .|/ \""" println(kotlinLogo.trimMargin(".")) /* | // | // |/ \ */
줄 바꿈
여러 줄 문자열에는 줄 바꿈이 들어가지만 \n과 같은 특수 문자를 사용해 넣을 수 없다.
반면에 \를 넣고 싶으면 이스케이프 할 필요가 없다. 3중 따옴표를 사용하면 된다.
"C:\\Users\\yole\\kotlin-book" """C:\Users\yole\kotlin-book"""
3중 따옴표와 문자열 템플릿
$를 3중 따옴표 문자열 안에 넣을 수 없다는 문제 발생
→ 문자열 템플릿 안에 '$'를 넣어야 한다.
val 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") } //user를 데이터베이스에 저장한다. } fun main(args:Array<String>){ saveUser(User(1, "","")) //java.lang.IllegalArgumentException: Can't save user 1 : empty Name }
로컬 함수를 사용해 코드 중복 줄이기
fun saveUser(user:User2) { fun validate(user:User2,//한 필드를 검증하는 로컬 함수를 정의한다. 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") //user를 데이터베이스에 저장한다. }
로컬 함수에서 바깥 함수의 파라미터 접근하기
class User2(val id:Int, val name:String, val address: String) fun saveUser(user:User2) { fun validate(value:String, fieldName:String){//이제 saveUser 함수의 user 파라미터를 중복 사용하지 않는다. if(value.isEmpty()){//필드 검증이 중복된다. throw IllegalArgumentException( "Can't save user ${user.id} : empty $fieldName") } } validate(user.name, "Name") validate(user.address, "Address") //user를 데이터베이스에 저장한다. }
검증 로직을 확장 함수로 추출하기
class User3(val id:Int, val name:String, val address: String) fun User3.validateBeforeSave() { fun validate(value:String, fieldName:String){ if(value.isEmpty()){//필드 검증이 중복된다. throw IllegalArgumentException( "Can't save user $id : empty $fieldName")// User의 프로퍼티를 직접 사용할 수 있다. } } validate(name, "Name") validate(address, "Address") } fun saveUser(user:User3){ user.validateBeforeSave() // 확장 함수를 호출한다. //user를 데이터베이스에 저장한다. }
'Kotlin' 카테고리의 다른 글
4장. 클래스, 객체, 인터페이스 - (2)뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언 (0) 2022.02.25 4장. 클래스, 객체, 인터페이스 - (1) 클래스 계층 정의 (0) 2022.02.24 3장. 함수 정의와 호출 - 1 (0) 2022.02.22 2장. 코틀린 기초 - 클래스와 프로퍼티 (0) 2022.02.21 2장. 코틀린 기초 - 함수와 변수 (0) 2022.02.20