Kotlin 從學習到 Android 第一章 基礎語法

Kotlin 從學習到 Android 第一章 基礎語法

1. 基本數據類型

1.1 數字類型

數據類型 字節長度
Double 8
Float 4
Long 8
Int 4
Short 2
Byte 1

注意: Kotlin 的字符類型不能轉化爲數字。

// java 代碼下面將輸出 98
System.out.println('a' + 1) ;

// kotlin 代碼下面將輸出 b
println('a' + 1) 

1.2 數字的表示

和 java 中一樣,不同數據類型有其特有的書寫方式,其格式如下:
- Long : 123L ,不能用 “l” (小寫 L)
- 十六進制: 0x11 ,對應十進制 17
- 二進制:0b11 ,對應十進制 3
- Double : 1.2e3 ,對應十進制 1.3 * 10^3
- Float : 123.4f 或 123.4F

注意: Kotlin 不支持八進制。

1.3 數字中的下劃線

爲了方便閱讀位數很多的數字,從 Kotlin 1.1 版後可以在數字中加入下劃線來分割,使用如下:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

1.4 拆箱與裝箱

在 java 中,數字類型是 JVM 私有的類型,當我們使用泛型或需要使其爲 null 時,需要對其進行裝箱操作(從 J2SE 5.0 可以自動裝箱拆箱 );而在 Kotlin 中如果一個基本數據類型(包括 Boolean 等)是可 null ,即:使用了 “?” 聲明,那麼就會被自動裝箱。

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
// “===”  比較的是地址
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!
// “==”  比較的是值
print(boxedA == anotherBoxedA) // Prints 'true'

注意:當 “===” 比較的對象是 Int 類型且其值在 [-128 , 127] 時(即: Byte 的取值範圍),Kotlin 返回的是 JVM 的內置屬性,也就是說此時 “===” 的返回結果爲 true 。

1.5 操作符

  • shl(bits) – 等同 java 中的 << , 如: 2 shl 1
  • shr(bits) – 等同 java 中的 >>
  • ushr(bits) – 等同 java 中的 >>>
  • and(bits) – 等同 java 中的 and
  • or(bits) – 等同 java 中的 or
  • xor(bits) – 等同 java 中的 xor
  • inv() – 等同 java 中的 inversion

1.6 字符型

字符型在 Kotlin 中用 Char 表示,不能像 java 中那樣直接當做數字來使用,例如:

fun check(c: Char) {
    if (c == 1) { // ERROR: 這樣不能編譯通過
        // ...
    }
}

既然 Char 類型無法當做數字來使用,那麼當我們需要像 java 中那樣使用 char 對應的數字時可以這麼做:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}

1.7 Boolean

Boolean 有兩個取值 true 或 false 。

1.8 Array

Array 類型在 Kotlin 中創建比較自由,常用的的方式有如下幾種:

// 1. 使用 arrayOf() 創建
val list01 : Array<Int> = arrayOf(1 , 2 , 3)
// 2. 創建一個指定長度的 null Array
val list02 : Array<Int> = arrayOfNulls<Int>(3) 
// 3. 創建數組 ["0", "1", "4", "9", "16"]
val list03 = Array(5, { i -> (i * i).toString() })
// 4. 不用泛型創建(Boolean 、 Float 等)
val list04: IntArray = intArrayOf(1, 2, 3)

1.9 String

在 Kotlin 中 String 類型可以像 Array 類型那樣通過迭代的方式挨個字符輸出,如:

val str: String = "abc"
for(c in str){
    println(c)
}
// 打印結果
// a
// b
// c

另外,String 在 Kotlin 中除了有像 java 中一樣的聲明方式,又添加了一種新的聲明方式:

// 1. 與 java 相同的聲明方式,特殊字符使用反斜槓
val str01 = "Hello \"World\" !\n"  
// 2. Kotlin 特有的聲明方式:三個雙引號開始至三個雙引號結束
val text = """
    for (c in "foo")
        print(c)
"""
// 這種方式兩組雙引號間的內用會原封不動的作爲一個字符串包括特殊字符和空格

