Kotlin教程(一)基礎

胡奚冰發表於2018-03-20

寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學習Kotlin的同學。系列文章的知識點會以《Kotlin實戰》這本書中順序編寫,在將書中知識點展示出來同時,我也會新增對應的Java程式碼用於對比學習和更好的理解。

Kotlin教程(一)基礎
Kotlin教程(二)函式
Kotlin教程(三)類、物件和介面
Kotlin教程(四)可空性
Kotlin教程(五)型別
Kotlin教程(六)Lambda程式設計
Kotlin教程(七)運算子過載及其他約定
Kotlin教程(八)高階函式
Kotlin教程(九)泛型


Kotlin與Java互轉

Kotlin程式碼在編譯後都會轉成Java檔案,對於編寫的Kotlin,我們可以通過工具提前看到轉換後的Java程式碼,具體方式是: 在as中找到Tools>Kotlin>Show Kotlin Bytecode,然後點皮膚上的Decompile。

對於之前寫好的Java程式碼,我們也可以用工具轉換成Kotlin程式碼,方法是: Code > Convert Java File To Kotlin File

函式和變數

Hello, world!

學習就從如何用Kotlin編寫一個“Hollo World”開始吧!先看熟悉的Java:

public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
複製程式碼

然後那,是Kotlin的寫法:

fun main(args: Array<String>) {
    println("Hello, world!")
}
複製程式碼

可以看到Kotlin中:

fun 關鍵字用來宣告一個函式;

main 是方法名;

args: Array<String>表示引數,可以發現於java中先型別後變數名相反,Kotlin中是先變數名,然後:,然後是型別宣告。

Kotlin中沒有宣告陣列的特殊語法,而是用Array表示陣列,有點類似集合的感覺;

println代替了System.out.println,這是Kotlin標準庫給Java標準庫函式提供了許多語法更簡潔的包裝;

不知道你有沒有注意到;,Kotlin中省略了分號。

函式

熟悉Java的你可能會想返回值在哪裡那?怎麼沒有那?

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}
複製程式碼

: Int這裡就出現,表示返回值是一個int型別,那為什麼上面那個函式沒有寫那?其實上面那個函式也有返回值,返回值是空,也就是void,在Kotlin中其實是: Unit,而: Unit預設可以省略,所以就看不到返回值的宣告瞭。

同樣方法在對比Java的看下:

public static int max(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        } 
    }
複製程式碼

是不是發現了方法體中if的使用好像有區別? 在Kotlin中,if是表示式,而不是語句。語句和表示式的區別在於,表示式有值,並且能作為另一個表示式的一部分使用;而語句總是包圍著它的程式碼塊中的頂層元素,並且沒有自己的值。在Java中,所用的控制結構都是語句。而在Kotlin中,除了迴圈(for,do,do/while)以外大多數控制結構都是表示式。

表示式函式體

如果一個方法的函式體是由單個表示式構成的,可以用這個表示式作為完整的函式體,並且去掉花括號和return語句:

fun max(a: Int, b: Int) = if (a > b) a else b
複製程式碼

ps:在as中通過alt+enter可以喚起操作,提供了在兩種函式風格之間轉換的方法:"Convert to expression body"(轉換成表示式函式體)和 "Convert to block body"(轉換成程式碼塊函式體)

細心的你或許注意到此處的表示式函式體也沒有寫出返回型別,作為一門靜態型別的語言,Kotlin不是要求每個表示式都應該在編譯期具有型別嗎?事實上,每個變數和表示式都有型別,每個函式都有返回型別。但是對錶達式體函式來說,編譯器會分析作為函式體的表示式,並把它的型別作為函式的返回型別,即使沒有顯示得寫出來。這種縫隙通常被稱作型別推導

變數

在Java中變數的宣告是從型別開始的,就像這樣:

final String str = "this is final string";
int a = 12;
複製程式碼

但是在Kotlin中這樣是行不通的,因為許多變數宣告的型別都可以省略。所以在Kotlin中以關鍵字開始,然後是變數名稱,最後可以加上型別(也可以省略):

val str: String = "this is a final string"
var a = 12
複製程式碼

其中: String也是可以省略的,通過=右邊推匯出左邊變數的型別是String,就像a變數省略型別。

可變變數和不可變變數

