문자열

문자열 (Strings)

String 타입은 문자들의 시퀀스를 나타냅니다. 단어, 문장, 메시지, 구조화된 텍스트 같은 텍스트 값에 사용할 수 있죠.

String 타입은 **불변(immutable)**입니다. String 객체를 만들고 나면 그 내용은 객체의 수명 내내 그대로 유지돼요. 겉보기에 문자열을 수정하는 것처럼 보이는 연산은 모두 실제로는 새 문자열을 만듭니다.

출처: Kotlin 공식 문서

본문

문자열 선언하기

String 리터럴을 선언하려면 값을 큰따옴표("")로 감쌉니다. String 타입을 명시적으로 지정하거나, Kotlin이 값으로부터 추론하게 할 수 있어요.

val name: String = "Kotlin"
val message = "Hello, world!" // Kotlin이 String으로 추론

큰따옴표 문자열 리터럴은 \n이나 \t 같은 이스케이프 시퀀스를 지원합니다.

val message = "Hello,\nworld!"
val quote = "Kotlin says, \"Hi\"."

여러 줄 문자열

여러 줄로 이루어졌거나, 이스케이프하고 싶지 않은 따옴표를 포함하는 텍스트를 저장하려면 삼중 따옴표(""" """)로 감싼 여러 줄 문자열(multiline string)을 사용해요.

val text = """
Hello,
Kotlin
"""

val quote = """Kotlin says, "Hi"."""

여러 줄 문자열은 이스케이프 시퀀스를 지원하지 않아요. Kotlin은 이 문자들을 일반 텍스트로 취급합니다.

여러 줄 문자열은 소스 코드에 적힌 그대로 줄바꿈과 들여쓰기를 보존합니다. 런타임 값이 파일의 텍스트 배치와 일치하길 원할 때 유용한 동작이죠. 다음 예시에서 각 줄 앞의 공백은 결과 문자열의 일부입니다.

val text = """
    Hello,
    Kotlin
"""

공통 선행 들여쓰기를 제거하려면 trimIndent() 함수를 사용해요. 이 함수는 비어 있지 않은 줄들의 공통 최소 들여쓰기를 감지해 제거합니다.

fun main() {
//sampleStart
    val text = """
        Hello,
        Kotlin
    """.trimIndent()

    println(text)
//sampleEnd
}

들여쓰기 제거를 더 명시적으로 제어하려면 trimMargin() 함수를 씁니다. 이 함수는 각 줄의 여백(prefix)과 그 앞의 모든 것을 제거해요.

fun main() {
//sampleStart
    val text = """
        |Hello,
        |Kotlin
    """.trimMargin()

    println(text)
//sampleEnd
}

기본적으로 trimMargin() 함수는 여백 접두사로 파이프 기호(|)를 사용하지만, 파라미터로 다른 문자를 넘길 수도 있어요. 예를 들어 trimMargin(">")처럼요.

trimIndent()trimMargin() 같은 함수로 문자열을 처리하면, 결과 문자열은 플랫폼과 무관하게 줄바꿈(\n) 구분자만 사용해요.

문자열 템플릿

문자열 템플릿(string template)은 String 리터럴 안에 변수와 표현식을 직접 끼워 넣을 수 있게 해줍니다. 이 과정을 보간(interpolation)이라고 해요. 문자열 템플릿은 일반 문자열과 여러 줄 문자열 양쪽에서 쓸 수 있습니다.

문자열 안에 변수를 넣으려면 $ 기호를 사용해요.

fun main() {
//sampleStart
    val name = "Kotlin"
    println("Hello, $name")
    // Hello, Kotlin
//sampleEnd
}

문자열 안에 표현식을 넣거나, 변수를 다른 텍스트 바로 옆에 배치하려면 ${}를 씁니다.

fun main() {
//sampleStart
    val text = "abc"
    println("The length of $text is ${text.length}")
    // The length of abc is 3

    val language = "Kotlin"
    println("${language}Lang")
    // KotlinLang
//sampleEnd
}

문자열은 + 연산자로도 결합할 수 있어요. 하지만 문자열 템플릿이 보통 더 읽기 쉽고 관용적입니다.

템플릿 표현식은 이스케이프 없이 큰따옴표 문자열도 포함할 수 있어요.

// 큰따옴표 문자열
val test = "${\"test\".uppercase()}"

// 여러 줄 문자열
val result = """
Result: ${"OK".lowercase()}
"""

문자열 템플릿의 널 가능 값

보간된 표현식이나 변수가 null로 평가되면, Kotlin 컴파일러는 결과 문자열에 null이라는 텍스트를 넣습니다. null을 다른 값으로 바꾸려면 엘비스 연산자(?:)를 사용해요.

