快速入門 Kotlin 編程

1.變量與函數

  • val:用於聲明不可變的變量,這種變量在初始賦值以後就不再能從新賦值,對應 Java 中的 final 變量。
  • var:用於聲明一個可變的變量,這種變量在初始賦值以後仍然能夠再被從新賦值,對應 Java 中的非 final 變量。

1.1 使用 val

fun main() {
    val a = 10
    println("a = " + a)
}
複製代碼

運行結果:數據庫

Kotlin 在賦值時會進行自動推導,能夠根據值的類型推導出變量的類型,若是使用下面這種延遲賦值的方式,那麼 Kotlin 將沒法推到值得類型,這樣程序就變報錯編程

fun main() {
    val a: Int = 10
    println("a = " + a)
}
複製代碼

1.2 使用 var

因爲上面使用的是不可變的變量,因此想要更改變量的值就會報錯,因此須要將 val 改爲 var 類型數組

fun main() {
    var a: Int = 10
    a = a * 10
    println("a = " + a)
}
複製代碼

總結:永遠優先使用 val 來聲明變量,當 val 沒法知足你的需求時再使用 var,這樣設計出來的程序更加健壯,也更加符合高質量的編碼規範。bash

1.3 使用函數

/**
 * 建立一個有兩個參數的 Int 返回類型的方法
 */
fun methodName(param1: Int, param2: Int): Int {
    return 0
}
複製代碼
fun main() {
    val a = 37
    val b = 40
    val value = largerNumber(a, b)
    println("larger number is " + value)
}

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int): Int {
    return max(param1, param2)
}
複製代碼

1.3.1 使用 Kotlin 語法糖

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int): Int = max(param1, param2)
複製代碼

進一步簡化版:服務器

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int) = max(param1, param2)
複製代碼

2.程序的邏輯控制

2.1 if 條件語句

Kotlin 中的條件語句有 if 和 when,其中 if 和 Java 中的 if 沒有區別,這裏簡單瞭解一下。架構

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int): Int {
    var value = 0
    if (param1 > param2) {
        value = param1
    } else {
        value = param2
    }
    return value
}
複製代碼

2.1.1 if 的另外一個用法

Kotlin 中的 if 用法和 Java 相比有一個額外的功能,它能夠有返回值,返回值就是 if 語句每個條件中最後一行代碼的返回值,所以能夠進行以下格式的書寫:ide

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int): Int {
    val value = if (param1 > param2) {
        param1
    } else {
        param2
    }
    return value
}
複製代碼

在這裏因爲 value 只須要進行一次賦值,因此能夠將 var 更改成 val函數

進一步簡寫:工具

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int): Int {
    return if (param1 > param2) {
        param1
    } else {
        param2
    }
}
複製代碼

再一次精簡:學習

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int) = if (param1 > param2) {
    param1
} else {
    param2
}
複製代碼

或者

/**
 * 對比 param1 和 param2 返回較大的那個數
 */
fun largerNumber(param1: Int, param2: Int) = if (param1 > param2) param1 else param2
複製代碼

2.2 when 條件語句

Kotlin 中的 when 語句有點相似於 Java 中的 switch 語句,可是比 switch 更加精簡。

使用格式:匹配值 -> {執行邏輯}

/**
 * 經過名字返回分數
 */
fun getScore(name: String) = if (name == "Tom") {
    86
} else if (name == "Jim") {
    77
} else if (name == "Jack") {
    95
} else if (name == "Lily") {
    100
} else {
    0
}

/**
 * 使用 when 語句實現經過名字返回分數
 */
fun getScore(name: String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    "Lily" -> 100
    else -> 0
}
複製代碼

注意:Java 中的 switch語句支持的類型有限,再 JDK1.7 中支持了字符串類型,可是有些類型卻仍然不支持,可是 when 語句卻解決了以上 痛點。

2.2.1 使用 when 語句進行類型匹配

/**
 * 判斷傳入的 number 是什麼數據類型
 */
fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("number is Int")
        is Double -> println("number is Double")
        else -> println("number not support")
    }
}
複製代碼

