Kotlin 基礎入門

来源:https://www.cnblogs.com/joy99/archive/2023/09/05/17681202.html
-Advertisement-
Play Games

如果你會 Java, 那麼來看一看 Kotlin , 基礎入門。 如果你不理解 Kotlin 的lambda 表達式,那麼來看一看,幫助你真正理解函數類型,lambda 表達式。 ...


目錄

一、基礎語法

Koltin 是一種靜態強類型語言,它要求在編譯時確定變數類型,併在運行時嚴格執行類型檢查。

  1. 聲明變數時必須指定變數類型,無法隱式地將不同類型的值分配給變數。
  2. 有助於減少類型錯誤,使得代碼更加穩健和可維護。

1.1 常見數據類型

Byte、 Short、 Int、 Long、 Float、 Double、 Char、 Boolean、 String

特殊的類型: Unit, 類似 java 中的 Void, 函數或者表達式沒有返回值時用 Unit

無 java 中的 int、 Integer 等,避免裝箱拆箱的性能損耗。
在某些情況下,Kotlin 仍然需要進行裝箱拆箱操作,例如將基本類型的值傳遞給需要對象類型的函數時,或者將基本類型的值放入集合或數組中時。

轉換:

3.toFloat()

val a = 5.6f
a.toInt()

字元串 String

val name: String = "Lucy"

// 可迴圈
for (c in name) {

}

// 可拼接
println(name + "abc") // Lucyabc

// 字面值
val s = "Hello, world!\n"  // 反斜杠轉義

// 三引號內部不轉義,可以包含任何字元,包括換行符
val text = """
    for (c in "abc") {
        println(c)
    }
"""

// 字元串模板
val name = "James"
println("$name is a basketball player!")

// 用 ${} 引用表達式
println("$name 這個單詞的長度是 ${name.length}")

1.2 變數

1.2.1 變數聲明

var um: Int = 0  // 使用 var 聲明一個可讀可寫的變數
val name: String = "Jimy"  // 使用 val 聲明一個引用不可變的變數,對象成員可變,Java 中 final 的效果, 儘可能採用 val 聲明變數。

1.2.2 類型推斷

val name = "Lucy"  // name 可以推斷出 name 是 String  類型
name.uppercase()  // 正確

// fails to compile
name.inc() // inc() 是 Int 類型的的運算浮, String 類型無法調用

1.2.3 Null 安全

可空類型

// Fails to complie
val name: String = null

要使變數持有 null 值,它必須是可為 null 類型,在變數類型後面加上一個 ? 尾碼。

val name: String? = null  

指定 String? 類型後,可以為 name 賦予 String 值或者 null

安全調用
安全調用 ?.
非空斷言 !!

// java
String name = "Lucy"
if (name != null ) {
    name.toUpperCase()
}

// Koltin
val name: String? = null 
name?.uppercase()  // name 不為 null 時執行方法調用
name!!.uppercase()  // name 為 null 時,拋出異常

多層判斷可以鏈式調用。

Elvis操作符

// java
int nameLength = name!=null ? name.length : -1

// Koltin
val nameLength = name?.length?:-1

註意: 上面 Kotlin 版本中 nameLength 類型為 Int

val name: String? = "Lucy"
val length = name?.length

上面這段代碼中 length 是什麼類型? ----- Int?

1.2.4 面向對象語言

Any ---- 類似於 java 中的 Object, 所有非空類型的根類型
Any? ---- 所有類型的根類型。 自然也是 Any 的跟類型。

Nothing ---- 所有類型的最底層,包含一個值 null

1.3 流程式控制制

1.3.1 if 表達式

// 傳統寫法
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}

// 表達式寫法
var max: Int = if (a > b) a else b

註意: if 作為表達式而不是語句時,需要有 else 分支。

1.3.2 When 表達式