fun main(){
//sampleStart
    val text: String? = null

    println("Hello, $text")
    // Hello, null

    println("Hello, ${text ?: "Kotlin"}")
    // Hello, Kotlin
//sampleEnd
}

다중 달러 문자열 보간

일반 문자열 템플릿에서는 $ 하나가 보간을 시작합니다. 문자열에 리터럴 달러 기호를 포함해야 한다면, 다중 달러 문자열 보간(multi-dollar string interpolation)을 사용해요.

다중 달러 문자열 보간은 보간을 촉발하는 데 필요한 연속 달러 기호의 개수를 지정할 수 있게 해줍니다. 그보다 적은 달러 기호는 리터럴 문자로 취급되죠. 예를 들어 문자열 리터럴 앞에 $$를 쓰면, 보간은 달러 두 개가 연속으로 나올 때만 시작합니다.

val KClass<*>.jsonSchema : String
    get() = $$"""
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "$id": "https://example.com/product.schema.json",
      "$dynamicAnchor": "meta",
      "title": "$${simpleName ?: qualifiedName ?: "unknown"}",
      "type": "object"
    }
    """

단일 달러 문자열 보간을 쓰고 있다면, 다중 달러 문자열 보간은 코드에 영향을 주지 않아요. 계속 $ 하나를 사용하고, 필요할 때만 다중 달러 기호를 적용하면 됩니다.

기본 문자열 연산

Kotlin은 문자열을 다루는 다양한 연산을 제공합니다. 이 섹션에서는 가장 흔히 쓰이는 연산 몇 가지를 소개할게요.

사용 가능한 모든 함수에 대한 자세한 내용은 API Reference를 참고하세요.

문자열 길이 구하기

문자열에 있는 문자의 개수를 구하려면 length 속성을 사용해요.

fun main (){
//sampleStart
    val language = "Kotlin"
    println(language.length)
    // 6
//sampleEnd
}

문자에 접근하기

인덱싱 연산자([])로 문자열의 개별 문자에 접근할 수 있어요.

fun main (){
//sampleStart
    val language = "Kotlin"

    println(language[0])
    // K
    println(language[5])
    // n
//sampleEnd
}

문자열 인덱스는 0에서 시작해요. 유효 범위 밖의 인덱스에 접근하려 하면 Kotlin이 예외를 던집니다.

문자열의 문자들을 순회할 수도 있어요.

fun main(){
//sampleStart
    for (char in "Kotlin") {
      println(char)
    }
//sampleEnd
}

문자열 일부 추출하기

문자열의 일부를 추출하려면 다음 함수 중 하나를 사용해요.

  • substring() — 원본 텍스트에서 고른 부분을 담은 새 문자열을 반환.
  • subSequence() — 원본 텍스트에서 고른 부분을 담은 CharSequence를 반환.

예를 들어:

fun main() {
//sampleStart
    val text = "Kotlin"
    println(text.substring(1))
    // otlin
    println(text.substring(1, 5))
    // otli
    println(text.subSequence(1, 5))
    // otli
//sampleEnd
}

String 타입은 불변이므로, 이 함수들은 원본 문자열을 수정하지 않아요.

문자열 비교하기

== 연산자로 두 문자열이 같은 내용을 갖는지 확인할 수 있어요.

fun main(){
//sampleStart
    println("kotlin" == "kotlin")
    // true

    println("kotlin" == "Kotlin")
    // false
//sampleEnd
}

문자열을 사전순(문자 단위)으로 비교하려면 compareTo() 함수를 사용해요. 이 함수는 두 문자열을 스캔하며 첫 번째로 다른 문자 쌍을 찾고 다음을 반환합니다.

  • 0 — 문자열이 같을 때.
  • 0보다 작은 값 — 수신자(receiver)가 인자보다 작을 때.
  • 0보다 큰 값 — 수신자가 인자보다 클 때.
fun main() {
//sampleStart
    println("abc".compareTo("abd") < 0)
    // true

    println("abc".compareTo("ABC") > 0)
    // true

    // true를 넘겨 대소문자 차이를 무시한다
    println("abc".compareTo("ABC", ignoreCase = true) == 0)
    // true
//sampleEnd
}

문자열 내용 다루기

문자열의 내용을 바꾸고 싶다면, .trim(), .replace(), .uppercase(), .lowercase() 같은 함수로 수정된 복사본을 만들어요.

fun main() {
//sampleStart
    val text = "  Hello, Kotlin  "

    println(text.trim())
    // Hello, Kotlin

    println(text.replace("Kotlin", "world"))
    //   Hello, world

    println(text.uppercase())
    //   HELLO, KOTLIN

    println(text.lowercase())
    //   hello, kotlin
//sampleEnd
}

contains(), startsWith(), endsWith() 함수로 문자열 내용을 검사할 수도 있어요.

