Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.
코틀린 문자열은 자바 문자열과 같다.
모든 코틀린 문자열을 자바 메서드에 넘겨도, 자바 문자열을 코틀린 메서드에 넘겨도 괜찮다.
특별한 변환도 필요없고 별도의 `Wrapper`(래퍼)도 필요 없다.
코틀린의 다양한 확장 함수 덕분에 표준 자바 문자열을 더 쉽게 다룰 수 있다.
문자열 나누기
코틀린에서의 문자열을 구분 문자열에 따라 나누는 작업을 알아보자
먼저 자바의 `split` 메서드를 간단히 사용해보자.
public void splitString() {
String str = "12.345-6.A";
String[] strings = str.split(".");
for (String string : strings) {
System.out.println(string); // 아무것도 출력되지 않음
}
}
코드의 의도는 문자열을 점(`.`) 으로 분리하는 것이다.
그런데 의도대로 동작하지 않는다.
실제로는 아무것도 출력되지 않는다. `strings` 는 빈 문자열이 된다.
`split` 의 구분 문자열은 실제로는 정규식(regular expression) 이기 때문이다.
즉, 마침표(`.`) 는 모든 문자를 나타내는 정규식으로 해석된다.
원래의 의도대로 라면 `.` 이 아닌 `\\.` 으로 수정해야 한다.
코틀린에서는 자바의 `split` 대신 여러 다른 조합의 파라미터를 받는 `split` 확장 함수를 제공하여 혼동을 야기하는 메서드를 감춰준다.
`split` 함수에 전달하는 값의 타입에 따라 정규식이나 일반 텍스트 중 어느 것으로 문자열을 분리하는지 쉽게 알 수 있다.
인텔리제이의 자동완성 기능을 보면 `Regex` 와 `Char`, `String` 등으로 패러미터를 받을 수 있는 것을 확인할 수 있다.
따라서 코틀린에서는 `split` 함수에 전달하는 값의 타입에 따라 정규식이나 일반 텍스트 중 어느 것으로 문자열을 분리하는지 쉽게 알 수 있다.
아래 코드는 마침표(`.`)나 대시(`-`) 로 문자열을 분리하는 예이다.
fun splitString(string: String) = println(string.split("[.\\-]".toRegex()))
@Test
fun splitStringTest() = splitString("12.345-6.A")
결과
[12.345, 6.A]
당연히 코틀린 정규식 문법은 자바와 같다.
정규식을 처리하는 API 는 조금 더 코틀린답게 변경되었다.
예를 들어 `toRegex()` 라는 확장 함수를 통해 정규식을 명시적으로 변환한다.
이러한 간단한 경우에는 꼭 정규식을 쓸 필요가 없다.
정규식은 꽤나 무거운 객체이다. 정규식이 아닌 여러 구분 문자열을 사용하여 문자열을 분리해보자.
fun splitString2(string: String) = println(string.split(".", "-"))
@Test
fun splitString2Test() = splitString2("12.345-6.A")
결과
[12.345, 6.A]
`split` 확장 함수를 오버로딩한 버전 중에서는 구분 문자열을 하나 이상 인자로 받는 함수가 있다.
혹은 `string.split('.', '-')` 처럼 `String` 대신 `Char` 을 인자로 넘겨도 마찬가지 결과를 볼 수 있다.
이렇게 여러 문자를 받을 수 있는 코틀린 확장 함수는 자바에 있는 단 하나의 문자만 받을 수 있는 메서드를 대신한다.
정규식 & 3중 따옴표로 묶은 문자열
이번에는 다른 예를 들어보자.
어떤 파일의 전체 경로명을 디렉토리, 파일 이름, 확장자로 구분해 볼 것이다.
코틀린 표준 라이브러리에는 어떤 문자열에서 구분 문자열이 맨 나중(OR 처음)에 나타난 곳 뒤(OR 앞)의 부분 문자열을 리턴하는 `String `확장 함수가 있다.
1. `String` 확장 함수를 사용하여 구현하기
fun parsePath1(path: String) {
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")
}
코틀린에서는 정규식을 사용하지 않고도 문자열을 쉽게 파싱할 수 있다.
정규식은 강력하지만, 나중에 알아보기 힘든 경우가 있으며, 무거운 객체이다.
정규식이 필요할 때는 코틀린 라이브러리를 사용하면 더 편하다.
결과는 위와 아래 방법 모두 같다.
Dir: /Users/sh1mj1/kotlin-book, name: chapter, ext: adoc
2. 정규식 사용하기
fun parsePath2(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, fileName, extension) = matchResult.destructured
println("Dir: $directory, name: $fileName, ext: $extension")
}
}
이 코드에서는 3중 따옴표 문자열을 사용하여 정규식을 썼다.
3중 따옴표 문자열에서는 `\` 를 포함한 어떤 문자열도 escape 할 필요가 없다.
일반 문자열에서 마침표 기호를 escape 하려면 `\\.` 라고 써야 하지만, 3중 따옴표 문자열에서는 `\.` 라고 쓰면 된다.
이 예제에서 쓴 정규식은 슬래시와 마침표를 기준으로 `path` 를 세 그룹으로 분리한다.
첫 그룹인 `(.+)` 는 마지막 슬래시 이전 부분 문자열이다.
두 번째 그룹은 마지막 마침표 이전이면서 마지막 슬래시 이후인 부분 문자열이다.
세 번째 그룹은 나머지 모든 문자가 들어간다.
정규식 엔진은 각 패턴을 가능한 가장 긴 부분 문자열과 매치하려고 한다.
그래서 `"path/to/dir/filename.ext"` 에서는 (.+)/ 와 일치하는 패턴을 찾으면 `"path"` 가 아닌 `"path/to/dir"` 라는 부분 문자열이 된다.
이와 관련해 정규식 문서에서 greedy 나 lazy 를 찾아보면 좋다.
그렇게 정규식으로 `MathResult` 타입의 `mathResult` 를 생성한다.
이것이 성공하면 그룹별로 분해한 매치 결과를 의미하는 `destructured` 프로퍼티를 각 변수에 대입한다.
구조 분해 선언을 사용한 것이다.
이에 대해 자세한 내용은 나중에 다시 다루겠다.
public interface MatchResult { public open val destructured: kotlin.text.MatchResult.Destructured /* compiled code */ // ... }
`MathResult` 인터페이스와 그 프로퍼티 `destructured`
여러 줄 3중 따옴표 문자열
3중 따옴표 문자열은 줄 바꿈을 나타내는 아무 문자열이나 그대로 들어간다.
즉, 줄바꿈이 있는 텍스트를 쉽게 만들 수 있다.
여러 줄 문자열에는 들여쓰기나 줄 바꿈을 포함한 모든 문자가 들어간다.
fun multilineString() {
val kotlinLogo = """| //
.| //
.|/ \"""
println(kotlinLogo.trimMargin("."))
}
결과
multiline String 을 코드에서 더 보기 좋게 표현하고 싶다면 들여쓰기를 하되 들여쓰기의 끝부분을 특별한 문자열로 표시하고 `trimMargin` 을 사용해서 그 문자열과 그 직전의 공백을 제거한다.
위 예제 코드에서는 `.` 를 구분 문자열로 사용했다.
참고로 `trimMargin` 의 구분문자열 기본값은 `|` 이다.
multiline String 에는 줄 바꿈이 들어가지만 줄 바꿈을 `\n` 특수문자를 사용할 수는 없다.
반면, `\` 를 문자열에 넣고 싶으면 따로 escape 할 필요도 없다.
3중 따옴표 문자열 안에 String Template(문자열 템플릿)을 사용할 수도 있다.
만약 `$` 라는 문자를 문자열 안에 넣어야 한다면 escape 할 수 없기 때문에 `${'$'}` 처럼 문자열 템플릿 안에 '$' 문자를 넣어야 한다.
3중 따옴표 사이에 HTML 도 넣을 수 있다.
코틀린 코딩 컨벤션에서의 3중 따옴표 문자열
코틀린 코딩 컨벤션 문서에서 String convention 에 대한 내용이 있다.
- 문자열 연결보다 String Template 을 권고한다.
- 일반 문자열 리터럴에 escape 시퀀스를 삽입하는 것보다 multiline String 을 권고한다.
- multline String 에서 들여쓰기를 유지하려면 결과 문자열에 내부 들여쓰기가 필요하지 않은 경우에는 TrimIndent를 사용하고,
내부 들여쓰기가 필요한 경우에는 TrimMargin을 사용하라.
println("""
Not
trimmed
text
"""
)
println("""
Trimmed
text
""".trimIndent()
)
println()
val a = """Trimmed to margin text:
|if(a > 1) {
| return a
|}""".trimMargin()
println(a)
// print
/*
Not
trimmed
text
Trimmed
text
Trimmed to margin text:
if(a > 1) {
return a
}
*/
자바의 multiline String 사용법 차이
Java 15 이전에는 여러 줄 문자열을 만드는 방법 중 예로 `String` 클래스의 `join()` 함수를 사용할 수 있다.
String lineSeparator = System.getProperty("line.separator");
String result = String.join(lineSeparator,
"Kotlin",
"Java");
System.out.println(result);
Java 15에서는 텍스트 블록이 생겼다.
주의할 점은 multiline String 을 출력하고 삼중 따옴표가 다음 줄에 있으면 추가 빈 줄까지 출력된다.
String result = """
Kotlin
Java
""";
System.out.println(result);
참조
https://kotlinlang.org/docs/coding-conventions.html#strings
https://kotlinlang.org/docs/java-to-kotlin-idioms-strings.html#use-multiline-strings
'Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린 인터페이스 & 상속 제어 변경자 (2) | 2024.01.04 |
---|---|
[Kotlin] 로컬 함수 & 확장 (0) | 2024.01.04 |
[Kotlin] 중위 호출 & 구조 분해 선언 (1) | 2024.01.03 |
[Kotlin] 컬렉션 처리 API 확장 & 가변 인자(vararg) 함수 (1) | 2024.01.03 |
[Kotlin] 확장 함수 & 확장 프로퍼티 (0) | 2024.01.01 |