上述代碼中,is關鍵字是匹配類型的核心,它至關於 Java 中的 instanceof 關鍵字。因爲 checkNumber() 函數接收一個 Number 類型的參數,這是 Kotlin 中內置的抽象類,好比 Int、Double、Float、Long 都屬於它的子類。

2.2.2 when 語句的不經常使用用法

/**
 * 使用 when 表達式實現經過名字返回分數
 */
fun getScore(name: String) = when {
    name == "Tom" -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    name == "Lily" -> 100
    else -> 0
}
複製代碼

一般 when 語句的括號裏都是有參數的,若是不在括號裏寫參數就要再匹配項前面添加參數。

3.循環語句

在 Java 中提供了 for、while 新歡,在 Kotlin 中一樣也提供了這兩種循環,其中 while 循環沒有一點差別,因此這裏直接講解 for 循環。

3.1 使用 Kotlin 中的 for-in 循環

在使用循環以前先說明一下如何聲明區間,例如在 Kotlin 中聲明 [0, 10] 之間的區間使用val range = 0..10的形式,其中 .. 是建立兩端閉區間的關鍵字。

val range = 0..10
for (i in range) {
    println(i)
}
複製代碼

若是想聲明[0, 10)這個區間可使用 until 替代 ..

for (i in 0 until 10) {
    println(i)
}
複製代碼

默認狀況下,i 會每次自增 1,若是想讓 i 一次加 2 的話可使用 step 2實現,3,4,5.。。n 也是一樣的道理。

for (i in 0 until 10 step 2) {
    println(i)
}
複製代碼

注意:在進行遍歷時左邊的數值必須小於右邊的數值,若是想實現降序的話要使用downTo替代。

for (i in 20 downTo 10 step 2) {
    println(i)
}
複製代碼

4.面向對象編程

4.1 類和對象

/**
 * 建立 Person 實體類,因爲須要建立對象後再給屬性賦值,
 * 因此這裏使用 var 而不是 val
 */
class Person {
    var name = ""
    var age = 0
    fun eat() {
        println(name + " is eating. He is " + age + " years old")
    }
}

fun main() {
    val p = Person()
    p.name = "Jack"
    p.age = 19
    p.eat()
}
複製代碼

在 Kotlin 中取消了 new 關鍵字,由於調用構造函數就是爲了實例化,因此進行了精簡。

4.2 繼承和構造函數

若是定義一個學生類他的裏面會包含如學號、年級等屬性,但學生也是人,也須要姓名、年齡等屬性,若是再從新添加姓名和年齡屬性會有冗餘代碼。因此這裏可使用繼承的概念,這樣Student類就自動擁有了Person類的屬性。

4.2.1 建立學生類

class Student {
    var sno = ""
    var grade = 0
}
複製代碼

要是想繼承 Person 類,必須讓 Person 類具備能夠被繼承的能力,這也是 Kotlin 與 Java 不一樣的地方,這麼設計的緣由和 val 的設計理念時相同的,由於若是一個類能夠隨便被繼承就有可能會產生風險,在 Effective Java 一書中就指出,若是一個類不是專門爲繼承而設計的,那麼就應該主動加上 final 關鍵字,禁止它能夠被繼承。

很明顯 Kotlin 在設計時就遵循了這個規範,默認全部非抽象類時不能夠被繼承的,之因此一直說非抽象類,是由於抽象類自己是沒法建立實例的,必定要由子類去繼承它才能夠建立實例,所以抽象類必需要被繼承,不然就沒有意義了。

在 Kotlin 中要想讓一個類有被繼承的能力,只須要在類前面添加 open 關鍵字。

open class Student {
    var sno = ""
    var grade = 0
}
複製代碼

4.2.2 繼承 Person 類

/**
 * 建立 Person 實體類,因爲須要建立對象後再給屬性賦值,
 * 因此這裏使用 var 而不是 val。
 * 添加 open 讓類能夠被繼承
 */
open class Person {
    var name = ""
    var age = 0
    fun eat() {
        println(name + " is eating. He is " + age + " years old")
    }
}

/**
 * Kotlin 中的繼承與 Java 不一樣,Java 中使用 extends 關鍵字,
 * 在 Kotlin 中使用 : 代替,被繼承的類必需要調用它的構造函數,
 * 不然會報錯
 */