宣告變數的關鍵字有兩個: val(來自value)——不可變引用。在初始化之後不能再次賦值,對應Java中final修飾符。 var(來自variable)——可變引用。這種變數的值可以被改變,對應Java中的普通變數。

雖然var表示可變,並且如上面看到的也省略的型別,乍一看似乎和js等指令碼語言類似,可以直接賦值另一種型別的值,比如這樣:

var a = 12
a = "string"//錯誤
複製程式碼

但實際上,這樣做是錯誤的,即使var關鍵字允許變數改變自己的值,但它的型別卻是改變不了的。此處a變數在首次賦值時就確定了型別,這裡的型別是Int,再次賦值String型別的值時就會提示錯誤,並且執行也會發生ClassCastException。

注意,儘管val引用自身是不可變的,但是它指向的物件可能是可變的,例如:

val languages = arrayListOf("Java")
languages.add("Kotlin")
複製程式碼

其實和Java中一致,final定義一個集合,集合中的資料是可以改變的。

###字串模板

val name = "HuXiBing"
println("Hello, $name!")
複製程式碼

這是一個Kotlin的新特性,在程式碼中,你申明瞭一個變數name,並且後面的字串字面值中使用了它。和許多指令碼語言一樣,Kotlin讓你可以在字串字面值中引用區域性變數,只需要在變數名稱前面加上字元$,這等價於Java中的字串連結 "Hello, " + name + "!" ,效率一樣但是更緊湊。 通過轉換成Java程式碼,我們可以看到這兩句程式碼其實是這樣的:

String name = "HuXiBing";
String var3 = "Hello, " + name + '!';
System.out.println(var3);
複製程式碼

當然,表示式會進行靜態檢查,如果你試著引用一個不存在的變數,程式碼根本不會編譯。 如果要在字串中使用$,你需要對它轉義:println("\$x")會列印$x,並不會吧x解釋成變數的引用。 還可以引用更復雜的表示式,而不是僅限於簡單的變數名稱,只需要把表示式用花括號括起來:

println("1 + 2 = ${1 + 2}")
複製程式碼

還可以在雙引號中直接巢狀雙引號,只要它們在某個表示式的範圍內(即花括號內):

val a = 12
println("a ${if (a >= 10) "大於等於10" else "小於10"}")
複製程式碼

類和屬性

先來看一個簡單的JavaBean類Person,目前它只有一個屬性:name。

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
複製程式碼

在Java中,構造方法的方法體常常包含完全重複的程式碼:它把引數賦值給有著相同名稱的欄位。在Kotlin中,這種邏輯不用這麼多的樣板程式碼就可以表達。 使用Convert Java File To Kotlin File將這個物件轉換成Kotlin:

class Person(val name: String)
複製程式碼

這種只有資料沒有其他程式碼的類通常被叫做值物件,許多語言都提供簡明的語法來宣告它們。 注意從Java到Kotlin的轉換過程中public修飾符消失了,在Kotlin中預設是public,所以可以省略它。

屬性

類的概念就是把資料和處理資料的程式碼封裝成一個單一的實體。在Java中,資料儲存在欄位中,通常還是私有的。如果想讓類的使用者訪問到資料,得提供訪問器方法:一個getter,可能還有一個setter。在Person類中你已經看到了訪問器的例子。setter還可以包含額外的邏輯,包括汗蒸傳給它的值、傳送關於變化的通知等。 在Java中,欄位和其訪問器的組合常常被叫做屬性,在Kotlin中,屬性時頭等的語言特性,完全代替了欄位和訪問器的方法。在類中宣告一個屬性和宣告一個變數一樣:使用val和var關鍵字。宣告成val的屬性是隻讀的,而var屬性是可變的。

class Person(
        val name: String,//只讀屬性,生成一個欄位和一個簡單的getter
        var isMarried: Boolean//可寫屬性:生成一個欄位、一個getter、一個setter
)
複製程式碼

看看轉換成Java的程式碼可能更清晰一點:

public final class Person {
   @NotNull
   private final String name;
   private boolean isMarried;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final boolean isMarried() {
      return this.isMarried;
   }

   public final void setMarried(boolean var1) {
      this.isMarried = var1;
   }

   public Person(@NotNull String name, boolean isMarried) {
      super();
      Intrinsics.checkParameterIsNotNull(name, "name");
      this.name = name;
      this.isMarried = isMarried;
   }
}
複製程式碼

