Kotlin 2.4 컬렉션 리터럴 써봤습니다 — listOf 대신 대괄호로 끝낸 자리

얼마 전에 테스트 픽스처를 짜는데 listOf가 화면을 가득 도배하더라고요. 파이썬이면 대괄호 하나로 끝날 걸 말이죠. 근데 코틀린(Kotlin) 2.4에서 드디어 그게 됩니다.
코틀린 컬렉션 리터럴, 대괄호로 리스트 만들기
문법은 진짜 단순해요. 그냥 대괄호로 감싸면 끝입니다.
val list = ["apple", "banana"] // List<String>
val set: Set<Int> = [1, 2, 3]
listOf, setOf, mapOf가 반복되던 자리가 깔끔해집니다. 특히 테스트 코드에서 픽스처 데이터를 한가득 늘어놓을 때, 같은 함수 이름이 수십 번 찍히는 그 피로감이 확 줄어들더라고요.
다만 이건 2.4의 실험적 기능이라 그냥은 안 됩니다. 컴파일러 옵션에 -Xcollection-literals 플래그를 켜줘야 동작해요. 이 한 줄을 안 넣으면 대괄호가 그냥 컴파일 에러로 튕겨버립니다.

listOf 대신 대괄호가 동작하는 원리
이게 무슨 마법은 아니고요, 속을 까보면 신택틱 슈가(syntactic sugar)입니다. 쉽게 말하면 보기 좋으라고 입혀둔 설탕 같은 표기법이에요.
[1, 2, 3]은 컴파일 단계에서 List.of(1, 2, 3)으로 바뀝니다. 집합이면 Set.of(...)로 바뀌고요. 즉 동반 객체(companion object)에 들어있는 operator fun of 함수를 호출하는 셈입니다.
재밌는 건 이게 제가 만든 클래스에도 열려 있다는 점이에요. 직접 정의한 타입의 동반 객체에 operator fun of를 구현해두면, 그 클래스도 대괄호 문법으로 인스턴스를 만들 수 있게 됩니다. 내부 DSL 짤 때 꽤 쓸모 있어 보이네요.

대괄호 컬렉션 리터럴의 함정, 불변과 타입 추론
여기서부터가 진짜 조심할 자리입니다. 가장 크게 발 헛디디는 곳이 불변성이거든요.
타입을 안 적으면 컴파일러는 무조건 읽기 전용 List로 추론합니다. 그러니까 val numbers = [1, 2, 3]은 List<Int>고, 여기다 numbers.add(4)를 적으면 아예 컴파일이 안 됩니다. 코틀린의 List 타입엔 add 자체가 없어서 Unresolved reference: add로 빨간 줄이 그어지거든요. 자바의 List.of였다면 실행 중에 예외로 터졌을 자리인데, 코틀린은 컴파일 단계에서 미리 막아주는 셈이라 오히려 안전해요.
가변 리스트나 집합, 배열이 필요하면 타입을 반드시 명시해야 합니다.
val mutable: MutableList<String> = ["a", "b"]
val mutableSet: MutableSet<Int> = [1, 2, 3]
val arr: IntArray = [1, 2, 3]
근데 이렇게 되면 살짝 김이 빠지는데요. val numbers: Set<Int> = [1, 2, 3]보다 그냥 setOf(1, 2, 3)이 더 짧고 의도도 분명하잖아요. 타입을 길게 적어야 하는 순간엔 대괄호의 이득이 거의 사라지는 것 같습니다.
추가로 두 가지만 더 짚어둘게요.
- 자바(Java)에 정의된 컬렉션 타입에는 못 씁니다. 코틀린 동반 객체 기반이라 자바 쪽 타입은 대괄호를 받아주지 않아요.
array[i]같은 인덱스 접근과 생긴 게 겹쳐서, 코드 리뷰할 때 "이게 원소 접근이야 생성이야?" 하고 한 번 멈칫하게 될 수 있습니다.

실험 기능 도입 기준, 테스트 코드부터
제가 보기엔 들어갈 자리랑 아닌 자리가 꽤 분명해요.
테스트 코드나 내부에서만 쓰는 작은 도구처럼 언제든 고치기 쉬운 영역이라면, 저는 지금 바로 써도 괜찮다고 봅니다. 읽기 전용 List 비중이 높은 데이터 묶음일수록 대괄호 한 방으로 가독성이 확 좋아지니까요.
반대로 프로덕션 코드는 좀 기다리는 게 안전해 보여요. 실험적 기능은 정식 버전으로 가면서 문법이나 동작이 바뀔 수 있어서, 그때 가서 코드를 싹 다시 손봐야 하는 위험을 안고 가는 셈이지요. 가변 컬렉션 비중이 높은 코드라면 어차피 타입을 다 명시해야 해서 얻는 것도 적습니다.
개인적으로는 다음 테스트 모듈부터 -Xcollection-literals를 켜고 픽스처에만 살살 발을 담가볼 생각이에요. 깨져도 운영에 영향 없는 데서 익숙해진 다음에 판단하려고요.
결국 이 기능은 "대괄호로 만든 기본 리스트는 불변"이라는 한 줄로 정리됩니다. 이거 하나만 머리에 박아두면 add() 한 번에 빌드가 막히는 일은 미리 알고 피해갈 수 있어요.