class Student : Person() {
    var sno = ""
    var grade = 0
}
複製代碼

在 Kotlin 中每一個類都默認自帶一個無參的主構造函數(在 Kotlin 中有主構造函數和次構造函數之分),你也能夠主動的指明參數,主構造函數是最經常使用的構造函數,它沒有函數體,直接定義在類名後面便可。

4.2.3 使用主構造函數

class Student(val sno: String, val grade: Int) : Person() {}

val student = Student("a123", 5)
複製代碼

構造函數的參數直接寫在類後面便可,若是想在主構造函數中編寫一些邏輯的話,可使用 init 聲明結構體,

class Student(val sno: String, val grade: Int) : Person() {
    // 將主構造函數的邏輯寫在 init 結構體中
    init {
        println("sno is " + sno)
        println("grade is " + grade)
    }
}

val student = Student("a123", 5)
複製代碼

這樣書寫後能夠在初始化 Student 類時打印 snograde 的值,這裏的一個規範與 Java 中相同,就是在初始化子類時必須調用父類的構造函數。可是這麼寫會調用父類的哪一個構造方法呢,這取決於 Person() 中的括號中有幾個參數,這裏沒有傳入參數,因此會調用父類的無參構造函數。

將 Person 和 Student 的構造函數進行一下修改

open class Person(val name: String, val age: Int) {

}

class Student(val sno: String, val grade: Int, name: String, age: Int)
    : Person(name, age) {
}

val student = Student("a123", 5, "Jack", 19)
複製代碼

注意:在 Student 的主構造函數中添加 name 和 age 字段時,不能再將它們聲明爲 val,由於在主構造函數中聲明成 val 或者 var 的參數會自動成爲該類的字段,這回致使和父類中同名的 name 和 age 字段形成衝突,所以在這裏的 name 和 age 前面不須要加任何關鍵字,讓它的做用域僅限定在主構造函數中便可。

4.2.4 使用次構造函數

Kotlin 提供了一個給函數設定參數默認值的功能,基本上能夠替代次構造函數的做用,可是考慮到知識結構的完整性,仍是說一下此構造函數的相關知識並探討一下括號的問題在次構造函數上的區別。

一個類只能有一個主構造函數,可是能夠有多個次構造函數,次構造函數也能夠用於實例化一個類,這一點和主構造函數沒有什麼不一樣,只不過它有函數體

Kotlin 規定,當一個類既有主構造函數又有次構造函數時,全部的次構造函數都必須調用主構造函數(包括間接調用),這裏經過一個例子進行簡單的闡明。

class Student(val sno: String, val grade: Int, name: String, age: Int)
    : Person(name, age) {
    constructor(name: String, age: Int) : this("", 0, name, age) {
    }

    constructor() : this("", 0){
    }
}
複製代碼

這裏定義了兩個次構造函數,第一個次構造函數接收 name 和 age 參數,而後又經過 this 調用主構造函數,並將 snograde 參數賦值,第二個次構造函數不接收任何參數,經過 this 調用了上面的次構造函數,並將 nameage 參數也成功進行了賦值,因爲第二個次構造函數間接的調用了主構造函數,因此這也是合法的。

這麼寫完以後就擁有了三種初始化 Student 類的方式

val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)
複製代碼

在一個類中顯式的設置了次構造函數而且沒有顯式的設置主構造函數,此時是沒有主構造函數的,這種操做在 Kotlin 中是容許的。

class Student : Person {
    constructor(name: String, age: Int) : super(name, age) {

    }
}
複製代碼

這裏的 Student 類的後面沒有顯式的定義主構造函數,同時又由於定義了次構造函數,因此如今 Student 類是沒有主構造函數的,那麼在繼承 Person 類是就不須要再添加括號了,另外因爲沒有主構造函數,次構造函數只能顯式的調用父類的構造函數,因此能夠將 this 換成 super

4.3 接口

