코틀린 1.7.0 버전이 공개되었습니다. 대략적인 변화는 다음과 같습니다.
- 코틀린 K2 컴파일러의 알파 버전이 공개되었습니다.
- 코틀린 컴파일러의 incremental compilation(증분 컴파일) 기능이 개선되었습니다. 이제 non-Kotlin 모듈도 지원합니다.
- opt-in 어노테이션, definitely non-nullable 타입, builder 추론 기능이 이제 Stable 단계입니다.
- 언더스코어 연산자(_)를 사용하여 타입 매개변수를 자동으로 추론할 수 있습니다.
- 이제 인라인 클래스에 코틀린 delegation 을 활용할 수 있습니다.
- 코틀린 standard library 에 새로운 함수들이 추가되었습니다.
코틀린 K2 컴파일러 Alpha 버전
이번 코틀린 1.7.0 버전에서 코틀린 K2 컴파일러의 알파 버전이 공개되었습니다. K2 컴파일러는 성능 개선은 물론이고, 새로운 기능을 더 빠르게 개발하고 Kotlin이 지원하는 모든 플랫폼 통합을 위한 API를 제공하는 데 목표를 두고 있습니다.
K2 컴파일러의 이번 알파 버전은 퍼포먼스 개선에 특히 집중했습니다.
K2 컴파일러를 사용하려면 프로젝트의 컴파일러 옵션에 아래처럼 -Xuse-k2
인자를 추가해야합니다.
kotlinOptions.freeCompilerArgs += ['-Xuse-k2']
알파 버전은 우선 JVM 프로젝트만 지원합니다. Kotlin/JS, Kotlin/Native, 멀티 플랫폼 프로젝트는 지원하지 않으며 kapt 를 비롯한 컴파일러 플러그인 또한 지원하지 않습니다.
K2 컴파일러에 대한 자세한 내용은 아래 영상을 참고해주세요.
Incremental Compilation(증분 컴파일) 기능 개선
코틀린 1.7.0 버전 부터는 cross-module 증분 컴파일이 가능합니다. 그래서 프로젝트 내부의 non-Kotlin 모듈에 가해진 변화도 증분 컴파일의 영향을 받죠.
증분 컴파일에 대해 간단히 설명하면 코틀린 컴파일을 빠르고 효율적으로 하기 위한 테크닉이라고 할 수 있습니다. 이전에 이미 컴파일 되었고, 다시 컴파일 할 필요가 없는 파일은 이를 생략하는 방식입니다.
코틀린이 다시 컴파일해야 하는 코드의 양을 줄이기 위해 사용하는 방식은 크게 두가지입니다.
- 컴파일 회피 — 영향을 받은 모듈만 다시 컴파일
- 증분 컴파일 — 영향을 받은 파일만 다시 컴파일
모듈은 파일보다 넓은 개념이기 때문에 컴파일 회피가 증분 컴파일보다 그 구현이 간단합니다. 증분 컴파일은 IntelliJ의 내장 빌드 시스템인 JPS에서 지속적으로 지원되고 있지만 Gradle은 기본적으로 컴파일 회피만 지원합니다. 증분 컴파일은 아직 개선의 여지가 많기 때문입니다.
또한 증분 컴파일은 아직 JVM 만 지원하며 안정화를 위해 지속적으로 기능 개선이 이루어지고 있습니다.
언어 기능 개선
인라인 클래스의 delegation 지원
Delegation pattern(위임 패턴)은 구현 상속의 좋은 대체재로 평가 받아왔고, 코틀린은 이 위임 패턴을 보일러 플레이트 코드 없이 사용할 수 있도록 언어단에서 지원합니다.
위임 패턴을 코드로 구현하는 것은 다소 번거로운데 코틀린은 위 처럼 by
키워드 하나면 해결해주죠.
하지만 안타깝게도 인라인 클래스에는 적용할 수 없다는 제약사항이 있었는데요. 1.7.0 버전 부터는 이 제약이 없어지기 때문에 아래처럼 활용할 수 있습니다.
타입 매개변수에 언더스코어(_) 사용
코틀린 1.7.0 버전 부터는 타입 매개변수가 위치해야하는 자리지만 해당 타입을 합리적으로 추론할 수 있다면 언더스코어 연산자(_)로 매개변수를 대체할 수 있습니다. 코드로 살펴봅시다.
위 코드에서 Runner 클래스의 T 타입 매개변수는 SomeClass 클래스 매개변수 덕분에 T 매개변수를 명시적으로 선언할 필요가 없습니다.
하지만 1.7.0 이전 버전에서는 T 타입이 추론 가능함에도 명시적으로 넣어줬어야했습니다.
이 불편함은 1.7.0 버전에서 아래처럼 해결되었습니다.
타입 매개변수가 추론 가능한 경우에는 언더스코어 연산자(_)로 생략할 수 있도록 한거죠. 사실 제 개인적인 생각으로는 언더스코어를 쓰는 것 보다는 완전히 생략가능하도록 만드는 게 더 낫지 않았나 싶긴합니다.
Opt-in 어노테이션
opt-in 어노테이션은 이제 Stable 단계이며 더이상 추가적인 컴파일러 configuration 을 요구하지 않습니다.
1.7.0 이전 버전에서는 opt-in 어노테이션을 사용하려면 컴파일러 옵션에 -opt-in=kotlin.RequiresOptIn
를 추가해야 했습니다. (안그러면 IDE 가 경고를 띄웠습니다.)
1.7.0 버전 부터는 opt-in 어노테이션이 Stable 단계에 접어들어서 위 옵션을 추가할 필요가 없습니다.
Opt-in 이 뭐죠?
코틀린에는 특정 API를 사용하는데 명시적인 동의를 요구하는 경우가 있습니다. 라이브러리 개발자는 사용자에게Opt-in 이 필요한 특정 조건 (예 : API가 실험적 상태이고 향후 변경 될 가능성이있는 경우)에 대해 API에 알릴 수 있고, 라이브러리 사용자는 이에 동의를 하고 사용해야 하는거죠.
이를 위해 처음 나온 어노테이션이 @Experimental
, @UseExperimental
였지만, @RequireOptIn
및 @OptIn
어노테이션이 이를 대체했습니다.
Definitely(명백한) non-nullable 타입
1.6.20 버전에서 공개된 definitely non-nullable 타입이 이제 Stable 단계입니다.
코틀린에서는 제네릭 타입의 non-nullable 을 보장하는 문법이 따로 없었습니다. 이에 대한 불만은 4년전부터 나왔구요.
제네릭 자바 클래스나 인터페이스와의 호환성을 위해, 코틀린 1.6.20 버전 부터는 제네릭 타입을 non-nullable 하게 선언할 수 있습니다.
문법은 간단합니다. T & Any
처럼 사용하면 됩니다.
기존 코틀린에서는 위 printSomething 메소드에 null 을 파라미터로 넣어도 문제없이 컴파일되고 실행됩니다. 그리고 실행 결과 null 이 출력됩니다. 제네릭 타입의 non-null 을 보장할 수가 없었던거죠.
1.7.0 버전 부터는 제네릭 타입을 위 처럼 선언하면 파라미터로 null 을 넣을 수 없고 컴파일되지 않습니다.
Kotlin/JVM
JVM 타겟 버전 변경
Kotlin/JVM 타겟 버전의 default 값이 1.6 에서 1.8 로 변경되었습니다. 현 프로젝트의 JVM 타겟 버전이 1.6 이라면 1.8 이상으로 마이그레이션해야합니다.
컴파일러 성능 개선
코틀린 1.7.0 버전에서 Kotlin/JVM 컴파일러의 성능이 개선되었습니다. 벤치마크 결과, 1.6.0 버전과 비교했을 때 평균적으로 10% 정도의 성능 개선이 이루어졌습니다.
새로운 컴파일러 옵션 : -Xjdk-release
코틀린 1.7.0 버전에 -Xjdk-release
라는 새로운 옵션이 추가되었습니다. javac 의 --release
옵션과 비슷한 기능입니다. -Xjdk-release
옵션으로 타겟 바이트코드 버전과 JDK API 버전을 특정 자바 버전으로 제한할 수 있습니다.
Standard library
분명 구면인 것 같은데 새로운 컬렉션 함수 : min(), max()
코틀린 1.4.0 버전에서 min()
과 max()
컬렉션 함수의 이름을 minOrNull()
, maxOrNull()
로 변경했었습니다. 리시버 컬렉션이 비어있을 경우 null 을 반환하기 때문에 이름에 OrNull 을 붙여주는게 해당 메소드를 더 잘 나타낸다고 생각했기 때문이죠.
코틀린 1.7.0 버전에서 헤어졌던 min()
과 max()
컬렉션 함수를 다시 소개합니다. 이 함수들은 이전과 다르게 non-nullable 타입을 리턴합니다. 그리고 이건 minBy()
, maxBy()
, minWith()
, maxWith()
도 마찬가지입니다.
정규식의 특정 인덱스 검사
코틀린 1.5.30 버전에서 소개된 Regex.matchAt()
, Regex.matchesAt()
함수가 이제 Stable 단계입니다. 이 두 확장함수로 스트링의 특정 인덱스 값이 정규식을 만족하는지 검사할 수 있습니다.
리플렉션으로 어노테이션에 접근
코틀린 1.6.30 버전에서 소개된 KAnnotatedElement.findAnnotations()
확장함수가 이제 Stable 단계입니다. 이 함수는 주어진 타입과 관련한 모든 어노테이션을 반환합니다.
deep recursive function
deep recursive function 은 코틀린 1.4.0 버전에서 실험적으로 소개되었고 이번 1.7.0 에서 Stable 단계에 접어들었습니다. 함수에 DeepRecursiveFunction
을 사용하면 call stack 을 매번 생성하지 않고 stack 을 heap 에 유지합니다.
아래 코드는 재귀적으로 이진 트리의 depth 를 계산합니다. 재귀적으로 100000 번의 호출이 일어나지만 StackOverflowError
가 발생하지 않죠.
자바 Optional 타입에 대한 새로운 확장 함수
코틀린 1.7.0 버전에서 자바 Optional 타입에 대한 간편한 확장 함수를 실험적으로 제공합니다. 이 새로운 함수들로 JVM 의 optional 객체를 손쉽게 다룰 수 있습니다.
getOrNull()
, getOrDefault()
, getOrElse()
라는 확장함수입니다. 이 함수들로 Optional 객체의 값이 존재한다면 이를 반환하고 존재하지 않는다면 함수에 따라 null, 디폴트 값 혹은 특정 값을 반환하게 할 수 있습니다.
toList()
, toSet()
, asSequence()
확장함수는 optional 객체를 list, set 혹은 sequence 타입으로 변환합니다. toCollection()
함수는 이미 존재하는 특정 콜렉션에 optional 객체를 추가합니다.
위에서 소개한 Optional 클래스 확장함수들은 1.7.0 버전에서 실험적으로 공개한 기능입니다. 이에 대한 자세한 내용은 KEEP 문서를 참고해주세요.