Kotlin的特點及各版本新特性

GT9456發表於2018-11-01

文章地址:sguotao.top/Kotlin-2018…

Kotlin語言的特點

Kotlin語義是一座小島,是一種在Java虛擬機器上執行的靜態型別程式語言,Kotlin的目的就是要兼具現代程式語言的所有優點,同時還要具有Java語言的跨平臺性,並且要做到簡潔。它也可以被編譯成為JavaScript原始碼。Kotlin與Java 100%相容,能夠執行在Android平臺和瀏覽器上。

Kotlin的應用場景

1.Kotlin Script 在IntellJ Idea中建立字尾為.kts的指令碼檔案,可以直接執行。Gradle在3.0之後部分支援Kotlin作為其指令碼語言。

2.Java虛擬機器應用 常見的Web應用,JavaFx應用,Kotlin都完美支援。Kotlin非常適合開發伺服器端應用程式,允許編寫簡潔和富有表現力的程式碼,同時保持與現有基於Java的技術堆疊的完全相容性和平滑的學習曲線;

3.前端開發 Kotlin從1.1版本開始,Kotlin可以編譯成JavaScript程式碼,執行在瀏覽器上。目前Kotlin已經支援ECMAScript 5.1,有計劃最終支援ECMAScript 2015。

4.Android應用開發 這是Kotlin應用的主戰場,Kotlin非常適合開發Android應用程式,將現代語言的所有優勢帶入Android平臺,而不會引入任何新的限制。

5.Native程式開發 直接將Kotlin程式碼編譯成機器碼,不依賴JVM,區別於JNI,能與C直接進行互動。Kotlin支援C語言,支援Objective-C的相互操作,其主要實現原理是將Kotlin編譯為在沒有任何VM的情況下執行的本機二進位制檔案。

Kotlin各版本的新特性

2016年2月15日,JetBrains釋出了Kotlin的第一個官方的release版本,並承諾從此版本開始向後相容。

Kotlin 1.1 的新特性

Kotlin1.1 開始支援JavaScript所有語言特性

在kotlin1.1版本中開始正式支援JavaScript的所有語言特性,並且提供了很多工具用來遷移前端的開發環境。

Kotlin1.1 引入協程

在kotlin1.1版本中提出了協程,支援async / await , yield and similar 的程式模式。協程可以認為是輕量級的執行緒,支援掛起和恢復。通過async { ... } 來開啟一個協程,通過 await() 掛起協程。

Kotlin1.1 引入的現代語言特性

kotlin1.1中支援更多的現代程式語言的特性,諸如:

1. 輸入別名

kotlin1.1中允許對已經存在的型別定義別名,這對集合類中的泛型,函式型別尤其適用。比如:

//使用typealias關鍵字定義別名
typealias OscarWinners = Map<String, String>

fun countLaLaLand(oscarWinners: OscarWinners) =
    oscarWinners.count { it.value.contains("La La Land") }
複製程式碼

2. ::引用

::在kotlin中是作為一個操作符存在的,使用 :: 操作符來獲取指向特定物件例項的方法或屬性的成員引用。比如獲取一個類的kclass位元組碼,可以寫為:

val c = MyClass::class
複製程式碼

同時 :: 也可以用在方法引用上,比如:

list.forEach(::println)
複製程式碼

這裡就引用了kotlin標準庫中的println方法。

3. 密封資料類

kotlin1.1中刪除了在1.0中對密封類和資料類的限制。可以在同一個檔案中的任意地方定義密封類的子類,而不是在密封類中使用巢狀的方式,資料類現在也可以擴充套件其他類。

密封類常用來表示首先的繼承結構,某種意義上說,密封類是列舉的一種擴充套件,每一個列舉的常量是列舉的例項,列舉是例項有限,在密封類中,是子類有限。定義密封類使用sealed關鍵字,比如:

sealed class Expr
複製程式碼

資料類常用來作為實體bean來使用,使用data關鍵字進行宣告,比如:

data class Const(val number: Double) : Expr()
複製程式碼

4. lambda中的解構

即將一個物件解構成多個變數方便使用。比如:

for ((key, value) in map) {
 // 使用該 key、value 做些事情
}
複製程式碼

5. 強調為未使用的引數

對具有多個引數的lambda表示式,可以使用“_”來替代沒有使用到的引數,比如:

map.forEach { _, value -> println("$value!") }
複製程式碼

6. 強調了數字字面值

比如金額等,可以通過“_”來間隔,如:

val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
複製程式碼

7. 短的語法屬性

對於私有化的成員變數,get方法可以省略成員變數的型別,編譯器會推斷出型別,比如:

data class Person(val name: String, val age: Int) {
val isAdult get() = age >= 20 // 屬性型別推斷為 “Boolean”
}
複製程式碼

8. 內聯inline屬性訪問器

對於成員變數,當使用inline關鍵字修飾時,該成員變數對應的get()和set()會作為行內函數。所謂的行內函數,就是在函式宣告時,使用inline關鍵字進行修飾,使用行內函數能夠更好的提升效能。比如:定義一個高階函式,接受一個lambda表示式(無參無返回值):

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}
複製程式碼

當呼叫這個函式時:

nonInlined {
    println("do something here")
}
複製程式碼

其實現的方式是建立一個Function例項,將lambda表示式中的內容包裝在其中,類似這樣的方式:

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});
複製程式碼

而使用inline的方式,不會建立Function例項,行內函數塊內呼叫周圍的程式碼會被拷貝到呼叫點,類似這樣的方式:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");
複製程式碼

因為沒有Function例項的建立,減少了系統開銷,提供了更好的效能。

9. 區域性委託屬性

在kotlin1.1中開始支援區域性委託屬性,那什麼是委託屬性呢?

有一些常見的屬性型別,雖然我們可以在每次需要的時候手動實現它們, 但是如果能夠只實現一次並放入一個庫會更好。

委託屬性的語法是:

val/var <屬性名>: <型別> by <表示式>。
複製程式碼

在 by 後面的表示式是該委託, 因為屬性對應的 get()(與 set())會被委託給它的 getValue() 與 setValue() 方法。 屬性的委託不必實現任何的介面,但是需要提供一個 getValue() 或setValue()函式。使用var宣告的屬性需要提供這兩個方法,使用val宣告的屬性,需要提供getValue()方法,一個自定義屬性委託的示例:

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}
複製程式碼

當我們對Example的p屬性進行訪問時,

val e = Example()
println(e.p)

e.p = "NEW"
複製程式碼

會有如下的輸出結果:

Example@33a17727, thank you for delegating ‘p’ to me!
NEW has been assigned to ‘p’ in Example@33a17727.
複製程式碼

10.攔截委託屬性繫結

對於屬性委託,可以使用provideDelegate操作符來攔截屬性與委託之間的繫結。比如想要在繫結控制元件之前,檢查屬性的名稱,可以通過委託屬性來實現。

fun main(args: Array<String>) {
    val customUI: CustomUI = CustomUI()
    
    println(customUI.text)
    println(customUI.image)
}

class CustomDelegate<T>(t: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 這裡委託了 ${property.name} 屬性"
    }
}

class ResourceLoader<T>(id: Int) {
    operator fun provideDelegate(thisRef: CustomUI, prop: KProperty<*>): CustomDelegate<T?> {
        println("這裡攔截了屬性與委託之間的繫結...")
        checkProperty(thisRef, prop.name)
        // 建立委託
        var t: T? = null
        return CustomDelegate(t)
    }

    private fun checkProperty(thisRef: CustomUI, name: String) {
        println(name)
    }
}

fun <T> bindResource(id: Int): ResourceLoader<T> {
    return ResourceLoader<T>(id)
}

class CustomUI {
    val image by bindResource<String>(10000)
    val text by bindResource<String>(20000)
}
複製程式碼

11.通用的列舉值訪問

在kotlin1.1中可以通過泛型的方式來列舉列舉中的值。比如:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}
複製程式碼

12.DSL隱式接受者作用域進行了限制

在kotlin1.1中,@DslMarker 註解對DSL隱式接受者的作用域進行了限制,這在kotlin1.0中是沒有涉及的。如Html構建器:

table {
     tr {
            td { + "Text" }
        }
}
複製程式碼

在 Kotlin 1.0 中,傳遞給 td 的 lambda 表示式中的程式碼可以訪問三個隱式接收者:傳遞給 table 、tr 和 td 的。這允許你呼叫在上下文中沒有意義 的方法,例如在 td 裡面呼叫 tr ,從而在 中放置一個 標籤。

在 Kotlin 1.1 中,限制這種情況,通過定義標記有 @DslMarker 元註解的註解並將其應用於標記類的基類,以使只有在 td 的隱式接收者上定義的方法會在傳給 td 的 lambda 表示式。

13.kotlin1.1中使用rem運算子替代了kotlin1.0中的mod運算子