Kotlin 中的接口和 Java 幾乎徹底同樣,咱們都知道 Java 是單繼承結構的語言,任何一個類最多隻能繼承一個父類,可是卻能夠實現多個接口,Kotlin 也是如此。咱們能夠定義一系列抽象行爲,而後由具體的類去實現。下面仍是經過代碼進行演示。

4.3.1 使用接口

interface Study {
    fun readBooks()
    fun doHomework()
}
複製代碼

讓 Student 類實現 Study 接口

class Student(name: String, age: Int) : Person(name, age), Study {
    override fun readBooks() {
        println(name + " is reading.")
    }

    override fun doHomework() {
        println(name + " is doing homework")
    }
}
複製代碼

在 Java 中實現接口使用 implements 關鍵字,在 Kotlin 中不管是繼承仍是實現接口都是用 「:」 替代,中間使用逗號(,)隔開便可,另外在實現接口時不須要在接口後面加括號,由於接口沒有構造函數。

在 main 方法中調用方法

fun main() {
    val student = Student("Jack", 19)
    doStudy(student)
}

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}
複製代碼

4.3.2 對接口中的函數默認實現

interface Study {
    fun readBooks()
    fun doHomework() {
        println("do homework default implementation.")
    }
}
複製代碼

若是像以前那麼寫,在實現接口時裏面的兩個方法都必須實現,若是改爲這樣的話,只須要強制實現 readBooks() 函數了,doHomework()能夠選擇寫或者不寫,不寫的話則會打印do homework default implementation.

4.3.3 訪問修飾符

在 Java 中一共由 public、private、protected、default(什麼都不寫)這四種修飾符,在 Kotlin 中有 public、private、protected、internal 這四種修飾符,想要使用那種修飾符時直接將修飾符寫在 fun 前面便可。

首先 private 修飾符在兩種語言中的做用如出一轍,都表示只對當前類內部可見,public 修飾符的做用也是一致的,標識對全部類可見,可是在 Kotlin 中 public 修飾符是默認項,而在 Java 中是 default,前面書寫的函數都沒有加訪問修飾符,那麼這些函數的訪問權限所有是 public。protected 在 Java 中表示對當前類,子類和同一個包路徑下的類可見,在 Kotlin 中則表示只對當前類和子類可見。Kotlin 拋棄了 Java 中的 default 可見性(同一包路徑下的類可見)。引入了一種新的可見性概念,只對同一模塊中的類可見,使用的是 internal 修飾符。

好比咱們開發了一個模塊給別人使用,可是有一些函數只容許在模塊內部調用,不想暴露給外部,就能夠將函數聲明爲 internal修飾的。

Java 和 Kotlin 可見性修飾符對照表

4.4 數據類和單例類

在一個規範的系統中,數據類一般佔據者很是重要的角色,它們用於將服務器端或數據庫中的數據映射到內存中,爲編程邏輯提供數據模型的支持。其中經常使用的 MVC、MVP、MVVM 這些架構模式中的 M 值得就是數據類。

4.4.1 Java 中的數據類

在 Java 中數據類須要重寫 equals()hashCode()toString()方法,其中equals()用於判斷兩個數據類是否相等,hashCode()equals() 方法配套使用,toString()方法可讓輸出打印更加清晰。

public class Cellphone {
    String brand;
    double price;

    public Cellphone(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Cellphone) {
            Cellphone other = (Cellphone) obj;
            return other.brand.equals(brand) &&
                    other.price == price;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return brand.hashCode() + (int) price;
    }

    @Override
    public String toString() {
        return "Cellphone(brand=" + brand + ", price" + price + ")";
    }
}
複製代碼

4.4.2 Kotlin 中的數據類

data class Cellphone(val brand: String, val price: Double)
複製代碼

在 Kotlin 中只須要這一行代碼便可,其中神奇的地方在於 class 前面的 data 關鍵字,有了這個關鍵字就代表咱們想要聲明一個數據類,Kotlin 會根據主構造函數中的參數幫你將 equals()hashCode()toString()方法自動生成,從而減小了開發的工做量。

編寫 main 函數進行測試

fun main() {
    val cellphone1 = Cellphone("Samsung", 1299.99)
    val cellphone2 = Cellphone("Samsung", 1299.99)
    println(cellphone1)
    println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}
複製代碼

