이 글은 Kotlin Reference(v1.4.21)를 번역(?)해 보면서 학습한 내용을 정리한 글입니다.
영어가 익숙하지 않아 잘못된 번역이 있을 수 있습니다. 학습을 하면서 개인적인 생각(주석으로 표시)도 포함되어 있을 수 있으니, 이 부분을 감안하고 봐주시면 감사하겠습니다 🙇🏻♂️
패키지의 명세는 소스 파일의 맨 위에 위치해야 합니다.
package my.demo
import kotlin.text.*
// ...
소스 파일은 파일 시스템에서 임의로 배치될 수 있기 때문에 디렉토리와 패키지를 일치시킬 필요는 없습니다.
Kotlin 애플리케이션의 entry point는 main
함수입니다.
fun main() {
println("Hello, world!")
}
이 함수는 두 개의 Int
매개변수와 Int
반환 타입을 가집니다:
fun sum(a: Int, b: Int): Int {
return a + b
}
이 함수는 표현식으로 구성되어 있고 유추된 반환 타입을 가집니다:
fun sum(a: Int, b: Int) = a + b
반환 타입을 유추하는 것은 표현식으로 함수가 작성된 경우에만 가능합니다. 블록으로 함수를 정의한 경우에는 반환 타입을 명시해줘야 합니다 (Unit 타입 제외)
이 함수는 의미 없는 값을 반환합니다:
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
(의미 없는 반환값인) Unit
반환 타입은 생략할 수 있습니다:
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
읽기-전용 지역 변수(Read-only local variable)는 val
이란 키워드로 선언할 수 있습니다. 이는 한 번만 값을 할당할 수 있습니다:
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
c = 3 // deferred assignment
Java에서는
final
키워드로 이와 같은 역할을 하는 변수를 선언할 수 있었습니다. 그러나 변수의 선언과 동시에 할당을 해야만 했는데, Kotlin은 선언한 후에 할당을 하는 것도 가능합니다.
var
키워드로 선언한 변수(variables)는 값을 재할당 할 수 있습니다:
var x = 5 // `Int` type is inferred
x += 1
최상위(Top-level) 변수입니다:
val PI = 3.14
var x = 0
fun incrementX() {
x += 1
}
대부분의 현대 언어들처럼, 단일 행(single-line, end-of-line) 주석과 블록(block) 주석을 제공합니다:
// This is an end-of-line comment
/* This is a block comment
on multiple lines. */
블록 주석은 중첩이 가능합니다:
/* The comment starts here
/* contains a nested comment */
and ends here. */
var a = 1
// simple name in template:
val s1 = "a is $a"
a = 2
// arbitrary expression in template:
val s2 = "${s1.replace("is", "was")}, but now is $a"
Java 진영의 템플릿 엔진(thymeleaf, mustache 등)과 비슷한 것 같습니다.
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
Kotlin에서는 if
또한 표현식으로 사용될 수 있습니다:
fun maxOf(a: Int, b: Int) = if (a > b) a else b
null
값이 가능한 참조에는 null
일 수 있다고(nullable) 명시적으로 표시해야 합니다.
(아래는) str
이 integer가 아닐 경우 null
을 반환합니다:
fun parseInt(str: String): Int? {
// ...
}
null
일 수 있는 함수를 사용합니다:
fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)
// Using `x * y` yields error because they may hold nulls.
if (x != null && y != null) {
// x and y are automatically cast to non-nullable after null check
println(x * y)
}
else {
println("'$arg1' or '$arg2' is not a number")
}
}
또는
// ...
if (x == null) {
println("Wrong number format in arg1: '$arg1'")
return
}
if (y == null) {
println("Wrong number format in arg2: '$arg2'")
return
}
// x and y are automatically cast to non-nullable after null check
println(x * y)
is
연산자는 표현식이 어떤 타입(type)의 인스턴스(instance)인지 확인합니다. 만약, 불변 지역 변수(immutable local variable)나 속성(property)이 특정한 타입으로 확인되면, 명시적으로 형 변환(cast)을 하지 않아도 됩니다:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` is automatically cast to `String` in this branch
return obj.length
}
// `obj` is still of type `Any` outside of the type-checked branch
return null
}
또는
fun getStringLength(obj: Any): Int? {
if (obj !is String) return null
// `obj` is automatically cast to `String` in this branch
return obj.length
}
또는 심지어
fun getStringLength(obj: Any): Int? {
// `obj` is automatically cast to `String` on the right-hand side of `&&`
if (obj is String && obj.length > 0) {
return obj.length
}
return null
}
어떤 위치에서든 앞에서 나온 조건들로 타입을 특정할 수 있다면, 이후에 명시적인 형 변환을 하지 않아도 가능한 것 같습니다.
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
Java의
Enhanced for loop
에 해당하는 것 같습니다.
또는
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
Java의
switch case
와 비슷한 것 같습니다.
in
연산자를 사용하여 숫자가 범위 내에 있는지 확인합니다:
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
숫자가 범위 밖에 있는지 확인합니다:
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range, too")
}
범위에 걸쳐서 반복합니다:
for (x in 1..5) {
print(x)
}
또는 건너뛰면서 반복합니다:
for (x in 1..10 step 2) {
print(x)
}
println()
for (x in 9 downTo 0 step 3) {
print(x)
}
컬렉션(collection)을 반복합니다:
for (item in items) {
println(item)
}
Java의
Enhanced for loop
에 해당하는 것 같습니다.
컬렉션 내에 객체가 존재하는지를 in
연산자를 사용하여 확인합니다:
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
람다(lambda) 표현식을 사용하여 컬렉션을 필터링, 매핑합니다:
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
Java의
Stream
과 비슷해 보입니다.
val rectangle = Rectangle(5.0, 2.0)
val triangle = Triangle(3.0, 4.0, 5.0)
Java의 경우
new
키워드를 사용하여 인스턴스를 생성하는데 Kotlin은 그냥 생성할 수 있습니다.
기존에 Java를 사용하는 입장에서 Kotlin이 상당히 매력적인 언어란 것을 다시금 느낄 수 있었습니다.
이번 파트를 학습하면서 함수 표현식의 지원, final
의 아쉬움을 채워주는 val
키워드, if
문이 아닌 식으로의 동작, null
반환 여부에 대한 명시적인 표현, 똑똑한 loop 지원 등을 배웠습니다. 이 내용들만으로도 Kotlin이 충분히 매력적인 언어임을 느낄 수 있었습니다.
제가 Kotlin Koans를 하면서 관련된 내용으로 Kotlin Reference를 학습하다 보니 Reference 순서대로 학습 내용을 올리지 못하는 점 양해 부탁드립니다 😢
Kotlin Reference - Basics / Basic Syntax