即在kotlin1.1中表示式a%b對應的函式為a.rem(b)。

Kotlin1.1 對標準庫的擴充套件

1.字串到數字的轉換

在kotlin1.1中對String類進行了擴充套件,如String.toIntOrNull(): Int?當無效數字轉換為數字型別時,不再丟擲異常,類似的還有String.toDoubleOrNull(): Double?等,比如:

  val port = System.getenv("PORT")?.toIntOrNull()?: 80
複製程式碼

2.onEach()

onEach()會對集合及序列中的每個元素執行一次操作,使用方法與forEach()相似,但是forEach()函式只對每個元素執行給定的[action]。而onEach()函式對每個元素執行給定的[action],然後返回集合本身,比如:

inputDir.walk()
        .filter { it.isFile && it.name.endsWith(".txt") }
        .onEach { println("Moving $it to $outputDir") }
        .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) }
複製程式碼

3.also()、takeIf() 和 takeUnless()

also()使用方法與apply()相似,接收一個Receiver,執行一些動作,並且返回該接受者。區別是在also()內部可以使用it關鍵字,在apply()內部使用this關鍵字。示例:

fun Block.copy() = Block().also {
    it.content = this.content
}
複製程式碼

takeIf就像單個值的filter,它檢查接收者是否滿足該謂詞,並在滿足時返回該接收者否則不滿足時返回null。結合elvis-操作符使用,比如:

// 對現有的 outDirFile 做些事情
 val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false 
  // 對輸入字串中的關鍵字索引做些事情,鑑於它已找到
 val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
複製程式碼

takeUnless與takeIf相同,只是它採用了反向謂詞。當它不滿足謂詞時返回接收者,否則返回null。因此,上面的示例之一可以用takeUnless重寫如下:

 val index = input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")
複製程式碼

4.groupingBy()

可以用於按照鍵對集合進行分組,並同時摺疊每個組。例如,它可以用於計算文字中字元的頻率:

val frequencies = words.groupingBy { it.first() }.eachCount()
複製程式碼

5.Map.toMap() 和 Map.toMutableMap()

用於實現複製對映。

class ImmutablePropertyBag(map: Map<String, Any>) {
    private val mapCopy = map.toMap()
}
複製程式碼

6.Map.minus(key)

運算子 plus 提供了一種將鍵值對新增到只讀對映中以生成新對映的方法,但是沒有一種簡單的方法來做相反的操作:從對映中刪除一個鍵採用不那 麼直接的方式如 Map.filter() 或 Map.filterKeys() 。現在運算子 minus 填補了這個空白。有 4 個可用的過載:用於刪除單個鍵、鍵的集合、鍵 的序列和鍵的陣列。

 val map = mapOf("key" to 42)
val emptyMap = map - "key"
複製程式碼

7.minOf() 和 maxOf()

這些函式可用於查詢兩個或三個給定值中的最小和最大值,其中值是原生數字或 Comparable 物件。每個函式還有一個過載,它接受一個額外的 Comparator 例項。示例:

val list1 = listOf("a", "b")
val list2 = listOf("x", "y", "z")
val minSize = minOf(list1.size, list2.size)
val longestList = maxOf(list1, list2, compareBy { it.size })
複製程式碼

8.類似陣列的列表例項化函式

類似於 Array 建構函式,現在有建立 List 和 MutableList 例項的函式,並通過呼叫 lambda 表示式來初始化每個元素:

 val squares = List(10) { index -> index * index }
val mutable = MutableList(10) { 0 }
複製程式碼

9.Map.getValue()

Map 上的這個擴充套件函式返回一個與給定鍵相對應的現有值,或者丟擲一個異常,提示找不到該鍵。如果該對映是用 withDefault 生成的,這個函式將 返回預設值,而不是拋異常。

val map = mapOf("key" to 42)
// 返回不可空 Int 值 42
val value: Int = map.getValue("key")
val mapWithDefault = map.withDefault { k -> k.length } // 返回 4
val value2 = mapWithDefault.getValue("key2")
// map.getValue("anotherKey") // <- 這將丟擲 NoSuchElementException
複製程式碼

10.抽象集合

這些抽象類可以在實現 Kotlin 集合類時用作基類。對於實現只讀集合,有 AbstractCollection 、AbstractList 、AbstractSet 和 AbstractMap ,而對於可變集合,有 AbstractMutableCollection 、AbstractMutableList 、AbstractMutableSet 和 AbstractMutableMap。在JVM上,這些抽象可變集合從JDK的抽象集合繼承了大部分的功能。