注意:若是將 class 前面的 data 去掉,那麼它們的返回值就會變爲 false。

4.4.3 單例類

在講解單例類以前先說一下 Java 中的單例模式,單例模式主要是爲了防止爲一個對象建立多個實例,在 Kotlin 中若是想實現相似功能可使用單例類

4.4.4 Java 中的單例類

public class Singleton {
    
    private static Singleton INSTANCE = null;
    private Singleton() {}
    
    public synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    
    public void singletonTest() {
        System.out.println("singletonTest is called.");
    }
}
複製代碼

4.4.5 Kotlin 中的單例類

object Singleton {
    fun singletonTest() {
        println("singletonTest is called.")
    }
}
複製代碼

在 Kotlin 中實現單例要比 Java 中簡單的多,只須要使用 object 關鍵字便可,在這其中 Kotlin 幫咱們建立了一個 Singleton 類的實例,而且保證全局只存在一個 Singleton 實例。

5.Lambda 表達式

在 JDK1.8 中引入了 lambda 表達式,實現相同的功能時 lambda 表達式寫法會使用更少的代碼,從而提高開發效率。在 Kotlin 中也有 lambda 表達式,下面將對此進行介紹。

5.1 集合的建立和遍歷

如今有一個需求,建立一個包含許多水果名稱的集合,若是在 Java 中會建立一個 ArrayList 然將水果的名稱一個個的添加進集合中,固然在 Kotlin 中也能夠這麼作。

fun main() {
    val list = ArrayList<String>()
    list.add("Apple")
    list.add("Banana")
    list.add("Orange")
    list.add("Pear")
    list.add("Grape")
}
複製代碼

數據少的時候這麼寫一點問題都沒有,可是問題在於數據量多的時候這麼寫就會顯得很羅嗦,因此可使用 Kotlin 中內置的 listOf() 函數來簡化初始化集合的寫法,寫法以下:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
    println(fruit)
}
複製代碼

注意:在這裏使用 listOf()函數建立的是一個不可變的集合。在 Java 中沒有不可變的集合,可是在 Kotlin 中不可變的集合指的是,該集合中的元素只能用於讀取,不能進行添加、修改或者刪除。

這麼設計的理由和 val、類默認不可繼承是同樣的,可見 Kotlin 在不可變性方面的控制及其嚴格。那麼若是咱們確實須要建立一個可變的集合,可使用mutableListOf()函數便可。

val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
    println(fruit)
}
複製代碼

前面介紹的 List 集合的用法其實和 Set 如出一轍,只須要將建立集合的方法換成 setOf()mutableSetOf() 便可。

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in set) {
    println(fruit)
}

println("==========================")
val mutableSet = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Grape")
mutableSet.add("Watermelon")
for (fruit in mutableSet) {
    println(fruit)
}
複製代碼

接下來說解的 Map 和前面的 List 和 Set 有很大的不一樣,傳統的 Map 用法是先建立一個 HashMap 的實例,而後將一個個的鍵值對添加到 Map 中,好比給每一個水果一個對應的編號。

val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Orange", 3)
map.put("Pear", 4)
map.put("Grape", 5)
複製代碼

這種寫法與 Java 中的寫法類似,可是在 Kotlin 中並不建議使用 put()get() 方法對 Map 進行添加和讀取操做,而是更加建議使用一種相似於數組下標的語法結構,好比向 Map 中添加一條數據能夠這麼寫:

map["Apple"] = 1
複製代碼

從 Map 中讀取一條數據能夠這麼寫

val number = map["Apple"]
複製代碼

所以能夠將代碼優化爲一下形式

val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
複製代碼

這樣的寫法也不是最簡便的,在 Kotlin 中提供了一個 mapOf()mutableMapOf() 函數來繼續簡化 Map 的用法。在 mapOf() 函數中,咱們能夠直接傳入初始化的鍵值對組合來完成對 Map 集合的建立:

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
//    for (entry in map) {
//        println(entry.key + "\t" + entry.value)
//    }
for ((fruit, number) in map) {
    println("fruit is " + fruit + ", number is " + number)
}
複製代碼

5.2 集合的函數式 API