when(x) {
    1 -> { println("x == 1") }
    is Int -> { println("x is a Int") }
    2, 3 -> {println("x == 2 or x == 3")}  // 多個分支條件放在一起用 , 隔開
    else -> { println("x maybe a string") }
}

多個分支條件放在一起用 , 隔開。
when 將它的參數與所有的分支條件順序比較,直到某個分支滿足條件(匹配第一個滿足條件的分支)

when 既可以被當做表達式使用也可以被當做語句使用。如果它被當做表達式, 符合條件的分支的值就是整個表達式的值。必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經覆蓋了。(例如 枚舉 或者 密封類子類。

When 進階用法,不提供參數:

if (age > 60) {
    println("老年人,可以享受6折優惠")
} else if (age > 0) { 
    if (identity is Student) {
        println("學生,可以享受半價優惠")
    } else {
        println("需要全價")
    }
} else {
    throw AgeIlligalException("age must be greater than 0")
}

等價於

when {
    age < 0 -> throw AgeIlligalException("age must be greater than 0")
    age > 60 -> println("老年人,可以享受6折優惠") 
    identity is Student -> println("學生,可以享受半價優惠")
    else -> println("需要全價")
}

1.3.3 For 迴圈

// rangeTo [0, 10]
for(item in 0 .. 10) {
    ...
}

// [0, 10)  中綴函數
for (i in 0 until 10) {

}

for(item in list) {

}

for ((index, value) in list.withIndex()) {

}

1.3.4 While 迴圈

while (x > 0) {
    x--
}

do {
    val y = 5
    y-- 
} while (y > 0) // y 在此處可見的

二、函數與 lambda 表達式

2.1 函數聲明

Kotlin 函數用 fun 聲明,預設是 public 的。

代碼塊函數體:

fun sum(x: Int, y: Int): Int {
    return x + y
}

表達式函數體:

fun sum(x: Int, y: Int) = x + y

Kotlin 支持類型推導,使用表達式函數體,一般情況下可以不聲明返回值類型。在一些諸如遞歸等複雜情況下,即使是使用表達式函數體,也必須顯示聲明返回值類型。

總結:

  1. 函數參數必須顯示聲明類型
  2. 非表達式函數體,函數參數必須顯示聲明類型, 返回值除了類型是 Unit,可以省略,其它情況,返回值都必須顯示聲明類型
  3. 如果它是一個遞歸的函數,必須顯示聲明參數和返回值類型
  4. 如果它是一個公有方法,為了代碼的可讀性,儘量顯示聲明函數參數和返回值類型

2.2 函數類型

2.2.1 示例引入

先看一個例子:

// 定義一個打招呼的方法
fun greetPeople(name: String) {
    // ...  做一些額外的事情

    greetingWithEnglish(name)
}

private greetingWithChinese(name: String) {
    println("早上好, $name!")
}

假設現在要進行國際化,加入了英語打招呼的方式

fun greetingWithEnglish(name: String) {
    println("Good morning, $name!")
}

需要對 greetPeople 方法進行改造

enum class Language {
    Chinese, English
}

fun greetPeople(name: String, lang: Languge) {
    // ...  做一些額外的事情
    when(lang) {
        Chinese -> greetingWithChinese(name)
        English -> greetingWithEnglish(name)
    }
}

這樣是不是拓展性很差,有沒有一種可能,我把要調用的函數單做一個參數傳進去,我傳什麼參數,就調用什麼方法?
我希望的樣子,大概如下:

// 定義 greetPeople 方法
fun greetPeople(name: String, makeGreet: ***) {
    makeGreet(name)
} 

註意看,這個 *** 所在的這個位置應該是放置參數的類型,那這個應該是什麼類型呢,很多編程語言裡面有,很遺憾,在 java 裡面沒有這樣的類型。但是 java 我們可以有一種變通方案來實現 —— 介面回調。
用介面包裝一下方法,先看一下用 java 如何實現

// 定義一個介面
public interface IGreet {
    void makeGreet(String name);
}

// 把介面作為參數
public void greetPeople(String name, IGreet: greetMethod) {
    greetMethod.makeGreet(name);
}

// 調用
greetPeople("Jimy",  new IGreet() {
            @Override
            public void makeGreet(String name) {
                greetingWithEnglish("Jimy")
            }
        });

// java 8 lambda 表達式
greetPeople("Jimy", name -> {
            greetingWithEnglish("Jimy")
        });

2.2.2 Koltin 函數類型

從上面例子來看,實際上我希望把一個函數當做參數傳入一個方法,但是又找不到合適的類型來聲明這個參數,幸運的是,Kotlin 提供了這樣的類型——函數類型。

在 Kotlin 中,聲明函數類型必須遵循一下幾點:

Kotlin 中,函數類型聲明必須遵循以下幾點:

  1. 通過 -> 符號來組織參數類型和返回值類型。左邊是參數類型,右邊是返回值類型
  2. 必須用一個括弧來包裹參數類型
  3. 返回值類型即使是 Unit, 也必須顯示聲明。
() -> Unit  // 代表無參數,無返回值的函數類型
(Int, String) -> Boolean  // 代表第一個參數是 Int 類型,第二個參數是 String 類型,返回值類型是 Boolean 類型的函數類型

在 Kotlin 中 函數是一等公民。
再看上面的例子,我們需要的參數 makeGreet 的類型應該是只有一個參數,類型為 String, 且無返回值的函數類型,如下:

(String) -> Unit

Kotlin 實現上面的代碼 :

// 定義 greetPeople 方法
fun greetPeople(name: String, makeGreet: (String) -> Unit) {
    makeGreet(name)
} 

// 調用
greetPeople("Jimy", :: greetingWithChinese)

在上面的定義中,markGreet 是個什麼?是個函數類型對象的引用,只有對象才能被作為函數的參數。也就是說,我們需要把一個函數類型的一個具體 “對象” 當做函數參數傳遞。
這個 makeGreet 被稱之為函數引用,Function Reference,這也是 Kotlin 官方的說法。

這也說明,在 Kotlin 中,函數也可以是一個對象。

2.2.3 函數引用

在 Kotlin 里,一個函數名的左邊加上雙冒號,它就不表示這個函數本身了,而表示一個對象,或者說一個指向對象的引用,但,這個對象可不是函數本身,而是一個和這個函數具有相同功能的對象。

// 這是函數的定義
fun greetingWithChinese(name: String) {
    println("早上好, $name !")
}

:: greetingWithChinese // 表示函數對象

既然它表示一個函數的對象,那麼,我們也可以把它賦值給一個變數。

val a = :: greetingWithChinese
val b = a

所以,我們也可以把這個變數作為函數參數來調用

greetPeople("Jimy", a)
greetPeople("Jimy", b)

甚至,我們還可以直接通過這個引用調用 invoke() 來執行這個函數對象。

(:: greetingWithChinese).invoke("老王")
a.invoke("Jimy") 
b("Lucy") // 還可以這樣調用,這個是 Kotlin 的語法糖,實際上調用的 invoke 方法

2.2.4 高階函數

事實上,函數不僅可以當做一個函數的參數,還能作為函數的返回值

定義:一個函數如果參數類型是函數或者返回值類型是函數,那麼這就是一個高階函數。

換個角度看函數類型

fun greetingWithChinese(name: String) {
    println("早上好, $name !")
}

如果將這個函數的參數類型和返回值類型,抽象出來,它的函數類型就是 (String) -> Unit,
那麼,我們就可以直接聲明一個變數,指定這個變數的函數類型是它,

val a = (String) -> Unit = :: greetingWithChinese

註意
前面說了,可以把這個變數,當做參數傳遞給另一個函數。那麼思考一下,有沒有一種可能:我把這個函數本身直接挪過來作為參數使用呢?什麼意思?

// 這個是之前說的調用方式 
greetPeople("Jimy", a)

我能不能像下麵這麼寫?

greetPeople(
    "Lucy", 
    fun greetingWithChinese(name:String) {
        println("早上好, $name !")
    }
)

如圖

咦,編輯器報錯了,Anonymous functions with names are prohibited
禁止使用帶有名稱的匿名函數。提示我把函數名稱移除。

greetPeople(
    "Lucy", 
    fun(name: String) {
        println("早上好, $name !")
    }
)

這樣就可以了。這也很好理解,函數名稱是給其他地方調用的,當前這種情況下這個函數作為參數,並不會在其它地方調用,這個函數名稱也就沒有什麼作用,不允許有函數名稱,這種寫法叫做匿名函數。

而匿名函數是一個表達式。本質上是一個函數類型的具體實例對象。

既然是表達式,那也就可以賦值給一個變數:

// 把匿名函數賦值給變數 c
val c = fun(name: String) {
        println("早上好, $name !")
    }

// 將 c 作為參數傳遞,調用 greetPeople
greetPeople("Lucy", c)

那又有人會問了,能否把一個常規有名字的函數賦值給一個變數。比如

// 編譯報錯
val d = fun greetingWithChinese(name: String) {
        println("早上好, $name !")
    }

這是不允許的。
講道理,我都要把它賦值給一個變數了,那麼它本身的名字完全沒有必要。
有名字的函數不能賦值給一個變數,但是前面提到了,有名字的函數的引用,用雙冒號的形式,則可以。
只有對象才可以賦值給一個變數,仔細想一想,匿名函數,其實不是函數,是一個對象。它是一個表達式,本質上是一個與它形式相同(參數,返回值一致)的函數類型的具體對象。
它和一個函數前面加上雙冒號是一樣的。

2.3 lambda 表達式

2.3.1 lambda 表達式的概念

對於一個函數類型的變數,我們除了可以把一個函數對象或者一個匿名函數賦值給它,還可以賦值一個 lambda 表達式。
什麼是 lambda 表達式?

// 匿名函數
val c = fun(name: String) {
        println("早上好, $name !")
    }

// 等價於下麵的 lambda 表達式
{ name: String -> Unit
    println("早上好, $name !")
}

一個完整的 Lambda 表達式聲明如下:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

由於 Kotlin 支持類型推導,所以它可以簡化為:

val sum: (Int, Int) -> Int = { x, y -> x + y }

或者

val sum = { x: Int, y: Int -> x + y }

這個 lambda 表達式,也是一個函數類型的具體對象。所以也可以當做另一個函數的參數傳入。

2.3.2 lambda 表達式的寫法演變過程

回到前面的例子,調用 greetPeople() 就可以寫為

greetPeople(
    "Lucy", 
    { name: String -> Unit
        println("早上好, $name !")
    })

Kotlin 中的函數,如果最後一個參數是 lambda 表達式,則可以將 lambda 表達式寫到參數外面:

greetPeople("Lucy") { name: String -> Unit
        println("早上好, $name !")
    }

就是這麼個形式,還有,lambda 表達式支持類型推導,前面定義了函數

fun greetPeople(name: String, makeGreet: (String) -> Unit)

從這個函數定義處,已經知道,調用時 lambda 函數表達式應該是什麼樣的函數類型,它的參數,返回值類型都確定了,所以可以在 lambda 表達式中省略,如下:

greetPeople("Lucy") { name ->
        println("早上好, $name !")
    }

再來看 Android 中常用的一個例子, 給 View 設置點擊事件:

public interface OnClickListener {
  void onClick(View v);
}
public void setOnClickListener(OnClickListener listener) {
  this.listener = listener;
}

java 中調用:

view.setOnClickListener(new OnClickListener() {
  @Override
  void onClick(View v) {
    doSomething();
  }
});

到 Kotlin 中就可以這麼寫:

fun setOnclickListener(onClick: (View) -> Unit) {
    this.onClick = onClick
}

view.setOnclickListener({view: View -> Unit
    doSomething()
})

將 lambda 表達式移到括弧外面:

view.setOnclickListener() { view: View -> Unit
    doSomething()
}

參數和返回值類型可推導,省略參數和返回值類型:

view.setOnclickListener() {view ->
    doSomething()
}

如果 lambda 表達式是唯一參數,則括弧都可以直接省略,同時 lambda 表達式如果是單參數,則這個參數也可以直接省略不寫。因為對於 Kotlin 的 Lambda,單參數有一個預設的名稱:it, 使用的時候用 it 替代

view.setOnclickListener {
    doSomething()
}

所以就變成我們經常看到的樣子了。

對於多參數的 lambda 表達式,我們不能省略掉參數,但是如果參數沒有被用到,可以用 _ 來代替。

2.3.3 lambda 表達式自調用

另外 lambda 表達式也可以進行自調用

{x: Int, y: Int -> x + y}(1, 2)

2.3.4 總結

所以 Kotlin lambda 表達式本質是什麼呢?
其實和匿名函數一樣,本質上也是一個函數類型的具體對象。它可以作為函數的參數傳入,也可以賦值給一個變數。
總結一下

lambda 表達式語法:

  1. lambda 表達式必須通過 {} 來包裹
  2. 如果 lambda 聲明瞭參數部分的類型,且返回值支持類型推導,則 lambda 表達式變數就可以省略函數類型聲明
  3. 如果 lambda 變數聲明瞭函數類型,那麼 lambda 表達式的參數部分的類型就可以省略
  4. 如果 lambda 表達式返回的不是 Unit 類型,則預設最後一行表達式的值類型就是返回值類型。

lambda 表達式和函數的區別

  1. fun 在沒有等號、只有花括弧的情況下,就是代碼塊函數體,如果返回值非 Unit,必須帶 return
fun foo(x: Int) {
    print(x)
}

fun foo(x: Int, y: Int): Int {
    return x + y
}
  1. fun 帶有等號,沒有花括弧,是單表達式函數體,可以省略 return
fun foo(x: Int, y: Int) = x + y
  1. 不管是用 val 還是 fun 聲明,如果是等號加花括弧的語法,就是聲明一個 lambda 表達式。
val foo = { x: Int, y: Int ->
    x + y
}
// 調用方式: foo.invoke(1, 2) 或者 foo(1, 2)

fun foo(x: Int) = { y: Int ->
    x + y
}
// 調用方式: foo(1).invoke(2) 或者 foo(1)(2)

三、介面、類與對象

3.1 類

Kotlin 中用 class 或者 object 聲明類,如果沒有類體,可以省略 {}
區別在於用 object 聲明的類是一個單例類,無法被實例化。

Kotlin 多個類可以聲明在同一個文件中。

3.1.1 構造函數

在 Kotlin 中的一個類可以有一個主構造函數以及一個或多個次構造函數。主構造函數是類頭的一部分:它跟在類名(與可選的類型參數)後。

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

}

