Kotlin 기본 문법 훑어보기

Kotlin 기본 문법 훑어보기

Kotlin의 기본 문법 요소들을 예시와 함께 모아둔 문서예요. 각 섹션 끝에는 관련 내용을 자세히 설명한 문서 링크도 함께 달려 있으니, 궁금한 부분이 생기면 그 링크를 따라가면 돼요.

Kotlin의 핵심을 처음부터 끝까지 배우고 싶다면, JetBrains Academy에서 제공하는 무료 Kotlin Core 트랙을 활용해 보세요.

출처: Kotlin 공식 문서

본문

패키지 정의와 임포트

패키지 선언은 소스 파일의 맨 위에 와야 해요.

package my.demo

import kotlin.text.*

// ...

디렉터리 구조와 패키지 이름이 반드시 일치해야 하는 건 아니에요. 소스 파일은 파일 시스템 어디에 놓아도 상관없습니다. 자세한 내용은 Packages 문서를 참고하세요.

프로그램 진입점

Kotlin 애플리케이션의 진입점은 main 함수예요.

fun main() {
    println("Hello world!")
}

main 함수의 또 다른 형태는 String 인자를 가변 개수로 받을 수 있어요.

fun main(args: Array<String>) {
    println(args.contentToString())
}

표준 출력에 출력하기

print는 인자를 표준 출력에 출력해요.

fun main() {
//sampleStart
    print("Hello ")
    print("world!")
//sampleEnd
}

println은 인자를 출력하고 줄바꿈을 추가해서, 다음에 출력하는 내용이 다음 줄에 나타나게 해요.

fun main() {
//sampleStart
    println("Hello world!")
    println(42)
//sampleEnd
}

표준 입력에서 읽기

readln() 함수는 표준 입력에서 읽어요. 이 함수는 사용자가 입력한 전체 줄을 문자열로 읽습니다.

println(), readln(), print() 함수를 함께 사용하면 입력을 요청하고 보여주는 메시지를 출력할 수 있어요.

// Prints a message to request input
println("Enter any word: ")

// Reads and stores the user input. For example: Happiness
val yourWord = readln()

// Prints a message with the input
print("You entered the word: ")
print(yourWord)
// You entered the word: Happiness

더 자세한 내용은 Read standard input 문서를 참고하세요.

함수

두 개의 Int 파라미터를 받고 Int를 반환하는 함수예요.

//sampleStart
fun sum(a: Int, b: Int): Int {
    return a + b
}
//sampleEnd

fun main() {
    print("sum of 3 and 5 is ")
    println(sum(3, 5))
}

함수 본문은 표현식이 될 수도 있어요. 이 경우 반환 타입은 추론됩니다.

//sampleStart
fun sum(a: Int, b: Int) = a + b
//sampleEnd

fun main() {
    println("sum of 19 and 23 is ${sum(19, 23)}")
}

의미 있는 값을 반환하지 않는 함수도 있어요.

//sampleStart
fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}
//sampleEnd

fun main() {
    printSum(-1, 8)
}

Unit 반환 타입은 생략할 수 있어요.

//sampleStart
fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}
//sampleEnd

fun main() {
    printSum(-1, 8)
}

함수에 대한 자세한 내용은 Functions 문서를 참고하세요.

변수

Kotlin에서 변수는 키워드 val 또는 var로 시작하고, 그 뒤에 변수 이름을 붙여서 선언해요.

val 키워드는 값이 한 번만 할당되는 변수를 선언할 때 사용해요. 이런 변수는 불변(immutable)이고 읽기 전용인 지역 변수라서, 초기화된 이후에는 다른 값으로 재할당할 수 없습니다.

fun main() {
//sampleStart
    // Declares the variable x and initializes it with the value of 5
    val x: Int = 5
    // 5
//sampleEnd
    println(x)
}

var 키워드는 재할당할 수 있는 변수를 선언할 때 사용해요. 이런 변수는 가변(mutable) 변수라서 초기화 이후에 값을 바꿀 수 있어요.

fun main() {
//sampleStart
    // Declares the variable x and initializes it with the value of 5
    var x: Int = 5
    // Reassigns a new value of 6 to the variable x
    x += 1
    // 6
//sampleEnd
    println(x)
}

Kotlin은 타입 추론(type inference)을 지원해서, 선언한 변수의 데이터 타입을 자동으로 알아내요. 변수를 선언할 때 변수 이름 뒤의 타입을 생략할 수 있습니다.

