[譯]Kotlin珍品 7 -Unit,Nothing,Any,null

翻譯說明: 翻譯水平有限,文章內可能會出現不許確甚至錯誤的理解,請多多包涵!歡迎批評和指正.每篇文章會結合本身的理解和例子,但願對你們學習Kotlin起到一點幫助.java

原文地址: [Kotlin Pearls 7] Unit, Nothing, Any (and null) android

原文做者: Uberto Barbinigit

如何利用kotlin的特殊類型

前言

若是你正好瀏覽了這篇文章,應該能夠明顯看出我是真的很喜歡kotlin這門語言.其中一個緣由是它的類型層次結構,它既很是容易使用,又很是強大.它還很是接近Java類型系統,所以互操做性很是好.github

想徹底掌握kotlin的類型層次結構的話,搞懂下面的幾種類型是如何工做的頗有必要.數據庫

下面咱們先詳細的看一下Unit,Noting,Any並跟它們相應的Java類作個對比.編程

最後咱們再思考一下,null,和以?結尾的類型是如何適配到這些類型的.安全

正文

  • Unit

    在kotlin中Unit類型至關於Java中的void.
    或者若是你更喜歡這樣理解的話,咱們說在Kotlin中Unit是全部沒有指定返回值函數(方法)的返回值.(例如println())bash

    fun whatIsLove(){
      println("Baby don't hurt me!")
    }
    複製代碼

    在Kotlin中咱們有兩種定義方法或者函數的方式:若是把上面的語法轉成Java,那麼就是聲明方式;若是不轉在Kotlin中那麼就是表達式.jvm

    如何區分函數聲明和函數表達式是很重要的基礎知識,搞清二者的關係和區別有助於咱們準確的閱讀別人的文章和描述咱們的代碼! ide


    咱們能夠重寫全部不帶返回值的函數聲明爲返回`Unit`的函數表達式:

    由於Kotlin中的方法若是不指定返回值的話也會至少有一個Unit返回值,因此全部方法都是表達式.那麼咱們在Kotlin中指出表達式的時候,大部分都是下面這種單行表達式
    fun whatIsLove(): Unit = println("Baby don't hurt me!")

    一般表達式的可讀性是優於聲明的,至少保證了方法代碼的長度.不論怎麼說方法代碼太長都不太可取.
    在Kotlin標準庫中 Unit被簡單的定義爲一個被object關鍵字修飾的Any的子類,帶着一個重載的toString()方法.

    在Kotlin中Any是類層次結構的頂級類,是全部類的父類.

    被object修飾符修飾的類會在類的初始化生成器中建立一個靜態的全局單例.

    public object Unit {
        override fun toString() = "kotlin.Unit"
    }
    複製代碼

    在Kotlin和Java混合開發的時候,Kotlin編譯器足夠聰明,能夠把任何返回void的結果值轉成Unit,對一些特殊的函數類型也是同樣支持.

    例如咱們能夠傳遞一個返回類型爲Consumer<Integer>的java lambda給接受一個(Int) -> Unit類型參數的函數使用.

    //Java
    public static Consumer<Integer> printNum = x -> System.out.println("Num " + x);
    //Kotlin
    fun callMe(block: (Int) -> Unit): Unit = (1..100).forEach(block)
    fun main(){
        callMe { Lambdas.printNum } //it works fine
    }
    複製代碼

    注意一個容易讓人迷惑的地方,在Java中還有Void類(注意是大寫)間接的關聯着關鍵字void而且這個類不能實例化.因此null是這個返回類型的惟一有效的值.

  • Nothing

    這裏須要大家打起精神來了,由於Nothing是這篇文章最複雜的一段.

    Nothing是一個其餘全部類的子類(包括final類)的class(不是一個object).可是有一個陷阱:它只有一個私有構造函數,因此沒法實例化Nothing對象.這個類的註釋也說的很清楚了.Nothing沒有實例,咱們能夠用Nothing去表示一個不存在的值.例如,若是一個方法返回Nothing,那麼就表明永遠不會返回(永遠throws一個異常).

    public class Nothing private constructor()
    複製代碼

    在電腦編程中,不論是何種返回類型,都有可能不返回任何值.由於方法可能出錯或者陷入一個無限的計算中.而Kotlin經過Nothing類型,讓這個特性更明確易懂.

    如今讓咱們看看Nothing有用的場景.

    • 第一個有用的地方就是TODO()函數.當咱們還不想明確一個實現的時候,這個方法就格外有用了.
      public inline fun TODO(): Nothing = throw NotImplementedError()

      你是否曾經想過它如何替代任何可能的返回類型?

      它之因此起做用,是由於Nothing是任何類的子類型。

      fun determineWinner(): Player = TODO() 
      //It compiles because Nothing is a subclass of Player
      複製代碼

      注意在Java中咱們這樣寫的話,是沒法經過編譯的

      static Void todo(){
        throw new RuntimeException("Not Implemented");
      }
      String myMethod(){   
          return todo(); //沒法編譯由於Void不是一個String
      }
      複製代碼
    • 第二個有用的地方是表明全部的空集合
      fun findAllPlayers(): List<Player> = emptyList()

      Kotlin標準庫中的EmptyList怎麼就成了List<Player>有效的返回類型了呢?讓咱們看一下實現來解答這個疑問.

      public fun <T> emptyList(): List<T> = EmptyList 
      //使用類型推斷的泛型函數
      object EmptyList : List<Nothing> ... //最終的協變
      複製代碼

      大家可能已經猜到了,由於Nothing類型的關係讓上面的方法成立.同理,emptyMap(),emptySet()也都是同樣的.

    • 第三個地方可能沒有上面兩個地方有表明性,可是仍然頗有用!

      假定你有一個可能會失敗的方法,例如從一個數據庫中讀取用戶的操做.

      當一個用戶不存在的時候,既簡單又合理的一個選擇是返回一個null.方法簽名差很少會想下面這樣:

      fun readUser(id: UserId): User? = …

      可是有些時候咱們但願拿到更多的錯誤信息,好比說究竟是哪裏出錯了(連接失敗?表不存在?等等),而後讓調用者根據不一樣的錯誤去作不一樣的邏輯處理.
      這個時候Nothing就能夠出來完美的處理這個需求了.一個聰明的方法是提供一個可能發生錯誤的回調,並返回Nothing.

      inline fun readUser(id: UserId, onError: (DbError) -> Nothing): User = …

      它是如何工做的?讓咱們看下面的完整例子:

      fun createUserPage(id: UserId): HtmlPage {
          val user = readUser(id) { err ->
              when (err) {
                  is InvalidStatement -> //錯誤一的處理
      return@createUserPage throw Exception(err.parseError)
                  is UserNotFound -> //錯誤二的處理
      return@createUserPage HtmlPage("No such user!")
              }
          }
          return HtmlPage("User ${user.name}") //沒報錯的狀況
      }
      複製代碼

      lambda表達式中不容許使用不帶標籤的return語句(由於lambda中return是退出外圍的函數,這叫non-local return(非局部返回),但若是lambda是傳入到一個inline函數中,則能夠return.這個示例中,readUser是一個內聯函數.

      最後讓咱們經過一個例子來幫助咱們區分UnitNothing:

      fun fooOne(): Unit {
          while (true) {
          }
      }
      
      fun fooZero(): Nothing {
          while (true) {
          }
      }
      //兩個方法都能經過編譯
      
      fun barOne(): Unit {
          println("hi")
      }
      
      fun barZero(): Nothing {
          println("hi")
      }  //error
      //最後的方法編譯報錯
      複製代碼

      Nothing是Unit的子類,因此上面兩個都能編譯經過.而最後一個不行.

  • Any

    在Kotlin中Any是頂層層級.全部其餘的類型都繼承與它.

    在Any類的註釋中寫到:"Kotin類層級的頂層類.是全部Kotlin class的父類."

    它跟Java裏面的Object很像,可是方法簽名要精簡一點:

    public open class Any {
        public open operator fun equals(other: Any?): Boolean
        public open fun hashCode(): Int
        public open fun toString(): String
    }
    複製代碼

    Any一比,java.lang.Object有11個方法,其中還有5個是同步方法.(wait,notify,notifyAll).這是一個後續版本明顯能夠優化的地方.可能在以前的時候看起來是個好主意,可是如今讓同步原語在任何可能的對象上就不是那麼必要了.

    看一下Any類的代碼,咱們發現它是Kotlin標準庫中幾個很少被open修飾的類.這意味着咱們能夠直接繼承它.open在這也是必要的,由於全部Kotlin class會自動繼承Any,也就是說咱們不顯示的指定父類的時候也會默認繼承Any.

    在JVM層,並不存在Any類型,編譯器會把它轉成java.lang.Object.生成下面的字節碼,即便你不太熟悉這種格式,你也能發現參數的實際類型(加粗的).

    fun whatIcanDoWithAny(obj: Any){
        obj.toString()
    }
    
    public final static whatIcanDoWithAny(Ljava/lang/Object;)V
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
       L0
        ALOAD 0
        LDC "obj"
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 5 L1
        ALOAD 0
        INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;
        POP
    複製代碼

    因此即便咱們在Kotlin中不能直接調用Any中缺失的方法(好比說notify.wait這些),可是實際上全部Kotlin的對象都是有這些方法的.

    這讓傳遞任何Kotlin的對象,給任何須要Object類型參數的java方法當參數,變爲可能.

    最後,注意一個地方.Any類實現了 Java Object 類裏面的 equals 操做符.因此,equals == 是Kotlin中惟一須要被重載的操做符而且你不能改變方法簽名.例如,返回一個不同的類型來代替Boolean.

  • null

    null放在最後說是由於它很特殊.由於它並非一個類型但又能夠跟任意類型合用(類型後面加上?).

    String?就是一個組合類型,String+null.因此咱們知道這是一個可空的String類型.經過這種方式,在Koltin的智能轉換和特殊操做符的幫助下,咱們能夠輕鬆的處理空安全問題.

    那麼上面的提到的特殊類型呢?Unit?Nothing?Any?這些也都是能夠用的:

    • Unit? 容許返回一個聲明 或者 null.我不知道它有什麼有趣的用途.
    • Nothing? 比較特殊 由於這是讓一個方法只能返回 null 的返回類型.據我所知,目前還不知道這個的實際用途.
    • Any? 這個就很重要了.由於它至關於Java的Object,而且在Java是能夠返回null的.還有當你聲明一個泛型類,假設MyClass<T>,跟Java同樣,這個T類型的隱示約束就是Any?.若是你想限制這個泛型類不能爲可空類型,你就必須顯示的指定:MyClass<T:Any>

    下面是一個用擴展方法重寫的map和flatmap方法,讓它們能夠支持全部kotlin中可空的類型

    fun <A:Any, B:Any> A?.map(f: (A) -> B): B? = when(this) {
        null -> null
        else -> f(this)
    }
    fun <A:Any, B:Any> A?.flatMap(f: (A) -> B?): B? = when(this) {
        null -> null
        else -> f(this)
    }
    複製代碼

結尾

完整的代碼示例和更多的代碼都能在做者的GitHub上瀏覽下載項目倉庫

我但願大家能喜歡這篇文章,若是喜歡的話 請關注原做者或者在掘金關注我並給我點個贊吧.

相關文章
相關標籤/搜索