除了新的聲明方式,Kotlin 還爲 String 添加了一種以 “$” 作爲開頭的引入方式:

val text01 = "10"
val text02 = """abcd${text01}"""
println("${text01.length}")            // 2
println("$text02")                 // abcd10

2. 定義包名

包名應該在文件頭部,如果不聲明 Kotlin 文件所在的包,那麼該文件將沒有包名,被不同包下的函數調用時只需要 import funXXX 即可。

// 1. file01.kt
package my.demo
import java.util.*
import noPackageNameFun
// ...
noPackageNameFun()

// 2. file02.kt
fun noPackageNameFun(){...}

注意:在 kotlin 中包名可以不和文件路徑相對應。

Kotlin 也會像 java 那樣默認導入一些包,這樣我們就可以直接在文件中使用了,默認導入的包有:

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.* (since 1.1)
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

當針對不同平臺時,還會默認導入一些平臺特有的包,如:

JVM
- java.lang.*
- kotlin.jvm.*

JS
- kotlin.js.*

2.1 包

在 Kotlin 中導包操作和 java 有很大的不同:

  1. 命名衝突的處理

    // 命名衝突時,可以用 as 來重命名
    import foo.Bar
    import bar.Bar as bBar
    
  2. 除了 class 外,import 在 Kotlin 中還可以導入:

    • 頂級函數和屬性
    • 對象聲明中的函數和屬性
    • 枚舉常量

另外,Kotlin 沒有 import static ,所有情況都用 import 即可。

注意:如果頂級的聲明用了 private ,那麼它只能在所聲明的文件中使用。

2.2 函數

在 Kotlin 中函數的聲明要用 fun ,一般有如下幾種方式:
1. 聲明返回類型

    fun sum(a: Int, b: Int): Int {
        return * // 一個 Int 類型的數字
    }
  1. 對於表達式構成的函數,返回一個推測類型

    fun sum(a: Int, b: Int) = a + b // 這裏會返回 a 、 b 之和且爲 Int 類型
    
  2. 返回一個無意義的值

    // 此函數的返回值爲: kotlin.Unit
    fun printSum(a: Int, b: Int): Unit {
        ... // 函數體中如果有 return ,其後不能跟任何有意義的值
    }
    
  3. 當返回值爲 Unit 時,可以再函數聲明中省略,上面的函數等效於:

    // 此函數的返回值爲: kotlin.Unit
    fun printSum(a: Int, b: Int){
        ... // 函數體中如果有 return ,其後不能跟任何有意義的值
    }
    

2.2.1 中綴表達式

當符合下面三種情況時,函數也可以使用中綴表達式的方式聲明(並沒看出有什麼用 … ):

  • 成員函數或擴展函數
  • 函數有且只有一個參數
  • 函數聲明關鍵字 fun 前用 infix 修飾

使用函數如下:

class CustomObject{

}
infix fun CustomObject.plus100(x : Int): Int{
    return x + 100
}
infix fun Int.test(x : Int): Int{
    ...
}
CustomObject() plus100 20       // 與下面的等效
CustomObject().plus100(20)

1 test 2                        // 與下面的等效
1.test(2)

2.2.2 參數

函數參數使用 Pascal 表達式的格式聲明,也就是 參數名: 參數類型 的格式。參數間以逗號分隔,且參數必須指定參數類型。

fun powerOf(number: Int, exponent: Int) {
    ...
}

2.2.3 函數參數的默認值

在 Kotlin 中,可以直接對參數進行初始化:

open class A {
    open fun foo(i: Int = 10) {
        println("A:" + i)
    }
}

class B : A() {
    override fun foo(i: Int) {
        super.foo(i)
        println("B:" + i)
    }
}

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
    println(off) // 0
    ...
    B().foo() // 不傳參數時會使用默認值
    // A:10
    // B:10
    B().foo(2)
    // A:2
    // B:2
}

2.2.4 Named Arguments

對於一個有很多參數的函數,有時我們僅傳入一個參數,而其他參數都使用默認值,在 java 中你需要把其他每個參數的默認值按順序寫一遍;現在,在 Kotlin 中不需要那麼做了,我們可以只關注那些需要修改的參數,例如:

// 一個大量參數的函數
fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
...
}
// 當我們只關注 str ,其他參數使用默認值時,可以這樣調用
reformat(str)
// 可以這樣,直接跳過中間幾個參數
reformat("abc" , wordSeparator = 'a')
// 還可以這樣,不按照聲明的順序
reformat("abc" , wordSeparator = 'a' , normalizeCase = false)
// 但不能這樣
// reformat("abc" , 'a') // 錯誤的寫法

2.2.5 函數返回 kotlin.Unit

當一個函數無需返回任何有意義的值時,類似於 java 中的 void ,但不同的是:這個函數會返回一個唯一值 kotlin.Unit ,且返回類型 Unit 可以省略。

fun myPrint(str : String){
    ...
}
// 等效於
fun myPrint(str : String): Unit{
    ...
}

2.2.6 單一表達式

當一個函數的返回值是一個表達式語句時,大括號可以省略,將表達式置於 = 後即可。

fun double(x: Int): Int = x * 2

如果返回值類型是可以推測的也可以省略:

fun double(x: Int) = x * 2

2.2.7 明確函數返回類型

當函數的函數體用大括號括起來的時候必須指定函數的返回值類型,除非函數的返回值是 Unit ,此時可以省略不寫;因爲,當函數體有大括號時,Kotlin 不會推測函數返回值得類型。

2.2.8 不定長參數

不定長參數類似於 java 中的 void method(String …){…},當一個函數的參數是不定長參數時,使用關鍵字 vararg 聲明。

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts 可以看做是個數組,可以 ts[0] 這樣使用其中的元素,注意數組的下標越界
        result.add(t)
    return result
}
val list01 = asList(1, 2, 3)
// 當需要爲不定長傳入一個已經有的數組時,我們可以用 * 表示
val list02 = asList(1, 2, *list01 , 3)

注意:當不定長參數不是最後一個參數時,可以使用 named argument (見 2.2.4)。

2.2.9 函數的使用範圍

在 Kotlin 中,函數可以直接在文件中聲明,而不需要依賴 class 存在。例如下面的 Hello.kt 文件:

// Hello.kt 文件中只有一個函數
fun sayHello(){
    println("Hello")
}

另外,Kotlin 函數也能作爲 局部函數 、 成員函數 和 擴展函數。

局部函數

// 函數嵌套在另一個函數中,並能訪問這個函數外的變量
val name: String = "kotlin"
fun main(args: Array<String>) {
    val str : String = "str"
    fun localFun(str:String){
        println("local fun $name " + str)
    }
    localFun()
}

成員函數

成員函數就是定義在 class 中的函數。

class Sample() {
    fun foo() { print("Foo") }
}

泛型

和 java 中的泛型大同小異。

fun <T> singletonList(item: T): List<T> {
    // ...
}

內聯函數

Extension Functions

Higher-Order Functions and Lambdas

尾遞歸函數

關於什麼是尾遞歸函數可以看這篇,這裏只講一下如何在 Kotlin 中使用。

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

使用尾遞歸需要在關鍵字 fun 前添加 tailrec ,另外,使用尾遞歸時這個函數必須在最後調用自身,你不能在 try/catch/finally 中使用尾遞歸,而且當前尾遞歸(version 1.1)僅在 JVM 後端支持。

3.定義局部變量

只讀局部變量

// 只讀變量用關鍵字 val 聲明
val a: Int = 1  // 聲明的時候立刻初始化
val b = 2   // 編譯器會推測 b 是 Int 類型
val c: Int  // 聲明時不初始化,必須指定變量類型
c = 3       // 聲明後賦值
// 對於只讀變量只能初始化時賦值一次,下面的代碼如果不註釋則編譯器會給出提示編譯不通過
// a = 2

可變局部變量

// 可變變量用關鍵字 var 聲明
var x = 5 // 編譯器會推測 x 是 Int 類型
x += 1