fun main() {
//sampleStart
    // Declares the variable x with the value of 5;`Int` type is inferred
    val x = 5
    // 5
//sampleEnd
    println(x)
}

변수는 초기화한 뒤에만 사용할 수 있어요. 선언하는 순간에 초기화해도 되고, 먼저 선언만 해두고 나중에 초기화하는 방법도 있어요. 두 번째 방법을 쓸 때는 데이터 타입을 반드시 지정해야 합니다.

fun main() {
//sampleStart
    // Initializes the variable x at the moment of declaration; type is not required
    val x = 5
    // Declares the variable c without initialization; type is required
    val c: Int
    // Initializes the variable c after declaration 
    c = 3
    // 5 
    // 3
//sampleEnd
    println(x)
    println(c)
}

변수는 최상위 레벨(top level)에서도 선언할 수 있어요.

//sampleStart
val PI = 3.14
var x = 0

fun incrementX() {
    x += 1
}
// x = 0; PI = 3.14
// incrementX()
// x = 1; PI = 3.14
//sampleEnd

fun main() {
    println("x = $x; PI = $PI")
    incrementX()
    println("incrementX()")
    println("x = $x; PI = $PI")
}

프로퍼티 선언에 대한 정보는 Properties 문서를 참고하세요.

클래스와 인스턴스 만들기

클래스를 정의하려면 class 키워드를 사용해요.

class Shape

클래스의 프로퍼티는 선언부나 본문에 나열할 수 있어요.

class Rectangle(val height: Double, val length: Double) {
    val perimeter = (height + length) * 2 
}

클래스 선언부에 나열된 파라미터를 가진 기본 생성자는 자동으로 사용할 수 있어요.

class Rectangle(val height: Double, val length: Double) {
    val perimeter = (height + length) * 2 
}
fun main() {
    val rectangle = Rectangle(5.0, 2.0)
    println("The perimeter is ${rectangle.perimeter}")
}

클래스 사이의 상속은 콜론(:)으로 선언해요. 클래스는 기본적으로 final이므로, 상속할 수 있게 만들려면 open으로 표시해야 합니다.

open class Shape

class Rectangle(val height: Double, val length: Double): Shape() {
    val perimeter = (height + length) * 2 
}

생성자와 상속에 대한 자세한 내용은 Classes 문서와 Objects and instances 문서를 참고하세요.

주석

대부분의 현대 언어처럼 Kotlin도 한 줄(줄 끝) 주석과 여러 줄(블록) 주석을 지원해요.

// This is an end-of-line comment

/* This is a block comment
   on multiple lines. */

Kotlin의 블록 주석은 중첩될 수 있어요.

/* The comment starts here
/* contains a nested comment */  
and ends here. */

문서화 주석 문법에 대한 정보는 Documenting Kotlin Code 문서를 참고하세요.

문자열 템플릿

fun main() {
//sampleStart
    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"
//sampleEnd
    println(s2)
}

자세한 내용은 String templates 문서를 참고하세요.

조건 표현식

//sampleStart
fun maxOf(a: Int, b: Int): Int {
    if (a > b) {
        return a
    } else {
        return b
    }
}
//sampleEnd

fun main() {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}

Kotlin에서는 if를 표현식으로도 사용할 수 있어요.

//sampleStart
fun maxOf(a: Int, b: Int) = if (a > b) a else b
//sampleEnd

fun main() {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}

자세한 내용은 if-expressions 문서를 참고하세요.

for 루프

fun main() {
//sampleStart
    val items = listOf("apple", "banana", "kiwifruit")
    for (item in items) {
        println(item)
    }
//sampleEnd
}

또는 이렇게도 쓸 수 있어요.

fun main() {
//sampleStart
    val items = listOf("apple", "banana", "kiwifruit")
    for (index in items.indices) {
        println("item at $index is ${items[index]}")
    }
//sampleEnd
}

자세한 내용은 for loop 문서를 참고하세요.

while 루프

fun main() {
//sampleStart
    val items = listOf("apple", "banana", "kiwifruit")
    var index = 0
    while (index < items.size) {
        println("item at $index is ${items[index]}")
        index++
    }
//sampleEnd
}

자세한 내용은 while loop 문서를 참고하세요.

when 표현식

//sampleStart
fun describe(obj: Any): String =
    when (obj) {
        1          -> "One"
        "Hello"    -> "Greeting"
        is Long    -> "Long"
        !is String -> "Not a string"
        else       -> "Unknown"
    }
