6月9號(hào)憾筏,Kotlin 發(fā)布了1.7.0正式版嚎杨。本文將大致過(guò)一遍主要的新特性。
本文主要來(lái)自 What's new in Kotlin 1.7.0 | Kotlin氧腰,部分來(lái)自 Kotlin 1.7.0-Beta 現(xiàn)已發(fā)布 | The Kotlin Blog 枫浙,完整內(nèi)容也請(qǐng)參考這些鏈接。受限于本人水平古拴,難免有誤箩帚,敬請(qǐng)諒解。
作者:FunnySaltyFish (github.com)
以下是此版本主要更新:
新的 Kotlin К2 compiler 發(fā)布 Alpha 版本, 帶來(lái)顯著的性能提高黄痪。 目前僅有 JVM 可用紧帕,其余編譯器插件,包括
kapt
均無(wú)法使用Gradle 中增量編譯的新方法∥Υ颍現(xiàn)在是嗜,在依賴的非 Kotlin 模塊內(nèi)所做的更改也支持增量編譯,并且與 Gradle 兼容挺尾。
穩(wěn)定的opt-in 注解要求鹅搪、明確非空類型 和 Builder 推理。
用于類型參數(shù)的下劃線運(yùn)算符潦嘶。在指定其他類型時(shí)涩嚣,可以使用它來(lái)自動(dòng)推斷參數(shù)類型。
內(nèi)聯(lián)類的內(nèi)聯(lián)值可以委托〉嘟現(xiàn)在航厚,您可以創(chuàng)建在大多數(shù)情況下不分配內(nèi)存的輕量級(jí)wrapper。
新的 Kotlin K2 編譯器
此 Kotlin 版本引入了新的 Kotlin K2 編譯器的 Alpha 版本锰蓬。新的編譯器旨在加快新語(yǔ)言功能的開(kāi)發(fā)幔睬,統(tǒng)一Kotlin支持的所有平臺(tái),帶來(lái)性能改進(jìn)芹扭,并為編譯器擴(kuò)展提供API麻顶。
我們已經(jīng)發(fā)布了一些關(guān)于我們的新編譯器及其優(yōu)點(diǎn)的詳細(xì)說(shuō)明:
需要強(qiáng)調(diào)的是,對(duì)于新K2編譯器的Alpha版本舱卡,我們主要關(guān)注性能改進(jìn)辅肾,并且它僅適用于JVM項(xiàng)目。它不支持Kotlin / JS轮锥,Kotlin / Native 或其他多平臺(tái)項(xiàng)目矫钓,并且包括 kapt 在內(nèi)的編譯器插件都無(wú)法使用它。
我們的基準(zhǔn)測(cè)試顯示了我們內(nèi)部項(xiàng)目的一些杰出成果:
Project | 現(xiàn)版本 Kotlin 編譯器 | 新的 K2 Kotlin 編譯器 | 相對(duì)提升 |
---|---|---|---|
Kotlin | 2.2 KLOC/s | 4.8 KLOC/s | ~ x2.2 |
YouTrack | 1.8 KLOC/s | 4.2 KLOC/s | ~ x2.3 |
IntelliJ IDEA | 1.8 KLOC/s | 3.9 KLOC/s | ~ x2.2 |
Space | 1.2 KLOC/s | 2.8 KLOC/s | ~ x2.3 |
KLOC/s 為每秒鐘編譯器處理的源代碼行數(shù)(千行)
您可以查看 JVM 項(xiàng)目的性能提升舍杜,并將其與舊編譯器的結(jié)果進(jìn)行比較新娜。要啟用 Kotlin K2 編譯器,請(qǐng)使用以下編譯器選項(xiàng):
-Xuse-k2
此外既绩,K2 編譯器 還包括許多錯(cuò)誤修復(fù): fixed-in-frontend-ir sort by: Priority, votes, updated)概龄。請(qǐng)注意,此列表中以 State: Open 開(kāi)頭的 bugs 實(shí)際上在 K2 里也已得到修復(fù)饲握。
下一個(gè) Kotlin 版本將提高 K2 編譯器的穩(wěn)定性并提供更多功能私杜,敬請(qǐng)期待并提供您的反饋!
語(yǔ)法
內(nèi)聯(lián)類的內(nèi)聯(lián)值也能委托了
如果要為值或類實(shí)例創(chuàng)建輕量級(jí)wrapper救欧,則必須手動(dòng)實(shí)現(xiàn)所有接口方法歪今,委托實(shí)現(xiàn)解決了這個(gè)問(wèn)題。但在 1.7.0 之前颜矿,它不適用于內(nèi)聯(lián)類寄猩。此限制已被刪除,因此您現(xiàn)在可以創(chuàng)建在大多數(shù)情況下不分配內(nèi)存的輕量級(jí)wrapper骑疆。
// 接口田篇,唯一的方法返回一個(gè)字符串
interface Bar {
fun foo() = "foo"
}
@JvmInline
// kt1.7之前,對(duì)內(nèi)聯(lián)類不能如此寫(xiě)
// 否則會(huì)報(bào) "Value class cannot implement an interface by delegation if expression is not a parameter"
// kt1.7 解除了這個(gè)限制
value class BarWrapper(val bar: Bar): Bar by bar
fun main() {
val bw = BarWrapper(object: Bar {})
println(bw.foo())
}
類型參數(shù)的下劃線運(yùn)算符
Kotlin 1.7.0 為類型參數(shù)引入了一個(gè)下劃線運(yùn)算符 箍铭〔醇恚可以使用它在指定其他類型時(shí)自動(dòng)推斷類型參數(shù):_
abstract class SomeClass<T> {
abstract fun execute(): T
}
class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}
object Runner {
inline fun <reified S: SomeClass<T>, T> run(): T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T 被推斷為 String,因?yàn)?SomeImplementation 繼承自 SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// T 被推斷為 Int 诈火,因?yàn)?OtherImplementation 繼承自 SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
構(gòu)建器推斷變更
構(gòu)建器推斷 (Builder inference) 是一種特殊的類型推斷兽赁,在調(diào)用泛型構(gòu)建器函數(shù)時(shí)很有幫助。 它可以幫助編譯器推斷調(diào)用的類型實(shí)參,方法是使用其 lambda 實(shí)參中其他調(diào)用的相關(guān)類型信息刀崖。
在此版本中惊科,如果常規(guī)類型推斷無(wú)法獲得有關(guān)類型的足夠信息,即可自動(dòng)激活構(gòu)建器推斷亮钦。(以前需額外指定 -Xenable-builder-inference
編譯器選項(xiàng)——在 1.6.0 版本中引入)馆截。這意味著現(xiàn)在您無(wú)需應(yīng)用任何額外的注解或選項(xiàng),即可編寫(xiě)自己的使用構(gòu)建器類型推斷的構(gòu)建器蜂莉。
FunnySaltyFish:放幾個(gè)栗子:
val result = buildList {
// Type argument is inferred into Float based on the expected type
val x: Float = get(0)
} // result has the List<Float> type
fun takeMyLong(x: Long) { ... }
fun String.isMoreThat3() = length > 3
fun takeListOfStrings(x: List<String>) { ... }
fun main() {
val result1 = buildList {
val x = get(0)
takeMyLong(x)
} // result1 has the List<Long> type
val result2 = buildList {
val x = get(0)
val isLong = x.isMoreThat3()
// ...
} // result2 has the List<String> type
val result3 = buildList {
takeListOfStrings(this)
} // result3 has the List<String> type
}
更多內(nèi)容詳見(jiàn): 了解如何編寫(xiě)自定義通用構(gòu)建器蜡娶。
穩(wěn)定的 opt-in requirements
Opt-in requirements 已為 Stable ,無(wú)需添加額外的編譯器參數(shù).
在 1.7.0 之前, opt-in 需要指定參數(shù) -opt-in=kotlin.RequiresOptIn
以避免 warning映穗,現(xiàn)在不需要了; 不過(guò)窖张,您仍然可使用 -opt-in
選擇加入其他 annotations、 module-wise.
穩(wěn)定的明確非空類型
在 Kotlin 1.7.0 中蚁滋,絕對(duì)不可為 null 的類型已提升為 Stable荤堪。它們?cè)跀U(kuò)展通用 Java 類和接口時(shí)提供了更好的互操作性。
使用新語(yǔ)法 T & Any
標(biāo)記此為明確非空(絕對(duì)不可空)類型枢赔。 此語(yǔ)法來(lái)自 intersection types 的符號(hào)澄阳。在 &
左側(cè)為可空的類型參數(shù),右側(cè)為不可空的Any
踏拜。
fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
fun main() {
// OK
elvisLike<String>("", "").length
// 錯(cuò)誤: 'null' 無(wú)法用于 non-null 值
elvisLike<String>("", null).length
// OK
elvisLike<String?>(null, "").length
// 錯(cuò)誤: 'null' 無(wú)法用于 non-null 值
elvisLike<String?>(null, null).length
}
在此 KEEP 中了解有關(guān)絕對(duì)不可為 null 的類型的更多信息碎赢。
標(biāo)準(zhǔn)庫(kù)
在 Kotlin 1.7.0 中,標(biāo)準(zhǔn)庫(kù)進(jìn)行了一系列更改和改進(jìn)速梗。它們引入了新功能肮塞,穩(wěn)定了實(shí)驗(yàn)性功能,并統(tǒng)一了對(duì) Native姻锁、JS 和 JVM 的命名捕獲組的支持:
min() 和 max() 集合函數(shù)回歸
在 Kotlin 1.4 中枕赵,我們將 min()
和 max()
集合函數(shù)重命名為 minOrNull()
和 maxOrNull()
。 這些新的名稱能夠更好地反映它們的行為 – 如果接收器集合為空位隶,則返回 null
拷窜。 它還有助于使函數(shù)的行為與整個(gè) Kotlin Collections API 中使用的命名慣例保持一致。
minBy()
涧黄、maxBy()
篮昧、minWith()
和 maxWith()
同樣如此,在 Kotlin 1.4 中均具有自己的 *OrNull()
同義詞笋妥。 受此變更影響的舊函數(shù)已逐漸棄用懊昨。
Kotlin 1.7.0-Beta 重新引入了原始的函數(shù)名稱枚尼,但加入了不可空返回類型鸯隅。 現(xiàn)在,更新后的 min()
登颓、max()
、minBy()
躏惋、maxBy()
幽污、minWith()
和 maxWith()
會(huì)嚴(yán)格返回集合元素或拋出異常。
fun main() {
val numbers = listOf<int>()
println(numbers.maxOrNull()) // "null"
println(numbers.max()) // "Exception in… Collection is empty."
}
正則表達(dá)式特定位置匹配
在 1.5.30 中引入的 Regex.matchAt()
和 Regex.matchesAt()
函數(shù)現(xiàn)已達(dá)到穩(wěn)定版本其掂。 它們提供了一種方式來(lái)檢查正則表達(dá)式在 String
或 CharSequence
中的特定位置是否具有精確匹配油挥。
-
matchesAt()
可以檢查匹配并返回布爾結(jié)果:
fun main(){
val releaseText = "Kotlin 1.7.0 is on its way!"
// 正則表達(dá)式: 一個(gè)數(shù)字, “.”, 一個(gè)數(shù)字, “.”, 一個(gè)或多個(gè)數(shù)字
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchesAt(releaseText, 0)) // "false"
println(versionRegex.matchesAt(releaseText, 7)) // "true"
}
-
matchAt()
會(huì)在找到匹配的情況下返回匹配潦蝇,在未找到匹配的情況下返回null
:
fun main(){
val releaseText = "Kotlin 1.7.0 is on its way!"
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchAt(releaseText, 0)) // "null"
println(versionRegex.matchAt(releaseText, 7)?.value) // "1.7.0"
}
對(duì)以前語(yǔ)言和 API 版本的擴(kuò)展支持
為了支持庫(kù)作者 開(kāi)發(fā)可在各種舊 Kotlin 版本中使用的庫(kù)款熬,并解決 Kotlin 主版本更新頻率增加的問(wèn)題,我們擴(kuò)展了對(duì)以前語(yǔ)言和 API 版本的支持攘乒。
在 Kotlin 1.7.0 中贤牛,我們支持三個(gè)以前的語(yǔ)言和 API 版本,而不是兩個(gè)则酝。這意味著 Kotlin 1.7.0 支持針對(duì) 1.4.0 的 Kotlin 版本的庫(kù)開(kāi)發(fā)殉簸。有關(guān)向后兼容性的詳細(xì)信息,請(qǐng)參閱兼容性模式沽讹。
通過(guò)反射獲取注解
1.6.0 首次引入的 拓展函數(shù) KAnnotatedElement.findAnnotations()
現(xiàn)已進(jìn)入 Stable. 此 反射 函數(shù)返回某元素特定類型的所有注解, 包括 獨(dú)立使用 和 重復(fù)使用 的注解.
@Repeatable
annotation class Tag(val name: String)
@Tag("First Tag")
@Tag("Second Tag")
fun taggedFunction() {
println("I'm a tagged function!")
}
fun main() {
val x = ::taggedFunction
val foo = x as KAnnotatedElement
println(foo.findAnnotations<Tag>())
// [@Tag(name=First Tag), @Tag(name=Second Tag)]
}
穩(wěn)定的深度遞歸函數(shù)
深度遞歸函數(shù) (DeepRecursiveFunction) 自 Kotlin 1.4.0 以來(lái)一直作為實(shí)驗(yàn)性功能提供般卑,現(xiàn)在它們?cè)?Kotlin 1.7.0 中是穩(wěn)定的。使用DeepRecursiveFunction
可以定義一個(gè)函數(shù)爽雄,該函數(shù)將其堆棧保留在堆上蝠检,而不是實(shí)際的調(diào)用堆棧。這允許您運(yùn)行非常深的遞歸計(jì)算挚瘟。使用invoke
以調(diào)用這類函數(shù)叹谁。
在此示例中,深度遞歸函數(shù)用于以遞歸方式計(jì)算二叉樹(shù)的深度乘盖。即使此示例函數(shù)以遞歸方式調(diào)用自身 100焰檩,000 次,也不會(huì)拋出 StackOverflowError
class Tree(val left: Tree?, val right: Tree?)
val calculateDepth = DeepRecursiveFunction<Tree?, Int> { t ->
if (t == null) 0 else maxOf(
callRecursive(t.left),
callRecursive(t.right)
) + 1
}
fun main() {
// 生成一顆深度為 100000 的二叉樹(shù)
val deepTree = generateSequence(Tree(null, null)) { prev ->
Tree(prev, null)
}.take(100_000).last()
println(calculateDepth(deepTree)) // 100000
}
若遞歸深度超過(guò)1000订框, 請(qǐng)考慮在代碼中使用深度遞歸函數(shù)析苫。
基于默認(rèn)時(shí)間源的時(shí)間標(biāo)記現(xiàn)在基于內(nèi)聯(lián)類
Kotlin 1.7.0 通過(guò)將 返回的時(shí)間標(biāo)記更改為內(nèi)聯(lián)類,提高了時(shí)間測(cè)量功能的性能穿扳。這意味著調(diào)用像TimeSource.Monotonic
藤违、markNow()
、elapsedNow()
纵揍、measureTime()
顿乒、measureTimedValue()
、TimeMark
這樣的函數(shù)不會(huì)為其實(shí)例分配包裝類泽谨。特別是在測(cè)量作為hot path
一部分的代碼段時(shí)璧榄,這有助于最大限度地減少測(cè)量對(duì)性能的影響:
@OptIn(ExperimentalTime::class)
fun main() {
val mark = TimeSource.Monotonic.markNow() // 返回的 `TimeMark` 為內(nèi)聯(lián)類
val elapsedDuration = mark.elapsedNow()
}
僅當(dāng)從
TimeMark
中獲取的時(shí)間源對(duì)TimeSource.Monotonic
為靜態(tài)時(shí)特漩,此優(yōu)化才可用。
Java Optionals 的新實(shí)驗(yàn)性擴(kuò)展函數(shù)
Kotlin 1.7.0 附帶了新的便利函數(shù)骨杂,簡(jiǎn)化了 Java 中Optional
類的使用涂身。這些新功能可用于在 JVM 上拆箱和轉(zhuǎn)換可選對(duì)象,并幫助使 Java API 的使用更加簡(jiǎn)潔搓蚪。
拓展函數(shù)getOrNull()
蛤售、getOrDefault()
、getOrElse()
允許您獲取 Optional
的值(如果有的話)妒潭。否則悴能,將視情況獲得null
、默認(rèn)值或函數(shù)返回的值:
val presentOptional = Optional.of("FunnySaltyFish")
println(presentOptional.getOrNull())
// "FunnySaltyFish"
val absentOptional = Optional.empty<String>()
println(absentOptional.getOrNull()) // null
println(absentOptional.getOrDefault("給個(gè)默認(rèn)值")) // "給個(gè)默認(rèn)值"
println(absentOptional.getOrElse {
println("Optional 值缺失")
"默認(rèn)值"
})
// "Optional 值缺失"
// "默認(rèn)值"
擴(kuò)展函數(shù) toList()
雳灾、toSet()
漠酿、asSequence()
將現(xiàn)有 Optional
的值轉(zhuǎn)換為列表、集合或序列谎亩,否則返回空集合炒嘲。擴(kuò)展函數(shù) toCollection()
將值追加到已存在的目標(biāo)集合:
val presentOptional = Optional.of("I'm here!")
val absentOptional = Optional.empty<String>()
println(presentOptional.toList() + "," + absentOptional.toList())
// ["I'm here!"], []
println(presentOptional.toSet() + "," + absentOptional.toSet())
// ["I'm here!"], []
val myCollection = mutableListOf<String>()
absentOptional.toCollection(myCollection)
println(myCollection)
// []
presentOptional.toCollection(myCollection)
println(myCollection)
// ["I'm here!"]
val list = listOf(presentOptional, absentOptional).flatMap { it.asSequence() }
println(list)
// ["I'm here!"]
這些擴(kuò)展函數(shù)在 Kotlin 1.7.0 中作為實(shí)驗(yàn)性引入。您可以在此 KEEP 中了解有關(guān)Optional
擴(kuò)展的更多信息匈庭。與往常一樣夫凸,我們歡迎您在 Kotlin 問(wèn)題跟蹤器中提供反饋。
支持 JS 和本機(jī)中的命名捕獲組
從 Kotlin 1.7.0 開(kāi)始阱持,命名捕獲組不僅在 JVM 上受支持夭拌,在 JS 和 Native 平臺(tái)上也受支持。
若要為捕獲組命名紊选,請(qǐng)?jiān)谡齽t表達(dá)式中使用 (?<name>group
) 語(yǔ)法啼止。若要獲取與組匹配的文本,請(qǐng)調(diào)用新引入的 MatchGroupCollection.get()
函數(shù)并傳遞組名兵罢。
按名稱檢索匹配的組值
fun dateReplace() {
val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
val input = "Date of birth: 27-04-2022"
println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — by name
println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — by number
}
命名反向引用
現(xiàn)在献烦,您還可以在反向引用組時(shí)使用組名。反向引用與捕獲組先前匹配的相同文本匹配卖词。為此巩那,請(qǐng)使用正則表達(dá)式中的語(yǔ)法:\k<name>
fun backRef() {
val regex = "(?<title>\\w+), yes \\k<title>".toRegex()
val match = regex.find("Do you copy? Sir, yes Sir!")!!
println(match.value) // "Sir, yes Sir"
println(match.groups["title"]?.value) // "Sir"
}
換表達(dá)式中的命名組
命名組引用可與替換表達(dá)式一起使用。請(qǐng)考慮 replace()
函數(shù)此蜈,該函數(shù)將輸入中指定正則表達(dá)式的所有匹配項(xiàng)替換為替換表達(dá)式即横;以及僅替換第一個(gè)匹配項(xiàng)的 replaceFirst()
函數(shù)。
替換字符串中出現(xiàn)的 ${name}
將替換為與具有指定名稱的捕獲組相對(duì)應(yīng)的子序列裆赵。您可以按名稱和索引比較組引用中的替換項(xiàng):
fun dateReplace() {
val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
val input = "Date of birth: 27-04-2022"
println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — by name
println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — by number
}
Gradle
好多东囚,不翻了。見(jiàn) kotlinlang.org/docs/whatsn…
除上述之外战授,還有一些涉及到 JS/Native 的部分沒(méi)有翻譯页藻,感興趣的可自行參閱原鏈接桨嫁。