fun main() {
//sampleStart
    val domain = "kotlinlang.org"

    // 문자열이 "." 을 포함하는지 확인한다
    println(domain.contains("."))
    // true

    // 문자열이 "kotlin"으로 시작하는지 확인한다
    println(domain.startsWith("kotlin"))
    // true

    // 문자열이 ".org"로 끝나는지 확인한다
    println(domain.endsWith(".org"))
    // true
//sampleEnd
}

문자열 나누기

split() 함수로 문자열을 구분자 기준으로 나눌 수 있어요.

fun main() {
//sampleStart
    val numbers = "one, two, three"
    println(numbers.split(", "))
    // [one, two, three]
//sampleEnd
}

문자열을 개별 줄로 나누고 싶다면 lines() 함수를 사용해요.

fun main() {
//sampleStart
    val numbers = "one\ntwo\nthree"
    println(numbers.lines())
    // [one, two, three]
//sampleEnd
}

문자열 만들고 포맷하기

Kotlin에서 대부분의 포맷 작업에는 문자열 템플릿을 사용해요.

+ 연산자로 문자열을 연결하면, Kotlin은 연산마다 새 String 객체를 만들어요. 하지만 이 방식은 루프 안이나 많은 조각을 조립할 때는 이롭지 않을 수 있어요. 그런 문제를 피하려면 buildString() 함수나 StringBuilder를 사용하면 됩니다. 이 둘은 모든 조각을 단일 가변 버퍼에 모은 뒤, 끝에 문자열 하나만 만들어 내죠.

추가할 내용을 결정하는 로직이 복잡할 때, buildString() 함수를 사용해요. 예를 들어 각기 다른 조각을 기여하는 여러 조건이 있을 때처럼요. buildString()을 쓰면 버퍼를 직접 다룰 필요가 없어요. 함수가 내부적으로 StringBuilder를 만들고, 여러분의 블록을 실행한 뒤, 결과 문자열을 반환합니다.

fun main() {
//sampleStart

    val hasErrors = true
    val hasWarnings = true
    val isComplete = false

    // buildString은 빈 버퍼를 만든다
    val status = buildString {
        // "Errors found"를 버퍼에 추가한다
        if (hasErrors) append("Errors found")
        if (hasWarnings) {
            // 버퍼가 비어 있지 않으면 "; " 을 추가한다
            if (isNotEmpty()) append("; ")
            // "Warnings found"를 추가한다
            append("Warnings found")
        }
        // isComplete = false, 추가할 것 없음
        if (isComplete) {
            if (isNotEmpty()) append("; ")
            append("Completed")
        }
        // 버퍼가 비어 있지 않으면 폴백을 건너뛴다
        if (isEmpty()) append("OK")
    }

    println(status)
    // Errors found; Warnings found
//sampleEnd
}

버퍼를 명시적인 값으로 다뤄야 한다면 StringBuilder를 사용해요. 예를 들어 기존 텍스트를 바꿀 때처럼요.

fun main() {
//sampleStart
    val text = "Hello, Kotlin"
    val builder = StringBuilder(text)

    builder.replace(7, 13, "world")
    println(builder.toString())
    // Hello, world
//sampleEnd
}

JVM에서는 String.format() 함수로 문자열을 포맷할 수도 있어요.

val text = String.format("Hello, %s", "Kotlin")

String.format() 함수는 JVM에서 포매터 스타일 지정자(formatter-style specifier)가 명확히 필요할 때만 사용하세요. 포맷 지정자에 대한 자세한 내용은 Java Class Formatter documentation를 참고하세요.

문자열 변환

숫자, Boolean 값, 입력에서 온 식별자처럼 다른 타입의 값을 나타내는 데 문자열을 쓰는 경우가 많아요. Kotlin은 값을 문자열로 변환하는 함수와 문자열을 다른 타입으로 파싱하는 함수를 모두 제공합니다.

값의 문자열 표현을 얻으려면 toString() 함수를 사용해요.

val number = 10
val text = number.toString()

문자열 템플릿과 문자열 연결에서는 Kotlin이 값을 자동으로 문자열로 변환해요.

문자열을 다른 타입으로 변환하려면 해당 파싱 함수를 사용합니다.

문자열 형식이 유효하면 이 함수들은 요청한 타입의 값을 반환해요. 입력이 유효하지 않을 수 있다면 OrNull 변형을 사용하세요. 이 함수들은 예외를 던지는 대신 null을 반환해서, 사용자 입력이나 완전히 통제하지 못하는 데이터에 안전합니다.

val toInt = "10".toInt() // 10

// 1000000000000은 Int의 최댓값을 초과한다
val toIntInvalid = "1000000000000".toIntOrNull()

val toBoolean = "true".toBooleanStrict() // true
val toBooleanInvalid = "yes".toBooleanStrictOrNull() // null

더 알아보기 (Learn more)