//sampleEnd

fun main() {
    println(describe(1))
    println(describe("Hello"))
    println(describe(1000L))
    println(describe(2))
    println(describe("other"))
}

자세한 내용은 when expressions and statements 문서를 참고하세요.

범위(Ranges)

in 연산자를 사용해 숫자가 범위 안에 있는지 확인할 수 있어요.

fun main() {
//sampleStart
    val x = 10
    val y = 9
    if (x in 1..y+1) {
        println("fits in range")
    }
//sampleEnd
}

숫자가 범위 밖에 있는지 확인하려면 이렇게 해요.

fun main() {
//sampleStart
    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")
    }
//sampleEnd
}

범위를 순회할 수도 있어요.

fun main() {
//sampleStart
    for (x in 1..5) {
        print(x)
    }
//sampleEnd
}

진행(progression)을 순회할 수도 있어요.

fun main() {
//sampleStart
    for (x in 1..10 step 2) {
        print(x)
    }
    println()
    for (x in 9 downTo 0 step 3) {
        print(x)
    }
//sampleEnd
}

자세한 내용은 Ranges and progressions 문서를 참고하세요.

컬렉션

컬렉션을 순회해 봐요.

fun main() {
    val items = listOf("apple", "banana", "kiwifruit")
//sampleStart
    for (item in items) {
        println(item)
    }
//sampleEnd
}

in 연산자를 사용해 컬렉션에 객체가 들어 있는지 확인할 수 있어요.

fun main() {
    val items = setOf("apple", "banana", "kiwifruit")
//sampleStart
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")
    }
//sampleEnd
}

람다 표현식을 사용해 컬렉션을 필터링하고 매핑할 수 있어요.

fun main() {
//sampleStart
    val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
    fruits
      .filter { it.startsWith("a") }
      .sortedBy { it }
      .map { it.uppercase() }
      .forEach { println(it) }
//sampleEnd
}

자세한 내용은 Collections overview 문서를 참고하세요.

Nullable 값과 null 검사

null 값이 가능한 참조는 명시적으로 nullable로 표시해야 해요. nullable 타입 이름에는 끝에 ?가 붙습니다. 예를 들어 Int?처럼요.

str이 정수를 담고 있지 않으면 null을 반환해 봐요.

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

nullable 값을 반환하는 함수를 사용해 봐요.

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

//sampleStart
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")
    }    
}
//sampleEnd

fun main() {
    printProduct("6", "7")
    printProduct("a", "7")
    printProduct("a", "b")
}

또는 이렇게도 쓸 수 있어요.

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)
    
//sampleStart
    // ...
    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)
//sampleEnd
}

fun main() {
    printProduct("6", "7")
    printProduct("a", "7")
    printProduct("99", "b")
}

자세한 내용은 Null-safety 문서를 참고하세요.

타입 검사와 자동 캐스트

is 연산자는 표현식이 특정 타입의 인스턴스인지 검사해요. 불변인 지역 변수나 프로퍼티가 특정 타입인지 검사했다면, 명시적으로 캐스트할 필요가 없습니다.

//sampleStart
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
}
//sampleEnd

fun main() {
    fun printLength(obj: Any) {
        println("Getting the length of '$obj'. Result: ${getStringLength(obj) ?: "Error: The object is not a string"} ")
    }
    printLength("Incomprehensibilities")
    printLength(1000)
    printLength(listOf(Any()))
}

또는 이렇게 쓸 수도 있어요.

//sampleStart
fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null

    // `obj` is automatically cast to `String` in this branch
    return obj.length
}
//sampleEnd

fun main() {
    fun printLength(obj: Any) {
        println("Getting the length of '$obj'. Result: ${getStringLength(obj) ?: "Error: The object is not a string"} ")
    }
    printLength("Incomprehensibilities")
    printLength(1000)
    printLength(listOf(Any()))
}

이렇게도 쓸 수 있어요.

//sampleStart
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
}
//sampleEnd

fun main() {
    fun printLength(obj: Any) {
        println("Getting the length of '$obj'. Result: ${getStringLength(obj) ?: "Error: The object is not a string"} ")
    }
    printLength("Incomprehensibilities")
    printLength("")
    printLength(1000)
}

자세한 내용은 Classes 문서와 Type casts 문서를 참고하세요.

더 알아보기 (Learn more)