天天看點

預測一下 Kotlin 未來會有哪些新文法

前言

Kotlin 是一門開放的語言,不僅僅是源碼的開放,任意使用者都可以直接參與它的建設。大家可以通過 ​​YouTrack​​​ 向社群提出自己的 idea 和 issue ,其中一些呼聲高的 issue 會進入 ​​KEEP​​ 交由 Kotlin 團隊管理維護,并有可能被最終實作、出現在未來的某個版本中。

預測一下 Kotlin 未來會有哪些新文法
​​​YouTrack Hot Issues​​

為呼應 “2022技術趨勢” 征文活動(主要為了薅羊毛),本文将透過 YouTrack 中目前最熱門的 issue 大膽預測一下 Kotlin 未來可能引入的新功能:

  • 命名空間
  • 多接受者擴充函數
  • 集合字面值
  • 雙類型屬性
  • 名字解構
  • 異常多重捕獲
  • 包級通路

如果你喜歡這些功能,可以去 YouTrack 為它們點贊投票。

命名空間 (Namespace)

​​youtrack.jetbrains.com/issue/KT-11…​​

Java 可以通過類名調用 Class 的靜态方法或者靜态變量,這無形中提供了一種 namespace 機制,友善快速索引某個常量或方法。 Kotlin 雖然鼓勵開發者使用頂級方法替代 ​

​Util​

​ 類,但當這樣的頂級方法太多時,我們也希望有類似的 namespace 機制出現。

目前常見的 workaround 是使用 object 類替代靜态方法,例如

object DisplayUtil{
    fun dip2px(context:Context, px:Float) {
        ...
    }
}      

Kotlin 标準庫的 ​

​Delegates.notNull​

​ 、協程庫的 ​

​Dispatchers.Default​

​ 等都是這樣定義的,這種方式缺點是需要額外建立一個 object 對象,增加記憶體開銷。

也許未來你将看到一種 namespace 關鍵字,像下面這樣使用

namespace DiplayUtil {
    fun dip2px(context:Context, px:Float)
}      

namespace 可以避免執行個體對象的建立,将在位元組碼階段編譯成 jvm 靜态方法。

多接受者擴充函數 (Multiple receivers)

​​youtrack.jetbrains.com/issue/KT-10…​​

有時我們會在 Class 内定義擴充方法

class View { 
   val Float.dp // Float is an extension receiver for dp property
       get() = this * resources.displayMetrics.density
      // this refers to Float
      // resources, or [email protected], is a property in View
}      

這種擴充方法可以在 View 的内部使用,或者在外部借助 ​

​with​

​ 使用。

with (view) {
   42f.dp
}      

但實際情況是我們無法為像 View 這樣的三方庫中的類定義擴充方法,是以我們希望有一種能夠定義多 receiver 的擴充方法的機制。

未來有可能會引入 ​

​context​

​ 關鍵字,通過 ​

​context​

​ 可以定義兩個甚至多個 receiver

的擴充方法

context(View)
val Float.dp
   get() = this * resources.displayMetrics.density      

上述代碼等價于

context(View)
fun dp(view: View, float: Float) {
    float * view.resources.displayMetrics.density
}      

順便提一下,我更喜歡其中另一種建議的文法:

fun (View, Float).dp() = this@Float * [email protected]      

雖然由于不符合 Kotlin 的文法習慣,已經被否掉了。。

集合字面值(Collection literals)

​​youtrack.jetbrains.com/issue/KT-43…​​

很多現代語言(Python,JS,Go 等等)都支援使用字面值定義集合,例如在 Python 中我們可以這樣定義一個集合:

new_array = ["a", "b", "c"]      

字面值的定義方式更加直覺簡潔,這在重資料操作的語言中顯得非常重要。但 Kotlin 目前隻能使用 ​

​listOf​

​,​

​mapOf​

​,​

​setOf​

​,​

​intArrayOf​

​ 等 builder 方法進行定義,需要開發者記憶多個方法名,而且 ​

​varags​

​ 也存在一些使用上的隐患。

未來也許我們可以在 Kotlin 引入字面值定義:

val list = [1, 2, 3] // has type of List<Int>
val map = ["one": 1, "two": 2, "three": 3] // has type of Map<String, Int>      

以上字面值預設建立 Immutable 的 List 和 Map 類型集合,當然我們也可以在定義的同時明确指定集合類型:

val set: Set<Int> = [1, 2, 3] // creates a Set<Int> 
val map: MutableMap<String, Int> = ["one": 1, "two": 2, "three": 3] // creates a MutableMap      

數組可以使用下面方式建立,且可以實作類型推到,無需指定泛型

val array = Array [1, 2, 3] // creates Array<Int>, note that "Int" was inferred here      