如果主構造函數沒有任何註解或者可見性修飾符,可以省略這個 constructor 關鍵字。

class Student(id: Int, name: String, age: Int) {
    constructor(name: String, age: Int): this(0, name, age)  // 次構造函數
}

3.1.2 init 代碼塊

類中允許存在多個 init 代碼塊,對於不同的業務初始化可以放在不同的 init 代碼塊中,按代碼順序依次執行。
類似於 java 類中的靜態代碼塊。

init 代碼塊中可以直接訪問構造函數的參數,其它地方不可以。

3.1.3 成員變數

聲明

  1. 和 java 類似,可以在類代碼塊中直接聲明。
  2. 構造方法參數加上 var 或者 val 修飾,則可以視為類中聲明瞭一個同名的成員變數。也可以加上可見性修飾符。

初始化
成員變數聲明時需進行初始化,或者顯式聲明延遲初始化,使用關鍵字 lateinit 顯示聲明延遲初始化

class Student(id: Int, name: String, var age: Int) {
    constructor(name: String, age: Int): this(0, name, age)

    // init 代碼塊中訪問構造函數的參數
    init {
        println("student name is $name")
    }

    fun printInfo() {
        age += 1
    }

    lateinit var gender: String  // 延遲初始化
}

3.1.4 繼承

