- 함수 정의, 호출 방법
- 확장 함수와 프로퍼티를 사용해 자바 라이브러리를 코틀린 스타일로 적용하는 방법
컬렉션 만들기
val set = hashSetOf(1,7,53)
val list = arrayListOf(1,7,53)
val map = hashMapOf(1 to "one", 2 to "two")
println(set.javaClass) // class java.util.HashSet
- 기존 자바 컬렉션 사용
- 이렇게 사용하는 이유는 자바 코드와 상호작용하기가 더 쉽기 때문
- 아래와 같이 추가 기능 제공
println(strings.last()) // list
println(numbers.max()) // set
kotlin-collections의 소스이다.
/**
* Returns an empty read-only set. The returned set is serializable (JVM).
* @sample samples.collections.Collections.Sets.emptyReadOnlySet
*/
@kotlin.internal.InlineOnly
public inline fun <T> setOf(): Set<T> = emptySet()
코틀린 답게 함수 만들기
자바에 익숙한 나는 처음에 함수를 아래와 같이 만들 것이다
fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
joinToString(collection, separator=" ",prefix=" ",postfix=".") // 가독성 증가
디폴트 파라미터 값 - 자바에서는 생성자 파라미터 별로 많이 만들어야 함...
코틀린에서는 아래와 같이 디폴트 파라미터 값을 정할 수 있고, 파라미터의 순서도 바꿀 수 있다.
fun <T> joinToString(collection: Collection<T>, separator: String=" ", prefix: String = " ", postfix: String): String
joinToString(list, postfix=";", prefix="# ")
디폴트 값과 자바?
자바에는 디폴트 파라미터 개념이 없는데, 자바->코틀린 호출시 어떻게 처리 해야 하는가?
@JvmOverloads
fun joinToString(.... )
이라고 명시 해놓으면
코틀린 컴파일러가 자동으로 마지막 파라미터로부터 하나씩 생략한 오버로딩한 자바 메서드를 자동으로 만들어준다
정적 유틸 클래스 없애기 : 최상위 함수와 프로퍼티
자바에서 많이 보던 Util 클래스들은 코틀린에서 필요가 없다
package strings
fun joinToString(...) : String { ... }
// java compile 된 소스
package strings
public class JointKt {
public static String jointToString(...) { ... }
}
@file:JvmName("Stringfunctions")와 같이 명시해주면 컴파일시 해당 파일명을 이용한다 ( 10장서 부연 설명 )
const val UNIX_LINE_SEPARATOR = "\n" // 최상위 프로퍼티
// java compiled
public static final String UNIX_LINE_SEPARATOR = "\n"
메소드 다른 클래스에 추가하기 : 확장 함수와 확장 프로퍼티
코틀린의 핵심 목표 중 하나 - 기존 코드와 코틀린 코드 자연스럽게 통합하기
그러기 위해서는 기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러가지 편리한 기능을 사용할 수 있게 해줘야 한다.
확장함수가 그 역할을 해준다!
fun String.lastChar(): Char = this.get(this.length-1)
println("Kotlin".lastChar())
- String : 수신 객체 타입 ( receiver type )
- this : 수신 객체 ( receiver object )
- 확장 함수 내부에서 수신 객체의 메소드나 파라미터를 바로 사용할 수 있음 -> But, 캡슐화를 깨뜨리는 건 아니다! 확장 함수내에서는 private, protected 접근 불가
기존 예제에 확장 함수를 정의하여
fun <T> Collection<T>.joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) { // 수신 객체 this
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
확장 함수는 오버라이드 불가
- 코틀린은 확장 함수를 정적으로 결정하기 때문에 호출하는 방식은 정적 디스패치이다.
- 기존 함수 이름 = 확장함수 이름 이라면? 기존 함수가 우선순위가 높기 때문에 확장 함수 이름 지을 때 주의해야 함
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가 출력
// compiled java
View view = new Button();
ExtensionKt.showOff(view); // showOff 함수를 Extensions.kt 파일에 작성
확장 프로퍼티
- 프로퍼티 추가가 가능
- But, 상태를 저장할 적절한 방법이 없기 때문에 아무 상태도 가질 수 없음
- 왜 사용? 프로퍼티 문법으로 코드를 더 짧게 작성할 수 있음
val String.lastChar: Char
get() = get(length-1)
var StringBuilder.lastChar: Char
get() = get(length-1) // backing filed 사용 불가
set(value:Char) {
this.setCharAt(length-1,value)
}
컬렉션 처리 1 - 자바 컬렉션 API 확장
last(), max() 함수가 기존 자바 라이브러리에 추가가 되었는데, 위에서 본 확장 함수였던 것이다!
컬렉션 처리 2- 가변 인자 함수
fun listOf<T> (vararg values: T): List<T> {...}
fun main(args: Array<String>) {
val list = listOf("args : ", *args) // spread 연산자가 배열의 내용을 펼쳐줌
println(list)
}
컬렉션 처리 3- 중위 호출과 구조 분해 선언
val map = mapOf(1 to "one")
// to 정의 코드 간략본
infix fun Any.to(other:Any) = Pair(this, other)
- "to" 메소드는 중위 호출(infix call)
TODO - infix call 잘 쓴 코드? 실 활용 예 추가하기
아래와 같은 기능을 구조분해선언 ( destructuring declaration ) 이라 한다.
val (number, name) = 1 to "one"
문자열과 정규식 다루기
println("12.345-6.A".split(".","-")) // 12,345,6,A
var kotlinLogo = """| //
.| //
.|//"""
println(kotlinLogo.trimMargin("."))
코드 다듬기 : 로컬 함수와 확장
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}, emtpy ${fieldName}")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// user 데이터베이스에 저장
}
// REFACTOR 2 로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수 사용 가능
fun saveUser2(user: User) {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can`t save user ${user.id}, emtpy ${fieldName}")
}
}
validate(user.name, "Name")
validate(user.address, "Address")
// user 데이터베이스에 저장
}
// REFACTOR 3 - 확장 함수 ( 응집도 해치는거 아닌가? User 내부 코드 간결 + 사용하는쪽에서만 )
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can`t save user ${id}, emtpy ${fieldName}")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser3(user: User) {
user.validateBeforeSave()
// user 데이터베이스에 저장
}
- Refactor 3단계 - 확장 함수 이용하기
- 자바 관점 : User에다가 선언해서 응집도를 높여야 하는거 아닌가? => 코틀린 관점 : 여기서만 사용한다면 User 내부 코드는 간결하게 하고, 사용하는 쪽에서만 확장 함수를 이용하자!
'Kotlin' 카테고리의 다른 글
Kotlin in Action - 1장. 코틀린이란? 왜 필요한가? (0) | 2020.04.04 |
---|---|
Kotlin in Action 11장 : DSL 만들기 (0) | 2020.04.03 |
Kotlin In Action 7장. 연산자 오버로딩과 기타 관례 (0) | 2020.04.02 |
Kotlin In Action - 5장. 람다로 프로그래밍 (0) | 2020.03.29 |
Kotlin In Action - 2장. 코틀린 기초 (0) | 2020.03.29 |