字面值的使用場景不止局限于集合,甚至可以在 data class 等一些“類集合”場景中使用

data class Point(val x: Int, val y: Int)
fun drawLine(from: Point, to: Point)

// Normal Calling
drawLine(Point(1, 2), Point(3, 4))
// Calling with Collection literals
drawLine([1, 2], [3, 4])      

雙類型屬性 (public&private property type)

​​youtrack.jetbrains.com/issue/KT-14…​​

我們經常有這種需求:一個類的屬性對内使用 Mutable 類型友善使用,而對外隻想暴露 Immutable 類型,避免随意修改,提高安全性。

此時,通常需要我們分别定義兩個成員:

//List
private val _items = mutableListOf<Item>()
val item : List<Item> = _items

//LiveData
private val _list = MutableLiveData<List<News>>()
val list : LiveData<List<News>> get() = _items      

由于上述寫法備援,有人為了圖友善可能直接對外暴露 Mutable 類型,容易造成隐患。

未來 Kotlin 或許會引入下面的文法,屬性在定義時對外隻暴露其 Immutable 的抽象類型,保證安全性的同時,減少模闆代碼:

private val items = mutableListOf<Item>()
        public get(): List<Item>      

名字解構(Name based destructuring)

​​youtrack.jetbrains.com/issue/KT-19…​​

我們在通路 data class 或者 Pair、 Triple 等類型時,經常會使用解構文法

data class Address(
   val street: String,
   val city: String
)
val (myStreet, myCity) = myAddress      

目前 Kotlin 使用的基于位置的解構,這存在一定隐患。 比如上述代碼,如果某天我們修改了 ​

​Address​

​,插入了 ​

​postalCode​

​ 屬性:

data class Address(
   val street: String,
   val postalCode: String,
   val city: String
)      

此時,位置解構中的 ​

​myCity​

​ 将錯誤地擷取 ​

​postalCode​

​ 的值,編譯器卻無法及時發現問題。此外還有一個突出的問題,當 data class 有更多屬性時,位置結構隻能順序通路,、無法通路某個指定屬性而跳過中間的屬性。

未來 Kotlin 可能會增加基于名字的解構文法:

val (street = myStreet, city = myCity) = myAddress      

名字解構中 ​

​myCity​

​ 中的指派将不會發生上述錯誤,而且當有數量衆多的屬性時也可以更精準地進行指派。

異常多重捕獲 (Multi catch block)

​​youtrack.jetbrains.com/issue/KT-71…​​

Java 支援異常多重捕獲,即在一個 catch 塊中捕獲多種異常,而 Kotlin 每個 catch 隻能捕獲一種異常

fun loadData(path: String) {
  try {
    findDataLoader(path).loadXmlData()
  }
  catch (e: IOException) {
    println("Failed to load configuration from $path: ${e.message}")
  }
  catch (e: JDOMException) {
    println("Failed to load configuration from $path: ${e.message}")
  }
}      

比如上面例子,當我們加載一個 xml 并使用 jdom 對其進行解析時,我們隻希望捕獲解析相關的錯誤,忽略其它錯誤

fun loadData(path: String) {
  try {
    findDataLoader(path).loadXmlData()
  }
  catch (e: IOException) {
    println("Failed to load configuration from $path: ${e.message}")
  }
  catch (e: JDOMException) {
    println("Failed to load configuration from $path: ${e.message}")
  }
}      

上面這樣的寫法顯得比較備援,即便集中到 when 語句處理也會免不了增加一些模闆代碼。

未來 Kotlin 也許會像 Java 那樣支援下面這樣的寫法

fun loadData(path: String) {
   try {
       findDataLoader(path).loadXmlData()
   } catch (e: IOException | JDOMException) {
       println("Failed to load configuration from $path: ${e.message}")
   }
}      

包級通路(Package-private visibility)

​​youtrack.jetbrains.com/issue/KT-29…​​

Java 中提供了包級通路控制權限,但是 Kotlin 缺少對應的通路控制符。​

​internal​

​ 會使得整個 Module 都成為可見範圍。 Kotlin 團隊認為以前 Package 是劃分元件的單元,但如今 Module 成為了更常見的元件粒度,通路權限應該圍繞 Module 設計,是以才沒有設計包級通路權限。

雖然這麼說有一定道理,但是很多從 Java 轉到 Kotlin 的開發者仍然習慣于使用 Package 組織工程,包級通路權限将有助于更好地實作代碼隔離,是以不少人仍然希望追加相關能力。

package com.foo
import com.foo.detail.*;
class Public {
   private val impl = Detail()
}
// ...
package com.foo.detail
[email protected] class Detail {
// ...      
package com.fooimport com.foo.detail.*;class Public {   private val impl = Detail()}// ...package [email protected] class Detail {// ...複制代碼      

最後