需求:如何在一個水果集合中找到單詞最長的哪一個水果?

  • 傳統實現方式
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
var maxLengthFruit = "";
for (fruit in list) {
    if (fruit.length > maxLengthFruit.length) {
        maxLengthFruit = fruit
    }
}
println("max length fruit is " + maxLengthFruit)
複製代碼
  • 使用集合 API 實現
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)
複製代碼

5.2.1 Lambda 表達式語法結構

{參數名1: 參數類型, 參數名2: 參數類型 -> 函數體}
複製代碼

這是 Lambda 表達式最完整的語法結構定義,首先最外層是一對大括號,若是有參數傳入到 Lambda 表達式中的話,還須要聲明參數列表,參數列表的結尾使用 -> 符號,表示參數列表的結束以及函數體的開始,函數體中能夠編寫任意行代碼,而且最後一行代碼自動做爲返回值

5.2.2 Lambda 表達式寫法演進

  • 最初寫法

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val lambda = { fruit: String -> fruit.length }
    val maxLengthFruit = list.maxBy(lambda)
    複製代碼
  • 簡化版本1

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
    複製代碼
  • 簡化版本2

    Kotlin 規定當函數的最後一個參數是 Lambda 時,能夠將 Lambda 表達式寫在最外面.

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
    複製代碼
  • 簡化版本3

    當 Lambda 參數是函數的惟一一個參數的話,能夠省略函數的括號。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
    複製代碼
  • 簡化版本4

    因爲 Kotlin 的推導機制,Lambda 的參數列表在大多數狀況下沒必要聲明參數類型,所以代碼能夠進一步簡化。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { fruit -> fruit.length }
    複製代碼
  • 簡化版本5

    當 Lambda 表達式的參數列表中只有一個參數時,能夠沒必要聲明參數名,能夠用 it 代替。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { it.length }
    複製代碼

5.2.3 使用 map 函數

集合中的 map 函數時最經常使用的一種函數式 API,它用於將集合中的每個元素都映射成一個另外的值,映射的規則在 Lambda 表達式中指出,最終生成一個新的集合。

需求:讓全部的水果命都變成大寫模式

val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map { it.toUpperCase() }
for (fruit in newList) {
    println(fruit)
}
複製代碼

5.2.4 使用 filter 函數

filter 函數是用來過濾集合中的數據的,它能夠單獨使用。

需求:只保留集合中字符長度大於5的水果名,並將符合條件的水果名轉換爲大寫

val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.filter { it.length <= 5 }.map { it.toUpperCase() }
for (fruit in newList) {
    println(fruit)
}
複製代碼

在這個例子中若是先調用 map() 再調用 filter() 也是能夠的,可是效率會有影響,由於這麼作會讓轉換的次數增長。

5.2.5 使用 any 和 all 函數

any 函數用於判斷集合種是否至少存在一個元素知足指定條件,all 函數用於判斷集合中是否全部元素都知足給定條件。

val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
val anyResult = list.any { it.length <= 5 }
val allResult = list.all {it.length <= 5 }
println("anyResult is " + anyResult + ", allResult is " + allResult)
複製代碼

5.3 Java 函數式 API 的使用

若是咱們再 Kotlin 代碼中調用了一個 Java 方法,而且該方法接收一個 Java 單抽象方法接口參數,就可使用函數式 API。

5.3.1 演示單抽象接口

  • Java 中

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    複製代碼

    對於任何一個 Java 方法,只要它接收 Runnable 參數,就可使用函數時 API。不過 Runnable 接口主要仍是結合線程來一塊兒使用的,所以這裏就經過 Java 的線程類 Thread 進行學習。

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Thread is running.");
        }
    }).start();
    複製代碼
  • Kotlin 中

    Thread(object : Runnable {
        override fun run() {
            println("Thread is running.")
        }
    }).start()
    複製代碼

    與 Java 寫法不一樣的是,Kotlin 中使用 object 關鍵字代替了 new 關鍵字。

    • 簡化1

      Thread(Runnable {
          println("Thread is running.")
      }).start()
      複製代碼

      因爲 Runnable 接口中只有一個方法,因此沒有手動實現的話,Kotlin 就會推導出 Lambda 表達式裏要寫的是 run() 方法中的內容。

    • 簡化2

      因爲 Java 方法的參數列表中不存在一個以上 Java 單抽象方法接口參數,因此能夠將接口名省略。

      Thread({
          println("Thread is running.")
      }).start()
      複製代碼
    • 簡化3

      因爲 Lambda 中只有一個參數,因此能夠將括號花括號內的內容移動到外面,而且還能夠將函數的括號省略,因此簡寫成以下形式:

      Thread {
          println("Thread is running.")
      }.start()
      複製代碼