簡單的說就是平時我們用程式碼模板生成的bean,在Kotlin中連模板都不需要使用了,編譯時會自動生成對應的程式碼。 在Java中使用應該比較熟悉了,是這個是這樣的:

Person person = new Person("HuXiBing", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
複製程式碼

生成的getter和setter方法都是在屬性名稱前加上get和set字首作為方法名,但是有一種例外,如果屬性時以is開頭,getter不會增加字首,而它的setter名稱中is會被替換成set。所以你呼叫的將是isMarried()。 而在Kotlin中使用是這樣的:

val person = Person("HuXiBing", true)     //呼叫構造方法不需要關鍵字new
println(person.name)    //可以直接訪問屬性,但呼叫的時getter
println(person.isMarried)
複製程式碼

在Kotlin中可以直接引用屬性,不在需要呼叫getter。邏輯沒有變化,但程式碼更簡潔了。可變屬性的setter也是這樣:在Java中,使用person.setMarried(false)來表示離婚,而在Kotlin中,可以這樣寫:person.isMarried = false

自定義訪問器

如果getter和setter方法中需要額外的邏輯,可以通過自定義訪問器的方式實現。例如現在有這樣一個需求:宣告一個矩形,它能判斷自己是否是一個正方形。不需要一個單獨的欄位來儲存這個資訊,因為可以隨時通過檢查矩形的長寬是否相等來判斷:

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {//宣告屬性的getter
            return height == width
        }
}
複製程式碼

屬性isSquare不需要欄位來儲存它的值,它只有一個自定義實現的getter,它的值是每次訪問屬性的時候計算出來的。還記得之前的表示式函式體嗎?此處也可以轉成表示式體函式:get() = height == width。 同樣的看下轉換成Java程式碼更好理解:

public final class Rectangle {
   private final int height;
   private final int width;

   public final boolean isSquare() {
      return this.height == this.width;
   }

   public final int getHeight() {
      return this.height;
   }

   public final int getWidth() {
      return this.width;
   }

   public Rectangle(int height, int width) {
      this.height = height;
      this.width = width;
   }
}
複製程式碼

Kotlin原始碼佈局:目錄和包

與Java類似,每一個Kotlin檔案都能以一條package語句開頭,而檔案中定義的所有宣告(類、函式及屬性)都會被放到這個包中。如果其他檔案中定義的宣告也有相同的包,這個檔案可以直接使用它們;如果包不相同,則需要匯入它們。和Java一樣,匯入語句放在問價你的最前面使用關鍵字import

package com.huburt.imagepicker

import java.util.Random
複製程式碼

Java中的包和匯入宣告:

package com.huburt.imagepicker;

import java.util.Random;
複製程式碼

僅僅省略了; 還有點不同的時Kotlin不區分匯入是類還是函式(是的Kotlin的函式可以單獨存在,不是一定要宣告在類中)。例如:

import com.huburt.other.createRandom
複製程式碼

com.huburt.other是包名,createRandom是方法名,直接定義在Kotlin檔案的頂層函式。 在Java中,要把類放在和包結構相匹配的檔案與目錄結構中。而在Kotlin中包層級機構不需要遵循目錄層級結構,但是不管怎樣,遵循Java的目錄佈局更根據包結構把原始碼檔案放到對應的目錄中是個更好的選擇,避免一些不期而遇的錯誤。

表示和處理選擇:列舉和When

宣告列舉

Kotlin中宣告列舉:

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE
}
複製程式碼

而Java中列舉的宣告:

enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE
}
複製程式碼

這是極少數Kotlin宣告比Java使用了更多關鍵字的例子(多了class關鍵字)。Kotlin中,enum是一個軟關鍵字,只有當它出現在class前面是才有特殊的意義,在其他地方可以把它當做普通的名稱使用,與此不同的是,class任然是一個關鍵字,要繼續使用名稱clazz和aClass來宣告變數。 和Java一樣,列舉並不是值得列表,可以給列舉類宣告屬性和方法:

enum class Color(val r: Int, val g: Int, val b: Int) {

    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 255),
    BLUE(0, 0, 255);

    fun rgb() = (r * 256 + g) * 256 + b
}
複製程式碼