11.陣列處理函式

標準庫現在提供了一組用於逐個元素操作的函式:比較( contentEquals和contentDeepEquals ),雜湊碼計算( contentHashCode和contentDeepHashCode )以及轉換為字串( contentToString和contentDeepToString )。 它們都支援JVM(它們作為java.util.Arrays的相應函式的別名)和JS(在Kotlin標準庫中提供實現)

fun main(args: Array<String>) {
    val array = arrayOf("a", "b", "c")
    println(array.toString())  // JVM implementation: type-and-hash gibberish
    println(array.contentToString())  // nicely formatted as list
}
複製程式碼

Kotlin1.1 其它新特性

JVM後端

Java 8 位元組碼支援

Kotlin 現在可以選擇生成 Java 8 位元組碼(命令列選項 -jvm-target 1.8 或者 Ant/Maven/Gradle 中的相應選項)。目前這並不改變位元組碼的語義 (特別是,介面和 lambda 表示式中的預設方法的生成與 Kotlin 1.0 中完全一樣)。

Java 8 標準庫支援

現在有支援在 Java 7 和 8 中新新增的 JDK API 的標準庫的獨立版本。如果你需要訪問新的 API,請使用 kotlin-stdlib-jre7 和 kotlin- stdlib-jre8 maven 構件,而不是標準的 kotlin-stdlib 。這些構件是在 kotlin-stdlib 之上的微小擴充套件,它們將它作為傳遞依賴項帶到專案中。

位元組碼中的引數名

Kotlin 現在支援在位元組碼中儲存引數名。這可以使用命令列選項 -java-parameters 啟用。

常量內聯

編譯器現在將 const val 屬性的值內聯到使用它們的位置。

可變閉包變數

用於在 lambda 表示式中捕獲可變閉包變數的裝箱類不再具有 volatile 欄位。此更改提高了效能,但在一些罕⻅的使用情況下可能導致新的競爭條件。 如果受此影響,需要提供自己的同步機制來訪問變數。

javax.scripting 支援

Kotlin 現在與javax.script AP(I JSR-223)整合。其 API 允許在執行時求值程式碼段:

val engine = ScriptEngineManager().getEngineByExtension("kts")!! engine.eval("val x = 3")
println(engine.eval("x + 2")) // 輸出 5
複製程式碼

kotlin.reflect.full

為 Java 9 支援準備,在 kotlin-reflect.jar 庫中的擴充套件函式和屬性已移動到 kotlin.reflect.full 包中。舊包( kotlin.reflect )中的名稱已棄用,將在 Kotlin 1.2 中刪除。請注意,核心反射介面(如 KClass )是 Kotlin 標準庫(而不是 kotlin-reflect )的一部分,不受移動影響。

JavaScript 後端

統一標準庫

Kotlin 標準庫的大部分目前可以從程式碼編譯成 JavaScript 來使用。特別是,關鍵類如集合( ArrayList 、HashMap 等)、異常( IllegalArgumentException 等)以及其他幾個關鍵類( StringBuilder 、Comparator )現在都定義在 kotlin 包下。在 JVM 平臺上,一些名稱是相應 JDK 類的型別別名,而在 JS 平臺上,這些類在 Kotlin 標準庫中實現。

更好的程式碼生成

JavaScript 後端現在生成更加可靜態檢查的程式碼,這對 JS 程式碼處理工具(如 minifiers、optimisers、linters 等)更加友好。

external 修飾符

如果你需要以型別安全的方式在 Kotlin 中訪問 JavaScript 實現的類,你可以使用 external 修飾符寫一個 Kotlin 宣告。(在 Kotlin 1.0 中,使用了@native 註解。)與 JVM 目標平臺不同,JS 平臺允許對類和屬性使用 external 修飾符。例如,可以按以下方式宣告 DOM Node 類:

external class Node {
    val firstChild: Node
    fun appendChild(child: Node): Node
    fun removeChild(child: Node): Node
// 等等 }
複製程式碼

改進的匯入處理

現在可以更精確地描述應該從 JavaScript 模組匯入的宣告。如果在外部宣告上新增 @JsModule("<模組名>") 註解,它會在編譯期間正確匯入到模 塊系統(CommonJS或AMD)。例如,使用 CommonJS,該宣告會通過 require(......) 函式匯入。此外,如果要將宣告作為模組或全域性 JavaScript 物件 匯入,可以使用 @JsNonModule 註解。例如,以下是將 JQuery 匯入 Kotlin 模組的方法:

external interface JQuery {
    fun toggle(duration: Int = definedExternally): JQuery
    fun click(handler: (Event) -> Unit): JQuery
}
@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery
複製程式碼

在這種情況下,JQuery 將作為名為 jquery 的模組匯入。或者,它可以用作 $-物件,這取決於Kotlin編譯器配置使用哪個模組系統。 你可以在應用程式中使用如下所示的這些宣告:

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}
複製程式碼