使用

open class Person(id: Int, name: String)

class Student(id: Int, var name: String, var age: Int): Person(id,name) {
    // ...
}

被繼承的類需要用 open 修改,同樣父類的中法,預設是不可被重寫的,若允許被重寫,需要使用 open 修飾。

3.2 介面

interface MyInterface {
    fun bar()
    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(prop)
    }
}

介面允許有屬性,介面中的方法允許有預設實現,介面不需要用 open 修飾,因為介面就是用來讓子類實現,然後被被子類重寫的。包括屬性也是可以被重寫的。介面可以繼承自其它介面。

3.3 數據類

data class Student(val name: String, var age: Int = 18)
  • 自動生成 getter()/setter() 方法 !!!???
  • 編譯器自動生成 equals()/hashCode() 方法
  • 編譯器自動生成 toString() 方法
  • 編譯器自動生成 `componentN()`` 函數(解構)
  • 編譯器自動生成 copy 函數
data class Student(val name: String, var age: Int = 18) {
    var isBoy = true // 該屬性不會在生成的方法中
}

3.4 內部類

java 中在類中再聲明類,稱之為內部類,如果使用 static 修飾,則為靜態內部類。

class A {
    private String a

    class B {

    }

    static class C {

    }
}

問題:請問 B 和 C 中能直接訪問到 A 的成員 a 嗎?

在 Kotlin 中沒有 static 關鍵字,在類中預設聲明的就是靜態內部類,如要聲明內部類,需要添加 inner 關鍵字

class A {
    // 聲明內部類需要使用 inner 關鍵字
    inner class B {

    }

    // 靜態內部類
    class c {

    }
}

3.5 密封類

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

密封類可以用來代替枚舉,使用 When 表達式時如果能覆蓋所有,則無需 else 分支。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不再需要 `else` 子句,因為我們已經覆蓋了所有的情況
}

3.6 伴生對象

Kotlin 中沒有了 static 關鍵字,但是有伴生對象的概念,類似於 java 中的 static。

class Student {
    companion object {
        const val TAG = "Student" // 常量
        var count = 0 // 靜態變數

        // 靜態方法
        fun test() {

        }
    }
}

object 是在 Kotlin 中的用法。

  1. 在類中與 companion 一起,表示伴生對象。
  2. 聲明類。Kotlin 中除了用 class 聲明類以外,還可以用 object 來聲明一個類,用 object 聲明的類,天生單例。
object SystemUtils {
    ...
}
  1. 實現某個介面,或者抽象類:
interface ICount {
    fun count(num: Int): Int
}

val myCount  = object: ICount {
        override fun count(num: Int): Int {
            TODO("Not yet implemented")
        }
    }

3.7 可見性修飾符

  1. public 公開,可見性最大,哪裡都可以引用
  2. private 私有,可見性最小,根據聲明位置可以分為類中可見和文件中可見。
  3. protected 保護, 相當於 private + 子類可見。
  4. internal 內部,同一 module 可見

java 中 protected 表示包內可見 + 子類可見
Kotlin protected 的可見範圍收窄了,原因是 相比於包, Kotlin 更註重 module。

另外 private 修飾 java 中的內部類對外部類可見,Kotlin 中的內部類對外部類不可見。

Kotlin 中的類和方法,不寫時,預設是 public + final 的。

四、 Kotlin 的一些其它特性

4.1 函數參數預設值

fun sayHi(name: String = "world") = println("Hello, " + name)

重載函數再也不用寫很多個了。

4.2 本地函數(嵌套函數)

本地函數是個啥玩意?
我們知道在函數中可以聲明局部變數(這不是廢話嗎?)在 Kotlin 中我們甚至還可以在方法中類似聲明局部變數一樣聲明一個方法,稱之為本地函數。

fun login(user: String, password: String, illegalStr: String) {
    // 驗證 user 是否為空
    if (user.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
    // 驗證 password 是否為空
    if (password.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
    // 執行登錄
}

校驗參數這不部分代碼有些冗餘,我們可以抽成一個方法,但是我們又沒有必要暴露到其它地方,因為只有這個 login 方法會用到,則可以在 login 方法內部聲明一個嵌套函數:

fun login(user: String, password: String, illegalStr: String) {
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
    validate(user, illegalStr)
    validate(password, illegalStr)
    // 執行登錄
}

4.3 try-catch 表達式

Kotlin 中 try-catch 語句也可以是一個表達式,允許代碼塊的最後一行作為返回值

val a: Int? = try { 
    parseInt(input)
} catch (e: NumberFormatException) {
    null
}

4.4 == 和 ===

Kotlin 中用 == 判斷 equals 相等,對象內容相等
=== 判斷相等引用的記憶體地址相等

4.5 拓展函數和拓展屬性

顧名思義,可以給一個類“增加”額外的方法和屬性
舉個例子:

data class Student(val name: String)

這個類本身沒有“上課”這個方法,我現在給它增加一個拓展函數。

fun Student.attendClass() {
    println("${this.name} 正在上課")
}

這樣,Student 這個類的實例對象就可以調用這個拓展方法了。

val student = Student("Jimy")
student.attendClass()

拓展方法直接定義在文件中。
拓展屬性類似。
想一想:三方庫裡面的類,不能直接修改,是不是可以增加拓展方法和拓展屬性了?有沒有很激動。

4.6 Top-level

kotlin 文件已 .kt 為尾碼,在 Kotlin 文件中,我們可以直接聲明的變數,方法,也就是不在 class 內部聲明,稱之為 top-level declaration 頂層聲明。

// 屬於 package,不在 class/object 內
const val KEY = "123456"

fun test() {
    println("---test---")
}

它不屬於任何 class , 而是直接屬於 package, 它和靜態變數一樣是全局的,使用起來更方便,調用它的時候連類名都不用寫:

import com.chongjiu.demo.test
import com.chongjiu.demo.KEY

test()
val x = KEY

結合前面說的,寫工具類一般就有三種方式:

// companion object
class Util {
    companion object {
        fun test1() {

        }
        fun test2() {

        }
    }
}

// object
object Util {
    fun test1() {

        }
    fun test2() {

    } 
}

// top level 方法
fun test1() {
}
fun test2() {
} 

建議:

  • 如果想寫工具類的功能,直接創建文件,寫 top-level「頂層」函數。雖然沒有 class 的概念,但是相關的方法,寫在同一個文件中,方便閱讀管理。
  • 如果需要繼承別的類或者實現介面,就用 object 或 companion object。

4.7 by lazy (委托)

前面提到,如果一個屬性需要延遲初始化,可以使用 lateinit 進行修飾,另外還有另外一種方式,就是使用 by lazy 方法初始化。
在給一個變數賦值的時候使用 by lazy 代碼塊,可以做到單例,在第一次使用時初始化。by lazy 實際上使用了 Kotlin 的委托特性,底層原理和 DCL 的單例模式類似。

val student: Student by lazy {
    Student(name = "Lucy", age = 18)
}

這裡順便說一下用 Kotlin 實現單例,看看有多有多方便了。
前面提到,用 object 聲明一個類,即是單例的。

object Singleton

再看看 DCL 的方式,java 實現:

public class Singleton {
    private Singleton(){}

    private volatile static Singleton instance;

    private static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

再看看 Kotlin 實現

class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton()
        }
    }
}

這樣是不是很方便,當然上述方式也有一個缺點,就是獲取單例的時候沒有辦法傳參,那麼改為和 java 一樣的方式看看:

class Singleton private constructor() {
    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun getInstance(): Singleton {
            return instance?: synchronized(this) {
                instance?:Singleton().also {
                    // 初始化工作
                    instance = it
                }
            }
        }
    }
}

這次可以傳參進來了,代碼依然簡潔很多。

五、 More

Kotlin 其它內容還有很多:
數組
集合(可變集合、不可變集合、集合操作符)
協程
泛型(泛型類、泛型函數、逆變和協變)
註解
反射
委托
內聯函數、中綴函數等等
...

作者:SharpCJ     作者博客:http://joy99.cnblogs.com/     本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一說tp大多數人想到的是PHP使用tp,但今天不說PHP 說說c#使用tp 由於tp比較久遠 網上的資料又是少之又少 接下來說說tp的一些基本用法 1.首先就是數據綁定了 <%tp:foreach collection="{$model.Items}" var="m"%> <td>{$m.name} ...
  • [toc] # Linux運維工程師面試題(9) > 祝各位小伙伴們早日找到自己心儀的工作。 > 持續學習才不會被淘汰。 > 地球不爆炸,我們不放假。 > 機會總是留給有有準備的人的。 > 加油,打工人! ## 1 pod 的生命周期 第一階段: - Pending:正在創建 Pod 但是 Pod ...
  • 下麵的系列文章記錄瞭如何使用一塊linux開發扳和一塊OLED屏幕實現視頻的播放: 1) [項目介紹](https://www.cnblogs.com/kfggww/p/17672932.html) 2) [為OLED屏幕開發I2C驅動](https://www.cnblogs.com/kfggww ...
  • 為了讓程式能快點,特意瞭解了CPU的各種原理,比如多核、超線程、NUMA、睿頻、功耗、GPU、大小核再到分支預測、cache_line失效、加鎖代價、IPC等各種指標(都有對應的代碼和測試數據)都會在這系列文章中得到答案。當然一定會有程式員最關心的分支預測案例、Disruptor無鎖案例、cache ...
  • [TOC](【後端面經-資料庫】Redis數據結構和底層數據類型) 聲明:Redis的相關知識是面試的一大熱門知識點,同時也是一個龐大的體系,所涉及的知識點非常多,如果用一篇文章羅列,往往會陷入知識海洋中無法感知其全貌,因此,這段時間我會試著拆分Redis的相關章節,輔以思維導圖的形式介紹Redis ...
  • # pentaho使用 先展示一下用途和效果 ![image](https://jsd.cdn.zzko.cn/gh/YuanjunXu/Images@main/src/image.1gzusdgfiiao.webp) ## 1. 環境準備 ### 1.1 pentaho是什麼? > `pentah ...
  • 在這篇文章中,我將分享一次由於操作不當導致資料庫癱瘓的經驗。通過回顧故障發生的時間、系統簡介、時間線、問題分析和經驗總結等方面的內容。討論操作時間不當、操作流程不當、缺乏執行計劃和限流機制等問題,並提出一些建議,如確認資料庫更新時間、優化更新操作、使用限流工具、設置超時時間和重試機制、調整資料庫參數... ...
  • 最近研發apk校驗服務,很多游戲安裝包兩三個G,如果整個拿去校驗,耗時基本二十多秒,這還僅僅是校驗的時間,如果加上下載的時間,等待時間太長了 網上很多方案嘗試了一下,不太行 1、fast md5 一個第三方庫,csdn有人用過說可以提升40%的速度,然後我去試了一下,本來9秒可以完成的校驗,變成了2 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...