列舉常量用的宣告構造的方法和屬性的語法與之前你看到的常規類一樣。當你宣告每個列舉常量的時候,必須提供該常量的屬性值。注意這個向你展示了Kotlin語法中唯一必須使用分號(;)的地方:如果要在列舉類中定義任何方法,就要使用分號把列舉常量列表和方法定義分開。

使用When處理列舉類

對於Java,通常使用switch來匹配列舉,例如這樣:

public String getColorStr(Color color) {
        String str = null;
        switch (color) {
            case RED:
                str = "red";
                break;
            case BLUE:
                str = "blue";
                break;
            case GREEN:
                str = "green";
                break;
            case ORANGE:
                str = "orange";
                break;
            case YELLOW:
                str = "yellow";
                break;
        }
        return str;
    }
複製程式碼

而Kotlin中沒有switch,取而代之的是when。和if相似,when是一個有返回值的表示式,因此我們寫一個直接返回when表示式的表示式體函式:

fun getColorStr(color: Color) =
        when (color) {
            Color.RED -> "red"
            Color.ORANGE -> "orange"
            Color.YELLOW -> "yellow"
            Color.GREEN -> "green"
            Color.BLUE -> "blue"
        }
//呼叫方法
println(getColorStr(Color.RED))
複製程式碼

上面的程式碼根據傳進來的color值找到對應的分支。和Java不一樣,你不需要在每個分支都寫上break語句(在Java中遺漏break通常會導致bug)。如果匹配成功,只有對應的分支會執行,也可以把多個值合併到同一個分支,只需要逗號(,)隔開這些值。

fun getColorStr(color: Color) =
        when (color) {
            Color.RED, Color.ORANGE, Color.YELLOW -> "yellow"
            Color.GREEN -> "neutral"
            Color.BLUE -> "cold"
        }
複製程式碼

如果覺得寫了太多的Color,可以通過匯入的方式省略:

import com.huburt.other.Color //匯入類
import com.huburt.other.Color.* //匯入列舉常量

fun getColorStr(color: Color) =
        when (color) {
            RED, ORANGE, YELLOW -> "yellow" //直接使用常量名稱
            GREEN -> "neutral"
            BLUE -> "cold"
        }
複製程式碼

在When結構中使用任意物件

Kotlin中的when結構比Java中switch強大的多。switch要求必須使用常量(列舉常量、字串或者數字字面值)作為分支條件。而when允許使用任何物件。我們使用這種特性來寫一個函式來混合兩種顏色:

fun mix(c1: Color, c2: Color) {
    when (setOf(c1, c2)) {
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
        else -> throw Exception("Dirty color")
    }
}
複製程式碼

setOf是Kotlin標準函式庫中一個方法,用於建立Set集合(無序的)。 when表示式把setOf(c1, c2)生成的set集合依次和所有的分支匹配,直到某個分支滿足條件,執行對應的程式碼(返回混合後顏色值或者丟擲異常)。 能使用任何表示式作為when的分支條件,很多情況下會讓你的程式碼既簡潔又漂亮。

使用不帶引數的When

你可能意識到上面的例子效率多少有些低。沒此呼叫這個函式的時候它都會建立一些Set例項,僅僅是用來檢查兩種給定的顏色是否和另外兩種顏色匹配。一般這不是什麼大問題,但是如果這個函式呼叫很頻繁,它就非常值得用另一種方式重寫。來避免建立額外的垃圾物件。

