Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 스프링 컨테이너와 스프링 빈
- 싱글톤 컨테이너
- Kotlin in action 3장
- 스프링 핵심 원리 이해
- 자바 ORM 표준 JPA 프로그래밍 7장
- Kotlin
- Kotlin in action 5장
- 기능개발 python
- Kotlin in action 6장
- 코틀린
- spring
- 백준 20055 컨베이어 벨트 위의 로봇
- kotlin in action 정리
- 스프링 핵심 원리
- 13460 구슬탈출 2
- 백준 13460 Python
- KotlinInAction
- 코틸린인액션
- Kotlin in action 10장
- 고급매핑
- Python
- 20055 컨베이어 벨트 위의 로봇
- 코틀린인액션
- 7장 고급매핑
- 객체 지향 설계와 스프링
- 20055
- 백준
- 스프링 핵심 원리 - 기본편
- 컨베이어 벨트 위의 로봇 Python
- Kotlin In Action
Archives
- Today
- Total
기록하는 습관
[Kotlin In Action]3장 - 함수 정의와 호출 본문
** 이 글은 Kotlin In Action을 읽고 정리한 글입니다. **
1. 코틀린에서 컬렉션 만들기
- 코틀린은 자체 컬렉션을 제공하지 않는다.
- 표준 자바 컬렉션을 활용하면 자바 코드와 상호작용하기 훨씬 더 쉽기 때문.
val set = hashSetOf(1, 7, 53)
val list = arrayListOf(1, 7, 53)
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
- 코틀린 컬렉션은 자바 컬렉션과 똑같은 클래스다.
- 하지만 코틀린에서는 자바보다 더 많은 기능을 쓸 수 있다.
- ex) last(), max()
val strings = listOf("first", "second", "fourteeth")
println(strings.last()) // fourteeth
val numbers = setOf(1, 14, 2)
println(numbers.max()) // 14
2. 함수를 호출하기 쉽게 만들기
- 자바 컬렉션에는 디폴트 toString 구현이 들어있다. 하지만 이 toString의 출력 형식은 고정돼 있다.
- 커스텀을 위해서는 자바에서는 구아바나 아파치 커먼즈 같은 서드파티 프로젝트를 추가하거나 직접 관련 로직을 구현해야 한다.
- 코틀린에서는 함수가 표준 라이브러리에 이미 들어있다.
val list = listOf(1, 2, 3)
println(list) // [1, 2, 3]
이번에는 함수를 직접 구현해보자.
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()
}
- 이 함수는 제네릭하다. 어떤 타입의 값ㅇ르 원소로 하는 컬렉션이든 처리 가능.
val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")")) // (1; 2; 3)
- 개선하고 싶은 부분
- 매번 네 인자를 모두 전달하지 않도록 해보자.
2-1. 이름 붙인 인자
- 코틀린에서는 함수에 전달하는 인자 중 일부 또는 전부의 이름을 명시할 수 있다.
- 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
joinToString(collection, separator=" ", prefix=" ", postfix=".")
2-2. 디폴트 파라미터 값
- 자바에서는 일부 클래스에서 오버로딩한 메서드가 많아진다는 문제가 있다.
- 코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로 이런 오버로드 중 상당수를 피할 수 있다.
- 일반 호출 문법을 사용하려면 함수를 선언할 때와 같은 순서로 인자를 지정해야 한다.
- 이름 붙은 인자를 사용하는 경우에는 인자 목록의 중간에 있는 인자를 생략하고, 지정하고 싶은 인자를 이름에 붙여서 순서와 관계없이 지정할 수 있다.
- 디폴트 파라미터의 값은 함수를 호출하는 쪽이 아니라 함수 선언 쪽에서 지정된다.
fun <T> Collection<T>.joinToString(
separator: String = ", ", // 디폴트 값이 지정된 파라미터들
prefix: String = "",
postfix: String = ""
): String
2-3. 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티
- 다양한 정적 메서드를 모아두는 역할만 담당하며, 특별한 상태나 인스턴스 메서드는 없는 클래스가 생겨난다.
- ex) JDK의 Collections 클래스, Util
최상위 함수
- 코틀린에서는 함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시킬 수 있다.
/* join.kt */
package strings
fun joinToString(...): String{ ... }
- JVM이 클래스 안에 들어있는 코드만을 실행할 수 있기 때문에 컴파일러는 아래 파일을 컴파일 할 때 새로운 클래스를 정의해준다.
/* java */
package strings;
public class JoinKt {
public static String joinToString(...) { ... }
}
- 클래스의 이름은 최상위 함수가 들어있던 코틀린 소스 파일 이름과 대응한다.
- 이름을 변경하고 싶으면 @JvmName 어노테이션을 사용하면 된다.
최상위 프로퍼티
- 함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다.
- 다른 모든 프로퍼티처럼 접근자 메서드를 통해 자바 코드에 노출된다. (getter, setter 접근 가능)
/* kotlin */
const val UNIX_LINE_SEPARATOR = "\n"
/* java */
public static final String UNIX_LINE_SEPARATOR = "\n";
3. 메서드를 다른 클래스에 추가: 확장함수와 확장 프로퍼티
확장함수
- 어떤 클래스와 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스 밖에 선언된 함수이다.
- 확장 함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이면 된다.
- 수신 객체 타입: 클래스 이름
- 수신 객체: 확장함수가 호출되는 대상이 되는 값
fun String.lastChar(): Char = this.get(this.length - 1)
// 수신 객체 타입: String
// 수신 객체: this
- 어떤 면에서 이는 String 클래스에 새로운 메서드를 추가하는 것과 같다.
- 그루비언어와 같은 다른 JVM 언어로 작성된 클래스도 확장 가능하다.
- 일반 메서드와 마찬가지로 확장 함수 본문에도 this를 사용할 수 있고, 생략할 수 있다.
- 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 private 멤버나 protected 멤버를 사용할 수 없다.
3-1. 임포트와 확장 함수
- 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트 해야 한다.
- as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있다.
3-2. 자바에서 확장 함수 호출
- 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메서드다.
- 확장 함수를 StringUtil.kt 파일에 정의했다면 자바에서 아래와 같이 호출 가능하다.
/* java */
char c = StringUtilKt.lastChar("Java");
3-3. 확장 함수로 유틸리티 함수 정의
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
- Collection<T>에 대한 확장함수 선언.
- joinToString을 마치 클래스의 멤버인 것처럼 호출할 수 있다.
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
- 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수도 있다.
3-4. 확장 함수는 오버라이드 할 수 없다.
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
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!
- 확장 함수는 클래스의 일부가 아니다. 클래스 밖에 선언된다.
- 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되지, 그 변수에 저장된 동적인 타입에 의해 확장 함수가 결정되지 않는다.
- 확장함수는 정적으로 결정된다!
- 어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출된다. (멤버 함수의 우선순위가 더 높다)
3-5. 확장 프로퍼티
val String.lastChar: Char
get() = get(length - 1)
- 확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있다.
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
- 자바에서 확장 프로퍼티를 사용하고 싶다면 StringUtilKt.getLastChar(”Java”) 처럼 항상 게터나 세터를 명시적으로 호출해야 한다.
4. 컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원
4-1. 자바 컬렉션 API 확장
- 어떻게 자바 라이브러리 클래스의 인스턴스인 컬렉션에 대해 코틀린이 새로운 기능을 추가할 수 있을까?
- last와 max는 모두 확장 함수 였다. (last는 List 클래스의 확장 함수다.)
4-2. 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
- 가변 길이 인자는 메서드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 자바 컴파일러가 배열에 그 값들을 넣어주는 기능이다.
- 파라미터 앞에 vararg 변경자를 붙인다.
- 자바에서는 배열을 그냥 넘기지만, 코틀린에서는 배열을 명시적으로 풀어 배열의 각 원소가 전달되게 해야 한다.
- 스프레드 연산자(*)가 이런 역할을 해준다.
4-3. 값의 쌍 다루기: 중위 호출과 구조 분해 선언
중위 호출
- 인자가 하나 뿐인 일반 메서드나 인자가 하나 뿐인 확장 함수에 중위 호출을 사용할 수 있다.
- 함수를 중위 호출에 사용할 수 있게 하고 싶다면 infix 변경자를 함수 선언 앞에 추가한다.
1 to "one" // 중위 호출
infix fun Any.to(other: Any) = Pair(this, other)
구조 분해
val (number, name) = 1 to "one"
- Pair 인스턴스 외 다른 객체에도 구조 분해를 적용할 수 있다.
5. 문자열과 정규식 다루기
5-1. 문자열 나누기
- 코틀린 문자열은 자바 문자열과 같다.
- 정규식을 파라미터로 받는 함수는 String이 아닌 Regex 타입의 값을 받는다.
- 코틀린 정규식 문법은 자바와 똑같다.
println("12.345-6.A".split("\\.|-".toRegex()))
5-2. 정규식과 3중 따옴표로 묶은 문자열
val regex = """(.+)/(.+)\.(.+)""".toRegex()
5-3. 여러 줄 3중 따옴표 문자열
val kotlinLogo = """| //
.|//
.|/ \"""
6. 코드 다듬기: 로컬 함수와 확장
- 코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
- 로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.
- 확장 함수를 로컬 함수로 정의할 수 있다.
// 기존
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")
}
// Save user to the database
}
// 로컬 함수를 사용해 코드 중복 줄이기
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}: empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}
// 검증 로직을 확장 함수로 추출하기
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user $id: empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
// Save user to the database
}
fun saveUser(user: User) {
user.validateBeforeSave()
}
'스터디 > Kotlin In Action' 카테고리의 다른 글
[Kotlin In Action]6장 - 코틀린 타입 시스템 (0) | 2023.03.13 |
---|---|
[Kotlin In Action]5장 - 람다로 프로그래밍 (0) | 2023.02.27 |
[Kotlin In Action]4장 - 클래스, 객체, 인터페이스 (0) | 2023.02.20 |
[Kotlin In Action]2장 - 코틀린 기초 (0) | 2023.01.24 |
[Kotlin In Action]1장 - 코틀린이란 무엇이며, 왜 필요한가? (0) | 2023.01.16 |
Comments