Kotlin 1.2 的新特性

在1.2的開發過程中,團隊花了很多精力來優化編譯系統,據官方提供的資料顯示,與Kotlin 1.1相比,Kotlin帶來了大約25%的效能提升,並且看到了可以進一步改進的巨大潛力,這些改進將在1.2.x更新中釋出。

Kotlin1.2 引入多平臺性

跨平臺專案是 Kotlin 1.2 中的一個新的實驗性功能,它允許開發者從相同的程式碼庫構建應用程式的多個層——後端、前端和Android應用程式,在這個跨平臺方案中,主要包含三個模組。

  1. 通用(common)模組:包含非特定於任何平臺的程式碼,以及不附帶依賴於平臺的 API 實現的宣告。
  2. 平臺(platform)模組:包含用於特定平臺的通用模組中與平臺相關宣告的實現,以及其他平臺相關程式碼。
  3. 常規(regular)模組:針對特定平臺,可以是平臺模組的某些依賴,也可以是依賴的平臺模組。

多平臺專案支援的一個主要特點是可以通過預期宣告與實際宣告來表達公共程式碼對平臺相關部分的依賴關係。一個預期宣告指定一個AP(I 類、介面、注 解、頂層宣告等)。一個實際宣告要麼是該 API 的平臺相關實現,要麼是一個引用到在一個外部庫中該 API 的一個既有實現的別名。

Kotlin1.2 引入的現代語言特性

1.註解中的陣列常量

從 Kotlin 1.2 開始,註解的陣列引數可以使用新的陣列常量語法而不是 arrayOf 函式來傳遞:

@CacheConfig(cacheNames = ["books", "default"]) //陣列常量語法被限制為註釋引數。
public class BookRepositoryImpl {
// ...... }
複製程式碼

2.lateinit 頂層屬性與區域性變數

lateinit 修飾符現在可以用在頂級屬性和區域性變數上。例如,當一個 lambda 作為建構函式引數傳遞給一個物件時,後者可以用於引用另一個必須稍後定義的物件:

class Node<T>(val value: T, val next: () -> Node<T>)
fun main(args: Array<String>) { // 三個節點的環:
   lateinit var third: Node<Int>
   val second = Node(2, next = { third })
   val first = Node(1, next = { second })
   third = Node(3, next = { first })
   val nodes = generateSequence(first) { it.next() }
   println("Values in the cycle: ${nodes.take(7).joinToString { it.value.toString() }}, ...")
}
複製程式碼

3.檢查 lateinit 變數是否已初始化

現在可以通過屬性引用的 isInitialized 來檢測該 lateinit var 是否已初始化:

class Foo {
    lateinit var lateinitVar: String
    
