Kotlin 1.5.0 的新特性
Kotlin 1.5.0 introduces new language features, stable IR-based JVM compiler backend, performance improvements, and evolutionary changes such as stabilizing experimental features and deprecating outdated ones.
You can also find an overview of the changes in the release blog post.
语言特性
Kotlin 1.5.0 brings stable versions of the new language features presented for preview in 1.4.30:
Detailed descriptions of these features are available in this blog post and the corresponding pages of Kotlin documentation.
JVM 记录类型支持
Java is evolving fast, and to make sure Kotlin remains interoperable with it, we’ve introduced support for one of its latest features – record classes.
Kotlin’s support for JVM records includes bidirectional interoperability:
- In Kotlin code, you can use Java record classes like you would use typical classes with properties.
- To use a Kotlin class as a record in Java code, make it a
data
class and mark it with the@JvmRecord
annotation.
@JvmRecord
data class User(val name: String, val age: Int)
Learn more about using JVM records in Kotlin.
YouTube 视频:Support for JVM Records in Kotlin 1.5.0
密封接口
Kotlin interfaces can now have the sealed
modifier, which works on interfaces in the same way it works on classes: all implementations of a sealed interface are known at compile time.
sealed interface Polygon
You can rely on that fact, for example, to write exhaustive when
expressions.
fun draw(polygon: Polygon) = when (polygon) {
is Rectangle -> // ...
is Triangle -> // ...
// else is not needed - all possible implementations are covered
}
Additionally, sealed interfaces enable more flexible restricted class hierarchies because a class can directly inherit more than one sealed interface.
class FilledRectangle: Polygon, Fillable
Learn more about sealed interfaces.
YouTube 视频:Sealed Interfaces and Sealed Classes Improvements
包范围的密封类层次结构
Sealed classes can now have subclasses in all files of the same compilation unit and the same package. Previously, all subclasses had to appear in the same file.
Direct subclasses may be top-level or nested inside any number of other named classes, named interfaces, or named objects.
The subclasses of a sealed class must have a name that is properly qualified – they cannot be local or anonymous objects.
Learn more about sealed class hierarchies.
内联类
Inline classes are a subset of value-based classes that only hold values. You can use them as wrappers for a value of a certain type without the additional overhead that comes from using memory allocations.
Inline classes can be declared with the value
modifier before the name of the class:
value class Password(val s: String)
The JVM backend also requires a special @JvmInline
annotation:
@JvmInline
value class Password(val s: String)
The inline
modifier is now deprecated with a warning.
Learn more about inline classes.
YouTube 视频:From Inline to Value Classes
Kotlin/JVM
Kotlin/JVM has received a number of improvements, both internal and user-facing. Here are the most notable among them:
- 稳定版 JVM IR 后端
- 新的默认 JVM 目标:1.8
- 采用 invokedynamic 的 SAM 适配器
- 采用 invokedynamic 的 lambda 表达式
- @JvmDefault 与旧版 Xjvm-default 模式的弃用
- 处理可空性注解的改进
稳定版 JVM IR 后端
The IR-based backend for the Kotlin/JVM compiler is now Stable and enabled by default.
Starting from Kotlin 1.4.0, early versions of the IR-based backend were available for preview, and it has now become the default for language version 1.5
. The old backend is still used by default for earlier language versions.
You can find more details about the benefits of the IR backend and its future development in this blog post.
If you need to use the old backend in Kotlin 1.5.0, you can add the following lines to the project’s configuration file:
- In Gradle:
【Kotlin】
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
kotlinOptions.useOldBackend = true
}
【Groovy】
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) {
kotlinOptions.useOldBackend = true
}
In Maven:
<configuration>
<args>
<arg>-Xuse-old-backend</arg>
</args>
</configuration>
新的默认 JVM 目标:1.8
The default target version for Kotlin/JVM compilations is now 1.8
. The 1.6
target is deprecated.
If you need a build for JVM 1.6, you can still switch to this target. Learn how:
采用 invokedynamic 的 SAM 适配器
Kotlin 1.5.0 now uses dynamic invocations (invokedynamic
) for compiling SAM (Single Abstract Method) conversions:
- Over any expression if the SAM type is a Java interface
- Over lambda if the SAM type is a Kotlin functional interface
The new implementation uses LambdaMetafactory.metafactory() and auxiliary wrapper classes are no longer generated during compilation. This decreases the size of the application’s JAR, which improves the JVM startup performance.
To roll back to the old implementation scheme based on anonymous class generation, add the compiler option -Xsam-conversions=class
.
Learn how to add compiler options in Gradle, Maven, and the command-line compiler.
采用 invokedynamic 的 lambda 表达式
Compiling plain Kotlin lambdas into invokedynamic is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below), and you should use it only for evaluation purposes. We would appreciate hearing your feedback on it in YouTrack.
Kotlin 1.5.0 is introducing experimental support for compiling plain Kotlin lambdas (which are not converted to an instance of a functional interface) into dynamic invocations (invokedynamic
). The implementation produces lighter binaries by using LambdaMetafactory.metafactory(), which effectively generates the necessary classes at runtime. Currently, it has three limitations compared to ordinary lambda compilation:
- A lambda compiled into invokedynamic is not serializable.
- Calling
toString()
on such a lambda produces a less readable string representation. - Experimental reflect API does not support lambdas created with
LambdaMetafactory
.
To try this feature, add the -Xlambdas=indy
compiler option. We would be grateful if you could share your feedback on it using this YouTrack ticket.
Learn how to add compiler options in Gradle, Maven, and command-line compiler.
@JvmDefault 与旧版 Xjvm-default 模式的弃用
Prior to Kotlin 1.4.0, there was the @JvmDefault
annotation along with -Xjvm-default=enable
and -Xjvm-default=compatibility
modes. They served to create the JVM default method for any particular non-abstract member in the Kotlin interface.
In Kotlin 1.4.0, we introduced the new Xjvm-default modes, which switch on default method generation for the whole project.
In Kotlin 1.5.0, we are deprecating @JvmDefault
and the old Xjvm-default modes: -Xjvm-default=enable
and -Xjvm-default=compatibility
.
Learn more about default methods in the Java interop.
处理可空性注解的改进
Kotlin supports handling type nullability information from Java with nullability annotations. Kotlin 1.5.0 introduces a number of improvements for the feature:
- It reads nullability annotations on type arguments in compiled Java libraries that are used as dependencies.
- It supports nullability annotations with the
TYPE_USE
target for:- Arrays
- Varargs
- Fields
- Type parameters and their bounds
- Type arguments of base classes and interfaces
- If a nullability annotation has multiple targets applicable to a type, and one of these targets is
TYPE_USE
, thenTYPE_USE
is preferred. For example, the method signature@Nullable String[] f()
becomesfun f(): Array<String?>!
if@Nullable
supports bothTYPE_USE
andMETHOD
as targets.
For these newly supported cases, using the wrong type nullability when calling Java from Kotlin produces warnings. Use the -Xtype-enhancement-improvements-strict-mode
compiler option to enable strict mode for these cases (with error reporting).
Learn more about null-safety and platform types.
Kotlin/Native
Kotlin/Native is now more performant and stable. The notable changes are:
性能提升
In 1.5.0, Kotlin/Native is receiving a set of performance improvements that speed up both compilation and execution.
Compiler caches are now supported in debug mode for linuxX64
(only on Linux hosts) and iosArm64
targets. With compiler caches enabled, most debug compilations complete much faster, except for the first one. Measurements showed about a 200% speed increase on our test projects.
To use compiler caches for new targets, opt in by adding the following lines to the project’s gradle.properties
:
- For
linuxX64
:kotlin.native.cacheKind.linuxX64=static
- For
iosArm64
:kotlin.native.cacheKind.iosArm64=static
If you encounter any issues after enabling the compiler caches, please report them to our issue tracker YouTrack.
Other improvements speed up the execution of Kotlin/Native code:
- Trivial property accessors are inlined.
trimIndent()
on string literals is evaluated during the compilation.
停用内存泄漏检测器
The built-in Kotlin/Native memory leak checker has been disabled by default.
It was initially designed for internal use, and it is able to find leaks only in a limited number of cases, not all of them. Moreover, it later turned out to have issues that can cause application crashes. So we’ve decided to turn off the memory leak checker.
The memory leak checker can still be useful for certain cases, for example, unit testing. For these cases, you can enable it by adding the following line of code:
Platform.isMemoryLeakCheckerActive = true
Note that enabling the checker for the application runtime is not recommended.
Kotlin/JS
Kotlin/JS is receiving evolutionary changes in 1.5.0. We’re continuing our work on moving the JS IR compiler backend towards stable and shipping other updates:
升级到 webpack 5
The Kotlin/JS Gradle plugin now uses webpack 5 for browser targets instead of webpack 4. This is a major webpack upgrade that brings incompatible changes. If you’re using a custom webpack configuration, be sure to check the webpack 5 release notes.
Learn more about bundling Kotlin/JS projects with webpack.
用于 IR 编译器的框架与库
The Kotlin/JS IR compiler is in Alpha. It may change incompatibly and require manual migration in the future. We would appreciate your feedback on it in YouTrack.
Along with working on the IR-based backend for Kotlin/JS compiler, we encourage and help library authors to build their projects in both
mode. This means they are able to produce artifacts for both Kotlin/JS compilers, therefore growing the ecosystem for the new compiler.
Many well-known frameworks and libraries are already available for the IR backend: KVision, fritz2, doodle, and others. If you’re using them in your project, you can already build it with the IR backend and see the benefits it brings.
If you’re writing your own library, compile it in the ‘both’ mode so that your clients can also use it with the new compiler.
Kotlin 多平台
In Kotlin 1.5.0, choosing a testing dependency for each platform has been simplified and it is now done automatically by the Gradle plugin.
A new API for getting a char category is now available in multiplatform projects.
标准库
The standard library has received a range of changes and improvements, from stabilizing experimental parts to adding new features:
- 稳定版无符号整数类型
- 稳定版用于大小写文本的区域设置无关 API
- 稳定版字符到整数转换 API
- 稳定版 Path API
- 趋负无穷截尾的除余运算
- Duration API 变更
- 用于获取字符类别的新版 API 现已对多平台代码可用
- 新的集合函数 firstNotNullOf()
- String?.toBoolean() 的严格版本
You can learn more about the standard library changes in this blog post.
YouTube 视频:New Standard Library Features
稳定版无符号整数类型
The UInt
, ULong
, UByte
, UShort
unsigned integer types are now Stable. The same goes for operations on these types, ranges, and progressions of them. Unsigned arrays and operations on them remain in Beta.
Learn more about unsigned integer types.
稳定版用于大小写文本的区域设置无关 API
This release brings a new locale-agnostic API for uppercase/lowercase text conversion. It provides an alternative to the toLowerCase()
, toUpperCase()
, capitalize()
, and decapitalize()
API functions, which are locale-sensitive. The new API helps you avoid errors due to different locale settings.
Kotlin 1.5.0 provides the following fully Stable alternatives:
- For
String
functions:
早期版本 | 1.5.0 的另一选择 |
---|---|
String.toUpperCase() | String.uppercase() |
String.toLowerCase() | String.lowercase() |
String.capitalize() | String.replaceFirstChar { it.uppercase() } |
String.decapitalize() | String.replaceFirstChar { it.lowercase() } |
- For
Char
functions:
早期版本 | 1.5.0 的另一选择 |
---|---|
Char.toUpperCase() | Char.uppercaseChar(): Char Char.uppercase(): String |
Char.toLowerCase() | Char.lowercaseChar(): Char Char.lowercase(): String |
Char.toTitleCase() | Char.titlecaseChar(): Char Char.titlecase(): String |
For Kotlin/JVM, there are also overloaded
uppercase()
,lowercase()
, andtitlecase()
functions with an explicitLocale
parameter.
The old API functions are marked as deprecated and will be removed in a future release.
See the full list of changes to the text processing functions in KEEP.
稳定版字符到整数转换 API
Starting from Kotlin 1.5.0, new char-to-code and char-to-digit conversion functions are Stable. These functions replace the current API functions, which were often confused with the similar string-to-Int conversion.
The new API removes this naming confusion, making the code behavior more transparent and unambiguous.
This release introduces Char
conversions that are divided into the following sets of clearly named functions:
Functions to get the integer code of
Char
and to constructChar
from the given code:fun Char(code: Int): Char
fun Char(code: UShort): Char
val Char.code: Int
Functions to convert
Char
to the numeric value of the digit it represents:fun Char.digitToInt(radix: Int): Int
fun Char.digitToIntOrNull(radix: Int): Int?
An extension function for
Int
to convert the non-negative single digit it represents to the correspondingChar
representation:fun Int.digitToChar(radix: Int): Char
The old conversion APIs, including Number.toChar()
with its implementations (all except Int.toChar()
) and Char
extensions for conversion to a numeric type, like Char.toInt()
, are now deprecated.
Learn more about the char-to-integer conversion API in KEEP.
稳定版 Path API
The experimental Path API with extensions for java.nio.file.Path
is now Stable.
// construct path with the div (/) operator
val baseDir = Path("/base")
val subDir = baseDir / "subdirectory"
// list files in a directory
val kotlinFiles: List<Path> = Path("/home/user").listDirectoryEntries("*.kt")
Learn more about the Path API.
趋负无穷截尾的除余运算
New operations for modular arithmetics have been added to the standard library:
floorDiv()
returns the result of floored division. It is available for integer types.mod()
returns the remainder of floored division (modulus). It is available for all numeric types.
These operations look quite similar to the existing division of integers and rem() function (or the %
operator), but they work differently on negative numbers:
a.floorDiv(b)
differs from a regular/
in thatfloorDiv
rounds the result down (towards the lesser integer), whereas/
truncates the result to the integer closer to 0.a.mod(b)
is the difference betweena
anda.floorDiv(b) * b
. It’s either zero or has the same sign asb
, whilea % b
can have a different one.
fun main() {
//sampleStart
println("Floored division -5/3: ${(-5).floorDiv(3)}")
println( "Modulus: ${(-5).mod(3)}")
println("Truncated division -5/3: ${-5 / 3}")
println( "Remainder: ${-5 % 3}")
//sampleEnd
}
Duration API 变更
The Duration API is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate hearing your feedback on it in YouTrack.
There is an experimental Duration class for representing duration amounts in different time units. In 1.5.0, the Duration API has received the following changes:
- Internal value representation now uses
Long
instead ofDouble
to provide better precision. - There is a new API for conversion to a particular time unit in
Long
. It comes to replace the old API, which operates withDouble
values and is now deprecated. For example, Duration.inWholeMinutes returns the value of the duration expressed asLong
and replacesDuration.inMinutes
. - There are new companion functions for constructing a
Duration
from a number. For example, Duration.seconds(Int) creates aDuration
object representing an integer number of seconds. Old extension properties likeInt.seconds
are now deprecated.
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
@ExperimentalTime
fun main() {
//sampleStart
val duration = Duration.milliseconds(120000)
println("There are ${duration.inWholeSeconds} seconds in ${duration.inWholeMinutes} minutes")
//sampleEnd
}
用于获取字符类别的新版 API 现已对多平台代码可用
Kotlin 1.5.0 introduces the new API for getting a character’s category according to Unicode in multiplatform projects. Several functions are now available in all the platforms and in the common code.
Functions for checking whether a char is a letter or a digit:
fun main() {
//sampleStart
val chars = listOf('a', '1', '+')
val (letterOrDigitList, notLetterOrDigitList) = chars.partition { it.isLetterOrDigit() }
println(letterOrDigitList) // [a, 1]
println(notLetterOrDigitList) // [+]
//sampleEnd
}
Functions for checking the case of a char:
fun main() {
//sampleStart
val chars = listOf('Dž', 'Lj', 'Nj', 'Dz', '1', 'A', 'a', '+')
val (titleCases, notTitleCases) = chars.partition { it.isTitleCase() }
println(titleCases) // [Dž, Lj, Nj, Dz]
println(notTitleCases) // [1, A, a, +]
//sampleEnd
}
Some other functions:
The property Char.category and its return type enum class CharCategory, which indicates a char’s general category according to Unicode, are now also available in multiplatform projects.
新的集合函数 firstNotNullOf()
The new firstNotNullOf() and firstNotNullOfOrNull() functions combine mapNotNull() with first() or firstOrNull(). They map the original collection with the custom selector function and return the first non-null value. If there is no such value, firstNotNullOf()
throws an exception, and firstNotNullOfOrNull()
returns null.
fun main() {
//sampleStart
val data = listOf("Kotlin", "1.5")
println(data.firstNotNullOf(String::toDoubleOrNull))
println(data.firstNotNullOfOrNull(String::toIntOrNull))
//sampleEnd
}
String?.toBoolean() 的严格版本
Two new functions introduce case-sensitive strict versions of the existing String?.toBoolean():
- String.toBooleanStrict() throws an exception for all inputs except the literals
true
andfalse
. - String.toBooleanStrictOrNull() returns null for all inputs except the literals
true
andfalse
.
fun main() {
//sampleStart
println("true".toBooleanStrict())
println("1".toBooleanStrictOrNull())
// println("1".toBooleanStrict()) // Exception
//sampleEnd
}
kotlin-test 库
The kotlin-test library introduces some new features:
简化多平台项目中的测试依赖项用法
Now you can use the kotlin-test
dependency to add dependencies for testing in the commonTest
source set, and the Gradle plugin will infer the corresponding platform dependencies for each test source set:
kotlin-test-junit
for JVM source sets, see automatic choice of a testing framework for Kotlin/JVM source setskotlin-test-js
for Kotlin/JS source setskotlin-test-common
andkotlin-test-annotations-common
for common source sets- No extra artifact for Kotlin/Native source sets
Additionally, you can use the kotlin-test
dependency in any shared or platform-specific source set.
An existing kotlin-test setup with explicit dependencies will continue to work both in Gradle and in Maven.
Learn more about setting dependencies on test libraries.
Kotlin/JVM 源代码集测试框架的自动选择
The Gradle plugin now chooses and adds a dependency on a testing framework automatically. All you need to do is add the dependency kotlin-test
in the common source set.
Gradle uses JUnit 4 by default. Therefore, the kotlin("test")
dependency resolves to the variant for JUnit 4, namely kotlin-test-junit
:
【Kotlin】
kotlin {
sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test")) // This brings the dependency
// on JUnit 4 transitively
}
}
}
}
【Groovy】
kotlin {
sourceSets {
commonTest {
dependencies {
implementation kotlin("test") // This brings the dependency
// on JUnit 4 transitively
}
}
}
}
You can choose JUnit 5 or TestNG by calling useJUnitPlatform() or useTestNG() in the test task:
tasks {
test {
// enable TestNG support
useTestNG()
// or
// enable JUnit Platform (a.k.a. JUnit 5) support
useJUnitPlatform()
}
}
You can disable automatic testing framework selection by adding the line kotlin.test.infer.jvm.variant=false
to the project’s gradle.properties
.
Learn more about setting dependencies on test libraries.
断言函数更新
This release brings new assertion functions and improves the existing ones.
The kotlin-test
library now has the following features:
检测值的类型
You can use the new
assertIs<T>
andassertIsNot<T>
to check the type of a value:@Test
fun testFunction() {
val s: Any = "test"
assertIs<String>(s) // throws AssertionError mentioning the actual type of s if the assertion fails
// can now print s.length because of contract in assertIs
println("${s.length}")
}
Because of type erasure, this assert function only checks whether the
value
is of theList
type in the following example and doesn’t check whether it’s a list of the particularString
element type:assertIs<List<String>>(value)
.比较数组、序列以及任意可迭代容器的内容
There is a new set of overloaded
assertContentEquals()
functions for comparing content for different collections that don’t implement structural equality:@Test
fun test() {
val expectedArray = arrayOf(1, 2, 3)
val actualArray = Array(3) { it + 1 }
assertContentEquals(expectedArray, actualArray)
}
Double
andFloat
数值的assertEquals()
与assertNotEquals()
的新的重载There are new overloads for the
assertEquals()
function that make it possible to compare twoDouble
orFloat
numbers with absolute precision. The precision value is specified as the third parameter of the function:@Test
fun test() {
val x = sin(PI)
// precision parameter
val tolerance = 0.000001
assertEquals(0.0, x, tolerance)
}
用于检测集合与元素内容的新函数
You can now check whether the collection or element contains something with the
assertContains()
function. You can use it with Kotlin collections and elements that have thecontains()
operator, such asIntRange
,String
, and others:@Test
fun test() {
val sampleList = listOf<String>("sample", "sample2")
val sampleString = "sample"
assertContains(sampleList, sampleString) // element in collection
assertContains(sampleString, "amp") // substring in string
}
assertTrue()
、assertFalse()
、expect()
现在是内联函数From now on, you can use these as inline functions, so it’s possible to call suspend functions inside a lambda expression:
@Test
fun test() = runBlocking<Unit> {
val deferred = async { "Kotlin is nice" }
assertTrue("Kotlin substring should be present") {
deferred.await() .contains("Kotlin")
}
}
kotlinx 库
Along with Kotlin 1.5.0, we are releasing new versions of the kotlinx libraries:
coroutines 1.5.0-RC
kotlinx.coroutines
1.5.0-RC is here with:
- New channels API
- Stable reactive integrations
- And more
Starting with Kotlin 1.5.0, experimental coroutines are disabled and the -Xcoroutines=experimental
flag is no longer supported.
Learn more in the changelog and the kotlinx.coroutines 1.5.0 release blog post.
YouTube 视频:kotlinx.coroutines 1.5.0
serialization 1.2.1
kotlinx.serialization
1.2.1 is here with:
- Improvements to JSON serialization performance
- Support for multiple names in JSON serialization
- Experimental .proto schema generation from
@Serializable
classes - And more
Learn more in the changelog and the kotlinx.serialization 1.2.1 release blog post.
YouTube 视频:kotlinx.serialization 1.2.1
dateTime 0.2.0
kotlinx-datetime
0.2.0 is here with:
@Serializable
Datetime objects- Normalized API of
DateTimePeriod
andDatePeriod
- And more
Learn more in the changelog and the kotlinx-datetime 0.2.0 release blog post.
迁移到 Kotlin 1.5.0
IntelliJ IDEA and Android Studio will suggest updating the Kotlin plugin to 1.5.0 once it is available.
To migrate existing projects to Kotlin 1.5.0, just change the Kotlin version to 1.5.0
and re-import your Gradle or Maven project. Learn how to update to Kotlin 1.5.0.
To start a new project with Kotlin 1.5.0, update the Kotlin plugin and run the Project Wizard from File | New | Project.
The new command-line compiler is available for downloading on the GitHub release page.
Kotlin 1.5.0 is a feature release and therefore can bring incompatible changes to the language. Find the detailed list of such changes in the Compatibility Guide for Kotlin 1.5.