-
5. 추가적으로 알아두어야 할 코틀린 특성Kotlin/자바 개발자를 위한 코틀린 입문 2023. 7. 29. 10:31
19. 코틀린의 이모저모
1. Type Alias 와 as import
- 긴 이름의 클래스 혹은 함수 타입이 있을 때 축약하거나 더 좋은 이름을 쓰고 싶다!
typealias FruitFilter = (Fruit) -> Boolean fun filterFruits2(fruit: List<Fruit>, filter: FruitFilter) { } // 이름 긴 클래스를 컬렉션에 사용할 때도 간단히 줄일 수 있다. data class UltraSuperGuardianTribe( val name: String ) typealias USGTMap = Map<String, UltraSuperGuardianTribe>
- 다른 패키지의 같은 이름 함수를 동시에 가져오고 싶다면?!
- as import : 어떤 클래스나 함수를 임포트할 때 이름을 바꾸는 기능import com.lannstark.lec19.a.printHelloWorld as printHelloWorldA import com.lannstark.lec19.b.printHelloWorld as printHelloWorldB fun main() { printHelloWorldA() printHelloWorldB() }
2. 구조분해와 componentN 함수
- 구조분해 : 복합적인 값을 분해하여 여러 변수를 한 번에 초기화하는 것
val person = Person("박현민", 100) val (name, age) = person
ㆍ순서가 정해져있다. 첫번째 프로퍼티를 첫번째 변수, 두번째 프로퍼티를 두번째 변수에 넣어준다.
- Data Class는 componentN이란 함수도 자동으로 만들어준다!
val name = person.component1() val age = person.component2()
- Data Class가 아닌데 구조분해를 사용하고 싶다면, componentN 함수를 직접 구현해줄 수도 있다.
class Person2( val name: String, val age: Int ) { operator fun component1(): String { return this.name } operator fun component2(): Int { return this.age } }
val map = mapOf(1 to "A", 2 to "B") for ((key, value) in map.entries) { }
- 이 문법 역시 구조분해 이다.
3. Jump와 Label
- return / break / continue
ㆍreturn : 기본적으로 가장 가까운 enclosing function 또는 익명함수로 값이 반환된다.
ㆍbreak : 가장 가까운 루프가 제거된다
ㆍcontinue : 가장 가까운 루프를 다음 step으로 보낸다.
- for 문 및 while 문에서 break, continue 기능은 동일하다.
ㆍ단, forEach 문과 함께 break 또는 continue를 사용할 수 없다.
- forEach문과 함께 break 또는 continue를 꼭 쓰고 싶다면?!
run { numbers.map { number -> number + 1 } .forEach { number -> if (number == 3) { return@run // break 역할 } println(number) } }
numbers.map { number -> number + 1 } .forEach { number -> if (number == 3) { return@forEach // continue 역할 } println(number) }
- break, continue를 사용할 때엔 가급적 익숙한 for문 사용을 추천한다.
- 코틀린에는 라벨이라는 기능이 있다.
- 특정 expression에 라벨이름@ 을 붙여 하나의 라벨로 간주하고 break, continue, return 등을 사용하는 기능
loop@ for(i in 1..100) { for(j in 1..100) { if(j == 2) { break@loop } println("$i $j") } }
- 라벨을 사용한 Jump는 사용하지 않는 것을 강력 추천한다.
ㆍ복잡도가 증가하고 유지보수가 어려워진다.
4. TakeIf와 TakeUnless
fun getNumberOrNull(): Int? { return if (number <= 0) { null }else { number } }
- Kotlin에서는 method chaning을 위한 특이한 함수를 제공한다.
fun getNumberOrNullV2(): Int? { return number.takeIf { it > 0 } }
- takeIf : 주어진 조건을 만족하면 그 값이, 그렇지 않으면 null이 반환된다.
fun getNumberOrNullV3(): Int? { return number.takeUnless { it <= 0 } }
- takeUnless : 주어진 조건을 만족하지 않으면 그 값이, 그렇지 않으면 null이 반환된다.
정리
- 타입에 대한 별칭을 줄 수 있ㄷ는 typealias 라는 키워드가 존재한다.
- Import 당시 이름을 바꿀 수 있는 as import 기능이 존재한다.
- 변수를 한 번에 선언할 수 있는 구조분해 기능이 있으며 componentN 함수를 사용한다.
- for문, while문과 달리 forEach에는 break와 continue를 사용할 수 없다.
- takeIf와 takeUnless를 활용해 코드양을 줄이고 method chaning 을 활용할 수 있다.
20. 코틀린의 scope function
1. scope function이란 무엇인가?
- scope : 영역 / fucntion : 함수
- scope function : 일시적인 영역을 형성하는 함수
fun printPerson(person: Person?) { // if(person != null) { // println(person.name) // println(person.age) // } person?.let { println(it.name) println(it.age) } }
- Safe Call (?.) 을 사용 : person이 null이 아닐때에 let을 호출
- let : scope function의 한 종류
ㆍ확장함수. 람다를 받아, 람다 결과를 반환한다.
public inline fun <T, R> T.let(blcok: (T) -> R): R { return block(this) }
- 람다를 사용해 일시적인 영역을 만들고, 코드를 더 간결하게 만들거나, method chaning에 활용하는 함수를 scope function이라고 한다.
2. scope function의 분류
- 절대 외우지마라!
it 사용 this 사용 람다의 결과 리턴 let run 객체 그자체 리턴 also apply with ( 확장함수 X ) ㆍthis : 생략이 가능한 대신, 다른 이름을 붙일 수 없다.
ㆍit : 생략이 불가능한 대신, 다른 이름을 붙일 수 있다.
val value1 = person.let { p -> p.age } val value2 = person.run { age }
ㆍ let 은 일반함수를 받는다.
ㆍrun 은 확장함수를 받는다.
* 확장함수에서는 본인 자신을 this로 호출하고, 생략할 수 있었다.
val person = Person("박현민", 100) with(person) { println(name) println(this.age) }
- with(파라미터, 람다) : this를 사용해 접근하고, this는 생략 가능하다.
3. 언제 어떤 scope function을 사용해야 할까?
1. let
- 하나 이상의 함수를 call chain 결과 호출할 때
val strings = listOf("APPLE", "CAR") strings.map { it.length } .filter { it > 3 } // .let(::println) .let { lengths -> println(lengths) }
- non-null 값에 대해서만 code block을 실행시킬 때
val length = str?.let { println(it.uppercase()) it.length }
ㆍ제일 많이 사용
- 일회성으로 제한된 영역에 지역 변수를 만들 때
val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = numbers.first() .let {firstItem -> if (firstItem.length >= 5) firstItem else "!$firstItem!" }.uppercase() println(modifiedFirstItem)
ㆍ주로 쓰지 않음
2. run
- 객체 초기화와 반환 값의 계산을 동시에 해야 할 때
val person = Person("박현민", 100).run(personRepository::save)
ㆍ객체를 만들어 DB에 바로 저장하고, 그 인스턴스를 활용할 때
val person = Person("박현민",100).run { hobby = "독서" personRepository.save(this) }
ㆍ개인적으로는 잘 사용하지 않는다.
- 반복되는 생성 후처리는 생성자, 프로퍼티, init blolck으로 넣는 것이 좋다.
val person = personRepository.save(Person("박현민", 100))
3. apply
- apply 특징 : 객체 그 자체가 반환된다.
- 객체 설정을 할 때에 객체를 수정하는 로직이 call chain 중간에 필요할 때
- Test Fixture를 만들 때
fun createPerson( name: String, age: Int, hobby: String ): Person { return Person( name = name, age = age, ).apply { this.hobby = hobby } }
val person = Person("박현민", 100) person.apply { this.growOld() } .let{ println(it) }
4. also
- also 특징 : 객체 그 자체가 반환된다.
- 객체를 수정하는 로직이 call chain 중간에 필요할 때
mutableListOf("one", "two", "three") .also { println("four 추가 이전 지금 값: $it") } .add("four")
5. with
- 특정 객체를 다른 객체로 변환해야 하는데, 모듈 간의 의존성에 의해 정적 팩토리 혹은 toClass 함수를 만들기 어려울 때
return with(person) { PersonDto( name = name, age = age, ) }
ㆍthis를 생략할 수 있어 필드가 많아도 코드가 간결해진다.
4. scope function과 가독성
- scope function을 사용한 코드가 그렇지 않은 코드보다 가독성 좋은 코드일까?!
// 1번 코드 if(person != null && person.isAdult) { view.showPerson(person) } else { view.showError() } // 2번 코드 person?.takeIf { it.isAdult } ?.let(view::showPerson) ?: view.showError()
ㆍ1번 코드 : 전통적인 if와 else를 활용
ㆍ2번 코드 : scope function을 활용한 코틀린스러운 코드
▷ view.showPerson() 이 null을 반환한다면?! - elivs 연산자에서 view.showError 가 출력
showPerson도 불리고 showError도 불리는 버그가 생길 수 있다.
- 개인적(강사)으로 1번 코드가 훨씬 좋은 코드라고 생각한다.
ㆍ1. 구현 2는 숙련된 코틀린 개발자만 더 알아보기 쉽다.
어쩌면 숙련된 코틀린 개발자도 잘 이해하지 못할 수 도 있다.
ㆍ2. 구현 1의 디버깅이 쉽다.
ㆍ3. 구현이 1이 수정도 더 쉽다.
- 사용 빈도가 적은 관용구는 코드를 더 복잡하게 만들고 이런 관용구들을 한 문장 내에서 조합해 사용하면 복잡성이 훨씬 증가한다.
- 하지만 scope function을 사용하면 안되는 것도 아니다! 적절한 convention을 적용하면 유용하게 활용할 수 있다.
정리- 코틀린의 scope function은 일시적인 영역을 만들어 코드를 더 간결하게 하거나, method chain에 활용된다.
- scope function의 종류에는 let / run / also / apply / with 가 있었다.
- scope function을 사용한 코드는 사람에 따라 가독성을 다르게 느낄 수 있기 때문에, 함께 프로덕트를 만들어 가는 팀끼리 convention을 잘 정해야 한다.
강의에서 다루지 못한 내용
- 제네릭
- 리플렉션
- 복잡한 함수형 프로그래밍
- DSL
- 동시성 프로그래밍 (코루틴)
'Kotlin > 자바 개발자를 위한 코틀린 입문' 카테고리의 다른 글
4. 코틀린에서의 FP (0) 2023.07.26 3. 코틀린에서의 OOP (0) 2023.07.10 2. 코틀린에서 코드를 제어하는 방법 (0) 2023.07.06 1. 코틀린에서 변수와 타입, 연산자를 다루는 방법 (0) 2023.07.05