Kotlin的特色及各版本新特性

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

Kotlin語言的特色

Kotlin語義是一座小島,是一種在Java虛擬機上運行的靜態類型編程語言,Kotlin的目的就是要兼具現代編程語言的全部優勢,同時還要具備Java語言的跨平臺性,而且要作到簡潔。它也能夠被編譯成爲JavaScript源代碼。Kotlin與Java 100%兼容,可以運行在Android平臺和瀏覽器上。前端

Kotlin的應用場景

1.Kotlin Script 在IntellJ Idea中建立後綴爲.kts的腳本文件,能夠直接運行。Gradle在3.0以後部分支持Kotlin做爲其腳本語言。java

2.Java虛擬機應用 常見的Web應用,JavaFx應用,Kotlin都完美支持。Kotlin很是適合開發服務器端應用程序,容許編寫簡潔和富有表現力的代碼,同時保持與現有基於Java的技術堆棧的徹底兼容性和平滑的學習曲線;node

3.前端開發 Kotlin從1.1版本開始,Kotlin能夠編譯成JavaScript代碼,運行在瀏覽器上。目前Kotlin已經支持ECMAScript 5.1,有計劃最終支持ECMAScript 2015。jquery

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
相關文章
相關標籤/搜索