fun mixOptimized(c1: Color, c2: Color) =
        when {
            (c1 == Color.RED && c2 == Color.YELLOW) ||
                    (c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE

            (c1 == Color.BLUE && c2 == Color.YELLOW) ||
                    (c1 == Color.YELLOW && c2 == Color.BLUE) -> Color.GREEN
            
            else -> throw Exception("Dirty color")
        }
複製程式碼

如果沒有給when表示式提供引數,分支條件就是任意的布林表示式mixOptimized方法和上面那個例子做了一模一樣的事情,這種寫法不會穿件額外的物件。

智慧轉換

在Java中經常會有這樣一種情形:用父類申明引用一個子類物件,當要使用子類的某個方式時,需要先判斷是否是哪個子類,如果是的話在強轉成子類物件,呼叫子類的方法,用程式碼的話就是如下的情況:

class Animal {

}

class Dog extends Animal {
    public void dig() {
        System.out.println("dog digging");
    }
}

Animal a = new Dog();
if (a instanceof Dog) {
     ((Dog) a).dig();
}
複製程式碼

在Kotlin中,編譯器會幫你完成強轉的工作。如果你檢查過一個變數是某種型別,後面就不需要轉換它,就可以把它當做你檢查過的型別使用(呼叫方法等),這就是智慧轉換。

val d = Animal()
if (d is Dog) {
    d.dig()
}
複製程式碼

這裡is是檢查一個變數是否是某種型別(某個類的例項),相當於Java中的instanceof。可以看到d變數是一個Animal物件,通過is判斷是Dog後,無需強轉就能呼叫Dog的方法。 智慧轉換隻在變數經過is檢查且且之後不再發生變化的情況下有效。當你對一個類的屬性進行智慧轉換的時候,這個屬性必須是一個val屬性,而且不能有自定義的訪問器。否則,每次對屬性的訪問是否都能返回相同的值將無從驗證。

在Kotlin中用as關鍵字來顯示轉換型別(強轉):

val d = Animal()
val dog = d as Dog
複製程式碼

ps:其實只是省略強轉程式碼,個人感覺作用不是很明顯。

用When代替If

Kotlin和Java中if有什麼不同,之前已經提到過了。如if表示式用在適用Java三元運算子的上下文中:if (a > b) a else b (Kotlin)和a > b ? a : b(Java)效果一樣。Kotlin沒有三元運算子,因為if表示式有返回值,這一點和Java不同。 對於較少的判斷分支用if沒有問題,但是較多的判斷分支則用when是更好的選擇,有相同的作用,並且都是表示式,都有返回值。

程式碼塊作為If和When的分支

上面的例子滿足條件的分支執行只有一行程式碼,但如果某個分支中程式碼不止一行還如何處理那?當然是把省略的{}加上作為程式碼塊啦:

val a = 1
val b = 2
var max = if (a > b) {
   println(a)
   a
} else b

var max2 = when {
   a > b -> {
     println(a)
     a
  }
  else -> b
}
複製程式碼

程式碼塊中最後一個表示式就是結果,也就是返回值。 對比Java的程式碼:

int a = 1;
int b = 2;
int max;
if (a > b) {
   System.out.println(a);
   max = a;
} else {
    max = b;
}
//無法使用switch
複製程式碼

少了賦值操作,並且when的使用在多條件的情況下也更方便。是不是慢慢發現Kotlin的美妙了?

迴圈

While迴圈

Kotlin中whiledo-while迴圈與Java完全一致,這裡不再過多敘述。

迭代數字:區間和數列

Kotlin中有區間的概念,區間本質上就是兩個值之間的間隔,這兩個值通常是數字:一個起始值,一個結束值,使用..運算子來表示區間:

val oneToOne = 1 .. 10
複製程式碼

注意Kotlin的區間是包含的或者閉合的,意味著第二個值始終是區間的一部分。如果不想包含最後那個數,可以使用函式until建立這個區間:val x = 1 until 10 ,等同於val x = 1 .. 9

你能用整數區間做的最基本的事情就是迴圈迭代其中所有的值。如果你能迭代區間中所有的值,這樣的區間被稱作數列。 我們用整數迭代來玩Fizz-Buzz遊戲。遊戲玩家輪流遞增計數,遇到能被3整除的數字就用單詞fizz代替,遇到能被5整除的數字則用單詞buzz代替,如果一個數字是3和5的公倍數,你得說FizzBuzz。

fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -> "FizzBuzz"
    i % 5 == 0 -> "Buzz"
    i % 3 == 0 -> "Fizz"
    else -> "$i"
}

fun play() {
    for (i in 1..100) {
        print(fizzBuzz(i))
    }
}
複製程式碼

Kotlin中for迴圈僅以唯一一種形式存在,其寫法:for <item> in <elements>。 區間1 .. 100 也就是<elements>,因此上面這個例子遍歷了這個數列,並呼叫fizzBuzz方法。 假設想把遊戲變得複雜一點,那我們可以從100開始倒著計數,並且只計偶數。

for (i in 100 downTo 1 step 2) {
     print(fizzBuzz(i))
}
複製程式碼

這裡100 downTo 1是遞減的數列(預設步長是1),並且設定步長step為2,表示每次減少2。

迭代map