    fun initializationLogic() {
        println("isInitialized before assignment: " + this::lateinitVar.isInitialized)
        lateinitVar = "value"
        println("isInitialized after assignment: " + this::lateinitVar.isInitialized)    
    }
}
fun main(args: Array<String>) {
    Foo().initializationLogic()
}
複製程式碼

執行結果:

isInitialized before assignment: false
isInitialized after assignment: true
複製程式碼

4.行內函數帶有預設函式式引數

行內函數現在允許其內聯函式數引數具有預設值:

inline fun <E> Iterable<E>.strings(transform: (E) -> String = { it.toString() }) = 
map { transform(it) }

val defaultStrings = listOf(1, 2, 3).strings()
val customStrings = listOf(1, 2, 3).strings { "($it)" } 

fun main(args: Array<String>) {
    println("defaultStrings = $defaultStrings")
    println("customStrings = $customStrings")
}
複製程式碼

執行結果:

defaultStrings = [1, 2, 3]
customStrings = [(1), (2), (3)]
複製程式碼

5.源自顯式型別轉換的資訊會用於型別推斷

Kotlin編譯器現在支援通過強制轉換的資訊,來推斷出變數型別。如果你在呼叫一個返回“T”的泛型方法時,試圖將它的返回值“T”轉換為特定型別如“Foo”,編譯器現在知道這個方法呼叫中的“T”其實是“Foo”型別。這個對安卓開發者而言尤其重要,因為自從API26(Android7.0)開始,findViewById變成了泛型方法,然後編譯器也會正確分析該方法的呼叫返回值。比如下面這樣:

val button = findViewById(R.id.button) as Button
複製程式碼

6.智慧型別轉換改進

當一個變數有安全呼叫表示式與空檢測賦值時,其智慧轉換現在也可以應用於安全呼叫接收者:

fun countFirst(s: Any): Int {
    val firstChar = (s as? CharSequence)?.firstOrNull()
    if (firstChar != null)
    return s.count { it == firstChar } // s: Any is smart cast to CharSequence
    
    val firstItem = (s as? Iterable<*>)?.firstOrNull()
    if (firstItem != null)
    return s.count { it == firstItem } // s: Any is smart cast to Iterable<*>
    
    return -1
}
fun main(args: Array<String>) {
    val string = "abacaba"
    val countInString = countFirst(string)
    println("called on \"$string\": $countInString")
    
    val list = listOf(1, 2, 3, 1, 2)
    val countInList = countFirst(list)
    println("called on $list: $countInList")
}
複製程式碼

執行結果:

called on "abacaba": 4
called on [1, 2, 3, 1, 2]: 2
複製程式碼

智慧轉換現在也允許用於在 lambda 表示式中區域性變數,只要這些區域性變數僅在 lambda 表示式之前修改即可:

val flag = args.size == 0
var x: String? = null
if (flag) x = "Yahoo!"
run {
    if (x != null) {
println(x.length) // x 會智慧轉換為 String 
    }
}
複製程式碼

7.支援 ::foo 作為 this::foo 的簡寫

現在寫繫結到 this 成員的可呼叫引用可以無需顯式接收者,即 ::foo 取代 this::foo 。這也使在引用外部接收者的成員的 lambda 表示式中使 用可呼叫引用更加方便。

8.破壞性變更:try 塊後面的 sound smart casts

早些時候,Kotlin 使用了 try 塊中的賦值,以在塊之後進行 smart casts,這可能會破壞型別及 null 值的安全性並導致執行時失敗。這個版本修復了此問題,使 smart casts 更嚴格,但破壞了一些依賴這種 smart casts 的程式碼。

要切換到舊的 smart casts 行為,傳遞 fallback 標誌 -Xlegacy-smart-cast-after-try 作為編譯器引數。它將在 Kotlin 1.3 中被棄用。

9.棄用:資料類的覆寫性拷貝

當從已經具有相同簽名的拷貝函式的型別派生資料類時,為資料類生成的 copy 實現使用父型別的預設函式,會導致出現與預期相反的行為,如果父型別沒有預設引數,則在執行時失敗導致複製衝突的繼承已經被 Kotlin 1.2 中的警告所取代,並且在 Kotlin 1.3 中這將會提示是錯誤的。

10.棄用:列舉條目中的巢狀型別

在列舉項中,由於初始化邏輯中的問題,定義一個不是內部類的巢狀型別的功能已經被棄用。在 Kotlin 1.2 中這將會引起警告,並將在 Kotlin 1.3 中報錯。

11.棄用:vararg 單個命名引數

為了與註解中的陣列常量保持一致,在命名的表單(foo(items = i)) 中為 vararg 引數傳遞的單專案已被棄用。請使用具有相應陣列工廠函式的展開運算子:

  foo(items = *intArrayOf(1))
複製程式碼

在這種情況下,有一種優化可以消除冗餘陣列的建立,從而防止效能下降。單一引數的表單在 Kotlin 1.2 中會引起警告,並將在 Kotlin 1.3 中被移除。

12.棄用:擴充套件 Throwable 的泛型類的內部類

繼承自 Throwable 的泛型類的內部類可能會在 throw-catch 場景中違反型別安全性,因此已棄用,在 Kotlin 1.2 中會是警告,而在 Kotlin 1.3中會是錯誤。

13.棄用:改變只讀屬性的 backing 欄位

在自定義 getter 中通過賦值 field = ... 來改變只讀屬性的 backing 欄位已被棄用,在 Kotlin 1.2 中會被警告,在 Kotlin 1.3 中將會報錯。

Kotlin1.2 對標準庫的擴充套件

1.Kotlin 標準庫 artifacts 及拆分包

Kotlin 標準庫現在完全相容 Java 9 的模組系統,它會禁止對包進行拆分(多個 jar 包檔案在同一個包中宣告類)。為了支援這一點,引入了新的 artifacts kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8,取代了舊的 kotlin-stdlib-jre7 和 kotlin-stdlib-jre8。

新 artifacts 中的宣告從 Kotlin 的角度來看在相同的包名下可見的,但是對 Java 而言它們有不同的包名。因此,切換到新的 artifacts 不需要對原始碼進行任何更改。

確保與新模組系統相容的另一個更改是從 kotlin-reflect 庫中移除 kotlin.reflect 包中的棄用宣告。如果使用它們,則需要使用 kotlin.reflect.full 包中的宣告,自 Kotlin 1.1 以來該包是被支援的。

2.windowed、chunked、zipWithNext

Iterable, Sequence 和 CharSequence 的新擴充套件包含了諸如緩衝或批處理(chunked),滑動視窗和計算滑動平均值 (windowed)以及處理 subsequent item 對 (zipWithNext) 等用例:

fun main(args: Array<String>) {
    val items = (1..9).map { it * it }
    
    val chunkedIntoLists = items.chunked(4)
    val points3d = items.chunked(3) { (x, y, z) -> Triple(x, y, z) }
    val windowed = items.windowed(4)
    val slidingAverage = items.windowed(4) { it.average() }
    val pairwiseDifferences = items.zipWithNext { a, b -> b - a }
    
    println("items: $items\n")
    
    println("chunked into lists: $chunkedIntoLists")
    println("3D points: $points3d")
    println("windowed by 4: $windowed")
    println("sliding average by 4: $slidingAverage")
    println("pairwise differences: $pairwiseDifferences")
}
複製程式碼

執行結果:

items: [1, 4, 9, 16, 25, 36, 49, 64, 81]

chunked into lists: [[1, 4, 9, 16], [25, 36, 49, 64], [81]]
3D points: [(1, 4, 9), (16, 25, 36), (49, 64, 81)]
windowed by 4: [[1, 4, 9, 16], [4, 9, 16, 25], [9, 16, 25, 36], [16, 25, 36, 49], [25, 36, 49, 64], [36, 49, 64, 81]]
sliding average by 4: [7.5, 13.5, 21.5, 31.5, 43.5, 57.5]
pairwise differences: [3, 5, 7, 9, 11, 13, 15, 17]
複製程式碼

3.fill、replaceAll、shuffle/shuffled

新增了一系列擴充套件函式用於處理列表:針對 MutableList 的 fill, replaceAll 和 shuffle ,以及針對只讀 List 的 shuffled:

fun main(args: Array<String>) {
    val items = (1..5).toMutableList()
    
    items.shuffle()
    println("Shuffled items: $items")
    
    items.replaceAll { it * 2 }
    println("Items doubled: $items")
    
    items.fill(5)
    println("Items filled with 5: $items")
}
複製程式碼

執行結果:

Shuffled items: [5, 3, 1, 2, 4]
Items doubled: [10, 6, 2, 4, 8]
Items filled with 5: [5, 5, 5, 5, 5]
複製程式碼

4.kotlin-stdlib 中的數學運算

為滿足使用者長期以來的需求,Kotlin 1.2 中增加了用於數學運算的 kotlin.math API,也是 JVM 和 JS 的通用 API,包含以下內容:

  • 常量:PI 和 E
  • 三角函式:cos, sin, tan 及其逆函式 acos, asin, atan, atan2
  • 雙曲三角函式:cosh, sinh, tanh 及其逆函式 acosh, asinh, atanh
  • 指數函式:pow (擴充套件函式), sqrt, hypot, exp, expm1
  • 對數函式:log, log2, log10, ln, ln1p
  • Round 函式: ceil, floor, truncate, round (半值取偶)函式 roundToInt, roundToLong (半值取整)擴充套件函式
  • 符號函式和絕對值: abs 和 sign 函式 absoluteValue 和 sign 擴充套件屬性 withSign 擴充套件函式
  • 兩個數值的 max 和 min
  • 二分表示: ulp 擴充套件屬性 nextUp, nextDown, nextTowards 擴充套件函式 toBits, toRawBits, Double.fromBits (這些都位於 kotlin 包中)

同系列(但不包括常量)的函式也針對 Float 型引數提供了。

5.用於 BigInteger 與 BigDecimal 的操作符與轉換

Kotlin 1.2 引入了一組用於操作 BigInteger 和 BigDecimal 以及使用從其他數字型別進行轉換的函式。這些函式是:

  • 用於 Int 和 Long 型別的 toBigInteger
  • 用於 Int, Long, Float, Double, 和 BigInteger 型別的 toBigDecimal
  • 算術和位運算子函式: 二元運算子 +, -, *, /, % 和中綴函式 and, or, xor, shl, shr 一元運算子 -, ++, -- 和一個函式 inv

6.浮點數到位元的轉換

新增了新的函式,用於將 Double 和 Float 轉換成位表示形式:

  • toBits 和 toRawBits 對於 Double 型別返回 Long,而對於 Float 返回 Int
  • Double.fromBits 和 Float.fromBits 用於從位表示形式中轉換為浮點數

7.正規表示式現在可序列化

kotlin.text.Regex 類已成為可序列化的類,現在可以在可序列化的層次結構中使用。

8.如果滿足條件,Closeable.use 可以呼叫 Throwable.addSuppressed

在一些其他異常處理後,關閉資源期間丟擲異常時,Closeable.use 函式可呼叫 Throwable.addSuppressed。要啟用這個行為,你需要在你的依賴關係中包含 kotlin-stdlib-jdk7。

Kotlin1.2 其它新特性

JVM後端

建構函式呼叫標準化

自 1.0 以來,Kotlin 開始支援複雜控制流的表示式,例如 try-catch 表示式和行內函數呼叫。根據 Java 虛擬機器規範這樣的程式碼是合法的。不幸的是,當建構函式呼叫的引數中存在這樣的表示式時,一些位元組碼處理工具不能很好地處理這些程式碼。

為了減少使用此類位元組碼處理工具的使用者的這個問題,我們新增了一個命令列選項 (-Xnormalize-constructor-calls=MODE),它會告訴編譯器為這樣的結構生成更多的類 Java 位元組碼。這裡 MODE 的值是以下之一:

  • disable (預設值)—— 以和 Kotlin 1.0 和 1.1 相同的方式生成位元組碼
  • enable —— 為建構函式呼叫生成類 Java 位元組碼。這可以改變類載入和初始化的順序
  • preserve-class-initialization —— 為建構函式呼叫生成類 Java 位元組碼,以確保保持類初始化順序。這可能會影響應用程式的整體效能;僅在多個類之間共享一些複雜的狀態並在類初始化時更新時才使用它。

“手工”的解決方法是將控制流的子表示式的值儲存在變數中,而不是直接在呼叫引數中對它們進行求值。它類似於 -Xnormalize-constructor-calls=enable。

Java 預設方法呼叫

在 Kotlin 1.2 之前,介面成員在使用 JVM 1.6 的情況下重寫 Java 預設方法會在父呼叫中產生警告:Super calls to Java default methods are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'。在 Kotlin 1.2 中,這將會報錯,因此需要使用 JVM 1.8 來編譯這些程式碼。

破壞性變更:平臺型別的 x.equals(null) 一致行為

在對映到 Java 原語 (Int!, Boolean!, Short!, Long!, Float!, Double!, Char!) 的平臺型別上呼叫 x.equals(null) 時,如果 x 為 null,則會不正確地返回 true。從 Kotlin 1.2 開始,在平臺型別的空值上呼叫 x.equals(...) 會丟擲 NPE(但 x == ... 時並不會)。

要返回到 1.2 之前的行為,請將 -Xno-exception-on-explicit-equals-for-boxed-null 標誌傳遞給編譯器。

破壞性變更:通過內聯的擴充套件接收器修復平臺的 null 轉義

在平臺型別空值上呼叫的內聯擴充套件函式並沒有檢查接收器是否為 null,並因此允許 null 轉義到其他程式碼中。Kotlin 1.2 在呼叫點強制執行此檢查,如果接收方為空,則丟擲異常。

要切換到舊行為,請將 fallback 標誌 -Xno-receiver-assertions 傳遞給編譯器。

JavaScript 後端

預設啟用對型別化陣列(TypedArrays)的支援

S typed arrays 支援將 Kotlin 基本陣列(如 IntArray, DoubleArray)轉換為 JavaScript 的型別陣列,以前這是可選功能,現在預設情況下已啟用。

工具

將警告視為錯誤

編譯器現在提供了將所有警告視為錯誤的選項。在命令列中使用 -Werror,或使用以下的 Gradle 程式碼:

compileKotlin {
    kotlinOptions.allWarningsAsErrors = true
}
複製程式碼

學習資料

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans

相關文章