두번째, Idioms입니다.
Kotlin에서 자주 사용되는 관용어구 모음입니다. 코틀린 코딩 패턴이라고도 볼 수 있는 내용입니다. 일반적으로, 따라주시면 좋습니다. 공식 문서 내용에는 좋은 idiom이 있는 경우 PR(Pull Request)을 달라고 적혀 있기도 합니다.
이 부분은 이런 경우 이런 식으로 쓴다는 관용적 표현을 설명하고 있기 때문에 문법에 대한 상세 설명은 나오지 않는 편입니다. 언어가 지향하는 관용적 표현(패턴)을 습득하는 것은 해당 언어에 빠르게 익숙해지는 한 방법이라고 할 수 있습니다. 그러므로, 이 부분에서는 상세 내용보다는 패턴에 초점을 맞추어서 보시면 될 거 같습니다.
DTO (POJOs/POCOs) 만들기
data class Customer(val name: String, val email: String)
위와 같이 data class로 클래스를 정의하면, Custormer 클래스에 다음과 같은 기능을 제공합니다.
- 모든 프로퍼티에 getter를 제공합니다(var 프로퍼티에 대해서는 setter도 제공합니다)
- equals()
- hashCode()
- toString()
- copy()
- component1(), component2(), ..., 모든 프로퍼티 수만큼. 상세 내용은 여기를 참고하세요.
Kotlin에는 data 클래스가 추가됐습니다. DTO(Data Transfer Object) 클래스 같이 보통 정보를 담는데 사용하는 클래스를 위해 추가된 것입니다. 자바 개발자라면 익숙한 Lombok의 데이터 클래스 관련 기능들이 언어에 기본적으로 추가돼어, 별도의 라이브러리에 의존성 없이 편리하게 프로그래밍할 수 있게 해 줍니다.
POJO는 자바 개발자에게 익숙한 두문자인 Plain Old Java Object이며, POCO는 Plain Old Clr Object로 C#(닷넷) 환경에서의 POJO라고 생각하시면 됩니다(CLR은 Common Language Runtime입니다).
함수 매개변수의 기본값 지정
fun foo(a: Int = 0, b: String = "") { ... }
리스트 필터링
val positives = list.filter { x -> x > 0 }
또는 축약형으로,
val positives = list.filter { it > 0 }
컬렉션에 요소(element)가 포함돼 있는지 확인하는 방법
if ("john@example.com" in emailsList) { ... }
if ("jane@example.com" !in emailsList) { ... }
문자열 보간(interpolation)
보간: 끼워넣다. 문자열 내에 이름, 표현식을 끼워 넣어 원하는 문자열을 완성한다라고 이해하시면 됩니다.
println("Name $name")
인스턴스 확인
when (x) {
is Foo -> ...
is Bar -> ...
else -> ...
}
읽기 전용(read-only) 리스트
val list = listOf("a", "b", "c")
코틀린은 불변성을 강조하고 관련된 지원을 많이 합니다.
읽기 전용 맵
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
맵이나 페어(Pair)를 요소로 갖는 리스트의 열거(traverse)
for ((k, v) in map) {
println("$k -> $v")
}
k와 v라는 이름을 꼭 써야하는 것이 아니라 name, age 같이 원하는대로 사용하면 됩니다. Pair는 Kotlin이 지원하는 자료형으로 말 그대로 한 쌍의 데이터를 표현할 때 사용합니다.
val pairs = listOf(Pair(1,"x"), Pair(2, "y"))
for ((p1, p2) in pairs) {
println("$p1 -> $p2")
}
특정 범위 반복
for (i in 1..100) { ... } // 폐쇄형. 100 포함
for (i in 1..<100) { ... } // 개방형. 100 포함하지 않음
for (x in 2..10 step 2) { ... } // 2씩 증가
for (x in 10 downTo 1) { ... } // 1씩 감소
(1..10).forEach { ... }
레이지(lazy) 프로퍼티
val p: String by lazy { // 첫번째 접근 시에 싱에 값이 계산됩니다.
// 문자열 처리
}
프로퍼티의 값을 첫 접근시까지 초기화를 지연할 때 lazy를 사용합니다.
lazy에 대한 번역을 게으른/게으름 또는 지연 등으로 할 수도 있을 거 같습니다만, 일단 인식이 빠르게 레이지로 표기합니다. 이 용어에 대한 것은 연재 계속 진행되는 동안 고민을 좀 해 보려고 합니다.
확장 함수(Extentions Functions)
fun String.spaceToCamelCase() { ... }
"Convert this to camelcase".spaceToCamelCase()
확장함수는 기존에 있는 클래스나 인터페이스를 상속받지 않고도 기능을 확장(추가)할 수 있게 해 줍니다. 이 부분은 확장 함수에 대한 상세 내용을 다루는 부분은 아니기 때문에 '확장 함수를 사용할 수 있다' 정도로 아시면 될 거 같습니다. 실제로, 확장 함수는 필요한 부분에서 자유롭게 사용하라고 얘기하고 있기 때문에 기억해 둘 필요가 있습니다. 실제로 사용해 보면 상당히 유용한 기능입니다. 당장, 상세 사용법이 궁금한 분은 여기를 보시면 됩니다.
싱글톤 만들기
object Resource {
val name = "Name"
}
Java에서는 싱글톤 클래스를 만들려면 약간 복잡한 코딩이 필요하지만, Kotlin에서는 이렇게 바로 선언하면 싱글톤을 사용할 수 있습니다. 이 문법 역시 Kotlin이 실용성을 추구하는 언어라는 것을 보여주는 하나의 예입니다.
추상 클래스 인스턴스화
abstract class MyAbstractClass {
abstract fun doSomething()
abstract fun sleep()
}
fun main() {
val myObject = object : MyAbstractClass() {
override fun doSomething() {
// ...
}
override fun sleep() { // ...
}
}
myObject.doSomething()
}
if-not-null 축약형
val files = File("Test").listFiles()
println(files?.size) // files가 null이 아니면 size가 출력되고 null이면 null이 출력됩니다.
if-not-null-else 축약형
val files = File("Test").listFiles()
// 간단히 null일 경우 대체값 지정
println(files?.size ?: "empty") // files가 null이면 "empty" 출력
// 보다 복잡하게 대체값을 연산해야 하는 경우에는 `run` 사용
val filesSize = files?.size ?: run {
val someSize = getSomeSize()
someSize * 2
}
println(filesSize)
?: 는 바로 밑에 나옵니다.
run의 경우는 Kotlin에서 제공하는 범위 함수(scope function) 중에 하나입니다. 일단 여기서는 관용적인 코딩에 관한 것이므로 if-not-null-else에서 코드 블록이 필요한 경우 이렇게 하면 된다고 생각하시면 됩니다. 상세 내용이 궁금하신 분은 여기를 참고하시면 됩니다. 이 번 연재에서는 다루지 않겠지만, 추후 별도의 글로 범위 함수에 대해서 한 번 다루도록 하겠습니다.
null인 경우 문장(statement) 실행
val values = ...
val email = values["email"] ?: throw IllegalStateException("Email is missing!")
?: 는 엘비스 프레슬리의 헤어 스타일을 닮았다고 하여 엘비스 연산자라고 부릅니다. 앞의 표현식이 null일때 이 연산자의 뒤의 문장이 실행됩니다.
컬렉션이 비어 있을 수 있는 경우 첫번째 아이템 조회 방법
val emails = ... // might be empty
val mainEmail = emails.firstOrNull() ?: ""
Java와 Kotlin 사이에 첫번째 아이템을 얻는 방법에 대한 차이점은 여기를 참고하세요.
null이 아닌 경우에 실행
val value = ...
value?.let {
... // value가 null이 아니면 이 블록이 실행됩니다.
}
널 가능한 값이 널이 아닌 경우에 맵 연산
val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValue
// value나 transformValue()의 결과가 null인 경우 defaultValue가 반환됩니다.
when 문 반환
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}
try-catch 표현식
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// Working with result
}
if 표현식
val y = if (x == 1) {
"one"
} else if (x == 2) {
"two"
} else {
"other"
}
Unit를 반환하는 메소드의 빌더 스타일 사용법
fun arrayOfMinusOnes(size: Int): IntArray {
return IntArray(size).apply { fill(-1) }
}
apply는 Unit이 반환 타입입니다. apply를 이용한 빌더 스타일입니다.
단일 표현식 함수
fun theAnswer() = 42
위의 함수는 풀어쓰면 다음과 같습니다.
fun theAnswer(): Int {
return 42
}
이런 형식은 다른 관용구와 효과적으로 결합하여 코드를 보다 짧게 작성할 수 있게 합니다. 예를 들어, when 표현식에서는 다음과 같이 사용할 수 있습니다.
fun transform(color: String): Int = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
with 문으로 특정 인스턴스에 다중 메소드 호출하기
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { //draw a 100 pix square
penDown()
for (i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
apply를 사용하여 객체의 프로퍼티 값 설정
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA
}
생성자에 없는 프로퍼티를 초기화할 때 유용합니다.
Java 7 스타일의 try-with-resources
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}
제네릭 타입의 정보가 필요한 제네릭 함수
// public final class Gson {
// ...
// public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
// ...
inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T = this.fromJson(json, T::class.java)
이 관용구는 꼭 기억해 두시면 좋습니다. Kotlin에서 인라인 함수가 추가되고 reified 타입이 추가됨으로써, Java에서는 위의 예제코드에서 주석 내용처럼 제네릭을 통해 T의 정보를 알 수 있을 거 같은데도, Class<T> classOfT 부분 반드시 필요했던 것을 제거할 수 있게 됐습니다(제네릭 정보는 런타임에는 사라지기 때문에 논리적으로는 당연한 거기는 합니다만, 어쨌든 한 방에 되지 않아 불편했죠).
여기서는 이 패턴이 있다는 것을 기억하시면 될 거 같습니다. 추후, 여기에서 상세히 다줘질 예정입니다.
reified는 어떻게 발음할까요?
발음기호로 하면 이렇습니다. [rí:əfàid,réiə-] 유튜브에서 kotlin reified 관련 영상을 보면 외국인들은 보통 전자로 리어파이드로 많이 발음하는 것을 볼 수 있습니다.
두 변수의 값 상호 교환
var a = 1
var b = 2
a = b.also { b = a }
해야할 일(TODO) 표시
Kotlin 표준 라이브러리에는 TODO()라는 함수가 포함돼 있습니다. 이 함수는 항상 NotImplementError를 던집니다. 이 함수의 반환 타입은 Nothing이기 때문에 어떠한 함수 안에도 해당 함수의 반환 타입과 무관하게 사용할 수 있습니다. 또한, 이유를 인수로 받는 유형도 오버로딩돼 있습니다.
fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")
인텔리제이 IDEA의 Kotlin 플러그인은 TODO()를 이해하고 자동으로 해당 지정을 TODO 도구 창에 추가해 줍니다.
'Kotlin' 카테고리의 다른 글
공식 문서로 배우는 Kotlin - 6. Basic Types - Booleans (0) | 2024.03.02 |
---|---|
공식 문서로 배우는 Kotlin - 5. Basic Types - Unsigned integer types (0) | 2024.03.01 |
공식 문서로 배우는 Kotlin - 4. Basic Types - Numbers (0) | 2024.03.01 |
공식 문서로 배우는 Kotlin - 3. Basics - Coding conventions (0) | 2024.02.29 |
공식 문서로 배우는 Kotlin - 1. Basics - Basic syntax (1) | 2024.02.26 |