總結:本小節學習的 Java 函數式 API 的使用都現定於 Kotlin 中調用 Java 方法,而且單抽象方法接口也必須是用 Java 語言定義的,這麼設計是由於 Kotlin 中有專門的高階函數來實現更增強大的自定義函數式 API 功能,從而不須要像 Java 這樣藉助單抽象方法接口來實現。

6.空指針檢查

Java 程序在運行時遇到空指針異常致使運行崩潰的例子數不勝數,究其緣由是由於空指針異常時一種運行時異常,須要開發者手動進行檢測。

6.1 處理空指針異常

public void doStudy(Study study) {
    study.readBooks();
    study.doHomework();
}
複製代碼

以上的代碼就頗有可能出現空指針異常,具體可否出現徹底要看傳入的 study 是否爲空,爲 了避免空指針異常的發生,一般都會作以下操做:

public void doStudy(Study study) {
    if (study != null) {
        study.readBooks();
        study.doHomework();
    }
}
複製代碼

這只是一小段代碼,若是在一個比較大的工程中要想徹底避免空指針異常並不現實。

6.2 可空類型系統

Kotlin 就很科學的解決了這個問題,它利用編譯時判空檢查的機制幾乎杜絕了空指針異常。雖然編譯時判空檢查的機制會致使代碼變得比較難寫,可是不用擔憂,Kotlin 提供了一整套輔助工具,讓咱們能夠輕鬆的完成判空任務。

6.2.1 回到 Kotlin 代碼

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}
複製代碼

這段代碼看上去和 Java 的沒有什麼區別,可是在 Kotlin 中全部參數和變量都不能爲空,因此這段代碼不可能出現空指針。

通過 Kotlin 的檢測,避免了全部對象爲空的可能,可是有時候就是須要傳入空對象,這該怎麼辦呢?

Kotlin 提供了一套可爲空的類型系統,只不過在使用可爲空的類型系統時,咱們須要在編譯時期就將全部潛在的空指針異常處理掉。

使用可爲空類型的系統時只須要在類型參數後面添加一個 ? 便可,例如

6.3 判空輔助工具

6.3.1 ?. 操做符

當對象不爲空時進行正常調用,爲空就什麼都不作

  • 傳統寫法:

    fun doStudy(study: Study?) {
        if (study != null) {
            study.readBooks()
            study.doHomework()
        }
    }
    複製代碼
  • 優化寫法:

    fun doStudy(study: Study?) {
        study?.readBooks()
        study?.doHomework()
    }
    複製代碼

6.3.2 ?: 操做符

這個操做符兩邊都接收一個表達式,若是左邊表達式的結果不爲空就返回左邊的結果,不然返回右邊的。

  • 傳統寫法

    val c = if (a != null) {
        a
    } else {
        b
    }
    複製代碼
  • 優化寫法

    val c = a ?: b
    複製代碼

需求:編寫一個函數用來得到一段文本的長度

  • 傳統寫法:

    fun getTextLength(text: String?): Int {
        if (text != null) {
            return text.length
        }
        return 0
    }
    複製代碼
  • 優化寫法:

    fun getTextLength(text: String?) = text?.length ?: 0
    複製代碼

6.3.3 !!. 操做符

Kotlin 有的時候也不很智能,好比已經作了非空判斷,可是調用時依然沒法經過編譯,那麼此時可使用非空斷言工具!!。便可。

注意:這種寫法存在風險,這樣寫意在告訴 Kotlin,我這裏必定不爲空,若是爲空後果我本身承擔。

6.3.4 let 函數