注意:在 Kotlin 中沒有關鍵字 new ,所以創建對象時可以直接調用構造方法:

class A{
    ...
}
var a = A()

4.註釋

和 java 一樣,Kotlin 支持也單行註釋和多行註釋,但是 Kotlin 支持多行註釋間的嵌套。

// This is an end-of-line comment

/* This is a block comment
   on multiple lines. */

// 多行註釋的嵌套
/* This is a block comment  /* This is a block comment
       on multiple lines. */
            on multiple lines. */

5.條件表達式

同 java 。

6.null 安全

在 java 中,稍不注意就會報 NullPointException ,而在 Kotlin 中完全不用擔心,我們可以使用 ? 標識對象獲屬性是 null 安全的。

fun main(args : Array<String>){
    fun testNullSafety() : String?{
        return null
    }
    // 這裏並不會報 NPE
    println(testNullSafety().toString()) // null
}

7.類型自動轉換

is 操作符用來檢測一個實例的類型,當一個局部變量或屬性被檢測屬於某個類型(如: String)時,則在這個檢測分支中可以直接使用這個類型的方法(如:String.length)而不需要強制轉換。

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // obj 自動轉換成 String 類型,我們可以直接使用 String 的方法
        return obj.length
    }

    // 如果這裏返回 obj.length 則編譯不通過,因爲此處的 obj 是 Any 類型,沒有 length 屬性
    // return obj.length
    return null
}

當然,你也可以這樣寫

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null
    return obj.length
}

或者

fun getStringLength(obj: Any): Int? {
    if (obj is String && obj.length > 0) {
        return obj.length
    }
    return null
}

8.for 循環

val items = listOf("apple", "banana", "kiwi")
for (item in items) {
    println(item)
}

或者

val items = listOf("apple", "banana", "kiwi")
for (index in items.indices) {
    println("item at $index is ${items[index]}")
}

從上面我們可以看出,for 循環和 java 中的 for 功能一樣,而操作符 in 相當於 java 中的 : 。

9.while 循環

val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
}

while 和 java 中的 while 類似。

10.when

Kotlin 中的 when 更像是 switch:

fun describe(obj: Any): String =
when (obj) {
    1          -> "One"
    "Hello"    -> "Greeting"
    is Long    -> "Long"
    !is String -> "Not a string"
    else       -> "Unknown"
}

11.範圍操作

範圍操作是使用 .. 、 downTo 和 step 對數字取值的操作。

檢測某個值在一定範圍內

val x = 10
val y = 9
if (x in 1..y+1) {// 取值範圍 [1,10]
    println("success") // success
}
val z = 2.34f
if (z in 1..y+1) {
    println("success") // success
}

檢測某個值不在範圍內

val list = listOf("a", "b", "c")

if (-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if (list.size !in list.indices) {
    println("list size is out of valid list indices range too")
}

注意:當判斷範圍時,Int 、 Long 、 Float 等可以混用,但是當迭代時只取 Int 類型。

for (x in 1..5) {
    print(x)    // 12345
}

上面的都是升序和逐個取,也可以降序和設定取值間隔

for (x in 1..10 step 2) {
    println(x)      // 13579
}
for (x in 9 downTo 0 step 3) {
    println(x)      // 9630
}

12.集合的使用

集合的迭代

val items = listOf("apple", "banana", "kiwi")
for (item in items) {
    println(item)
}

// apple
// banana
// kiwi

檢測集合中是否包含某個元素

val items = setOf("apple", "banana", "kiwi")
when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

// apple is fine too

使用 lambda 表達式操作集合中的元素

val fruits = listOf("banana", "avocado", "apple", "kiwi")
fruits
.filter { it.startsWith("a") } // 過濾出以字母 a 開頭的字符串
.sortedBy { it }               // 將過濾出的 String 默認升序排列
.map { it.toUpperCase() }      // 將所選 String 映射成全大寫,而 fruits 的不會改變
.forEach { println(it) }       // 輸出操作結果

println(fruits[1])

// APPLE
// AVOCADO
// avocado
發佈了89 篇原創文章 · 獲贊 67 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章