我們用一個列印字元二進位制表示的小程式作為例子。

    val binaryReps = TreeMap<Char, String>()//使用TreeMap讓鍵排序
    for (c in 'A'..'F') {//使用字元區間迭代從A到F之間的字元
        val binary = Integer.toBinaryString(c.toInt())//吧ASCII碼換成二進位制
        binaryReps[c] = binary//根據鍵c把值存入map
    }
    for ((letter, binary) in binaryReps) {//迭代map,把鍵和值賦給兩個變數
        println("$letter = $binary")
    }
複製程式碼

.. 語法不僅可以建立數字區間,還可以建立字元區間。這裡使用它迭代從A到F的所有字元,包括F。 for迴圈允許展開迭代中集合的元素(map的鍵值對),把展開的結果儲存到兩個獨立的變數中:letter是鍵,binary是值。 map中可以根據鍵老訪問和更新map的簡明語法。使用map[key]讀取值,並使用map[key] = value設定它們,而不需要地愛用get和put。這段binaryReps[c] = binary等價於Java中的binaryReps.put(c, binary);

你還可以使用展開語法在迭代集合的同時跟蹤當前項的下標。不需要建立一個單獨的變數來儲存下標並手動增加它:

    val list = arrayListOf("10", "11", "1001")
    for ((index, element) in list.withIndex()) {
        println("$index : $element")
    }
複製程式碼

使用in檢查集合和區間的成員

使用in運算子來檢查一個值是否在區間中,或者它的逆運算!in來檢查這個值是否不在區間中。下面展示瞭如何使用in來檢查一個字元是否屬於一個字元區間。

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'z'
fun isNotDigit(c: Char) = c !in '0'..'9'
複製程式碼

這種檢查字元是否是英文字元的技巧看起來很簡單。在底層,沒有什麼特別處理,依然會檢查字元的編碼是否位於第一個字母編碼和最後一個字母編碼之間的某個位置(a <= c && c <= z)。但是這個邏輯被簡潔地隱藏到了標準庫中的區間類實現。 in運算子合!in也適用於when表示式。

fun recognize(c: Char) = when (c) {
    in '0'..'9' -> "It's a digit!"
    in 'a'..'z', in 'A'..'z' -> "It's a letter!"
    else -> "I don't know..."
}
複製程式碼

Kotlin中的異常

Kotlin的異常處理語句基本形式與Java類似,除了不需要new關鍵字,並且throw結構是一個表示式,能作為另一個表示式的一部分使用。

 val  b = if (a > 0) a else throw Exception("description")
複製程式碼

try、catch、finally

和Java一樣,使用帶有catchfinally子句的try結構來處理異常,下面這個例子從給定的檔案中讀取一行,嘗試把它解析成一個數字,返回這個數字;或者當這一行不是一個有效數字時返回null

fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}
複製程式碼

Int? 表示值可能是int型別,也可能null,Kotlin獨特的null機制,不帶?標識的宣告無法賦值null,在之後的文章中會具體介紹。

和Java最大的區別就是throws子句沒有出現在程式碼中:如果用Java來寫這個函式,你會顯示地在函式宣告的後寫上throws IOException。你需要這樣做的原因是IOException是一個受檢異常。在Java中,這種異常必須顯示地處理。必須申明你的函式能丟擲的所有受檢異常。如果呼叫另外一個函式,需要處理這個函式的受檢異常,或者宣告你的函式也能丟擲這些異常。 和其他許多現在JVM語言一樣,Kotlin並不區分受檢異常和未受檢異常。不用指定函式丟擲的異常,而且可以處理也可以不處理異常。這種設計是基於Java中使用異常實踐做出的決定。經驗顯示這些Java規則常常導致許多毫無意義的重新丟擲或者忽略異常的程式碼,而且這些規則不能總是保護你免受可能發生的錯誤。

try作為表示式

Kotlin中try關鍵字就像if和when一樣,引入了一個表示式,可以把它的值賦給一個變數。例如上面這個例子也可以這樣寫:

fun readNumber(reader: BufferedReader): Int? =
        try {
            val line = reader.readLine()
            Integer.parseInt(line)
        } catch (e: NumberFormatException) {
            null
        } finally {
            reader.close()
        }
複製程式碼

如果一個try程式碼塊執行一切正常,程式碼塊中最後一個表示式就是結果。如果捕獲到了一個異常,相應的catch程式碼塊中最後一個表示式就是結果。

相關文章