let 函數提供了函數式 API 的編程接口,並將原始調用對象做爲參數傳遞到 Lambda 表達式中。

obj.let { obj2 -> 
    // 編寫具體的業務邏輯
}
複製代碼

能夠看到這裏調用了 obj 對象的 let 函數,而後 Lambda 表達式中的代碼就會當即執行,而且這個 obj 對象自己還會做爲參數傳遞到 Lambda 表達式中。不過爲了防止變量重名,我將 obj 改成了 obj2 ,可是它們是同一個對象。

  • 使用 let 函數配合 ?. 操做符檢查空指針

    • 原代碼

      fun doStudy(study: Study?) {
          study?.readBooks()
          study?.doHomework()
      }
      複製代碼

      這種寫法與傳統的 if 判斷的寫法的區別在於使用 ?. 替代了 if,可是這裏要調用的方法不少的話就須要寫屢次 ?.,這種重複的操做就可使用 let 函數配合解決。

    • 優化版本1:

      fun doStudy(study: Study?) {
          study?.let { stu ->
              stu.readBooks()
              stu.doHomework()
          }
      }
      複製代碼

      這樣會在對象不爲空時調用 let 函數,而且只須要寫一遍 ?.

    • 優化版本2:

      在 Kotlin 中,Lambda 表達式若是隻有一個參數,能夠省略,使用 it 代替。

      fun doStudy(study: Study?) {
          study?.let {
              it.readBooks()
              it.doHomework()
          }
      }
      複製代碼

7.Kotlin 中的小魔術

7.1 字符串內嵌表達式

使用字符串表達式不再須要傻傻的拼接 字符串了,在 Kotlin 中,能夠直接使用字符串內嵌表達式,即便是很是複雜的字符串也能夠垂手可得地完成。

7.1.1 內嵌表達式語法

"hello, ${obj.name}. nice to meet you!"
複製代碼

在 Kotlin 中容許咱們在字符串裏嵌入 ${}這種語法結構的表達式,並在運行時使用表達式的執行結果替代這一部分的內容。另外,當表達式中只有一個變量的時候,能夠直接使用 $name 的形式進行簡寫,無需添加花括號了。

val brand = "Samsung"
val price = 1299.00
println("Cellphone(brand=$brand, price=$price)")    // 使用字符串表達式
println("Cellphone(brand = "+ brand +", price = " + price + ")")    // 不使用
複製代碼

7.2 函數的參數默認值

前面學習次構造函數的用法時提到過,次構造函數在 Kotlin 中不多使用,由於 Kotlin 提供了給函數設定參數默認值的功能,它在很大程度上可以替代次構造函數的做用。

具體來說,咱們能夠在定義函數的時候給任意參數設定一個默認值,這樣當調用此函數時就不會強制要求調用方爲此參數傳值,在沒有傳值的狀況下會自動使用參數的默認值。

7.2.1 給函數設定默認值

fun printParams(num: Int, str: String = "hello") {
    println("num is $num, str is $str")
}
printParams(1)
printParams(1, "哈哈")
複製代碼

fun printParams(num: Int = 100, str: String) {
    println("num is $num, str is $str")
}
複製代碼

若是咱們想爲 num 設置默認值,只傳字符串的參數值的話,像上面那麼寫就會報錯了

解決:將傳遞的參數指定參數名

fun printParams(num: Int = 100, str: String) {
    println("num is $num, str is $str")
}
printParams(str = "world")
複製代碼

7.2.2 用默認值替代次構造函數

  • 原來的代碼

    class Student(val sno: String, val grade: Int, name: String, age: Int)
        : Person(name, age) {
        constructor(name: String, age: Int) : this("", 0, name, age) {
        }
    
        constructor() : this("", 0){
        }
    }
    複製代碼

    這個構造函數的功能主要就是在調用無參構造函數時會對兩個參數的構造函數進行調用,並賦初始值,兩個參數的構造函數會調用四個參數的構造函數,並賦初始值,這徹底可使用函數默認值的方式進行替代。

  • 優化後的代碼

    class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) :
        Person(name, age) {
    }
    複製代碼
相關文章
相關標籤/搜索