全民 Kotlin:你沒有玩過的全新玩法

本文章已授權鴻洋微信公眾號轉載

目錄

  • 空安全

  • 方法支持添加默認參數

  • 方法上面的參數不可變

  • 類方法擴展

  • 函數變量

  • 內聯函數

  • 委托機制

    • 類委托

    • 屬性委托

    • 懶委托

  • 高階函數

    • let 函數

    • with 函數

    • run 函數

    • apply 函數

    • also 函數

  • 運算符重載

空安全

  • 在 Java 不用強制我們處理空對象,所以常常會導致 NullPointerException 空指針出現,現在 Kotlin 對空對象進行了限定,必須在編譯時處理對象是否為空的情況,不然會導致編譯不通過

  • 在對象不可空的情況下,可以直接使用這個對象

fun getText() : String {
    return "text"
}
val text = getText()
print(text.length)
  • 在對象可空的情況下,必須要判斷對象是否為空
fun getText() : String? {
    return null
}
val text = getText()
if (text != null) {
    print(text.length)
}
// 如果不想判斷是否為空,可以直接這樣,如果 text 對象為空,則會報空指針異常,一般情況下不推薦這樣使用
val text = getText()
print(text!!.length)
// 還有一種更好的處理方式,如果 text 對象為空則不會報錯,但是 text.length 的結果會等于 null
val text = getText()
print(text?.length)

方法支持添加默認參數

  • 在 Java 方法上,我們可能會為了擴展某個方法而進行多次重載
public void toast(String text) {
    toast(this, text, Toast.LENGTH_SHORT);
}

public void toast(Context context, String text) {
    toast(context, text, Toast.LENGTH_SHORT);
}

public void toast(Context context, String text, int time) {
    Toast.makeText(context, text, time).show();
}
toast("彈個吐司");
toast(this, "彈個吐司");
toast(this, "彈個吐司", Toast.LENGTH_LONG);
  • 但是在 Kotlin 上面,我們無需進行重載,可以直接在方法上面直接定義參數的默認值
fun toast(context : Context = this, text : String, time : Int = Toast.LENGTH_SHORT) {
    Toast.makeText(context, text, time).show()
}
toast(text = "彈個吐司")
toast(this, "彈個吐司")
toast(this, "彈個吐司", Toast.LENGTH_LONG)

方法上面的參數不可變

  • 在 Java 方法上面,我們可以隨意修改方法上面參數的賦值,但是到了 Kotlin 這里是不行的,Kotlin 方法參數上面的變量是 val (對應 Java 的 final)類型的,那么這個時候我們有兩種解決方案:

  • 第一種,在方法里面定義一個一模一樣的變量,具體寫法如下:

class XxxView : View {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var widthMeasureSpec: Int = widthMeasureSpec
        var heightMeasureSpec: Int = heightMeasureSpec
        
        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(30, MeasureSpec.EXACTLY)
        }

        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(30, MeasureSpec.EXACTLY)
        }
        
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
    }
}
  • 但是編譯器會報警告,提示我們出現了重復變量,但是仍可正常編譯和運行,所以不推薦這種寫法

  • 第二種,在方法里面定義一個不同名稱的變量,具體寫法如下:

class XxxView : View {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var finalWidthMeasureSpec: Int = widthMeasureSpec
        var finalHeightMeasureSpec: Int = heightMeasureSpec

        if (MeasureSpec.getMode(finalWidthMeasureSpec) == MeasureSpec.AT_MOST) {
            finalWidthMeasureSpec = MeasureSpec.makeMeasureSpec(30, MeasureSpec.EXACTLY)
        }

        if (MeasureSpec.getMode(finalHeightMeasureSpec) == MeasureSpec.AT_MOST) {
            finalHeightMeasureSpec = MeasureSpec.makeMeasureSpec(30, MeasureSpec.EXACTLY)
        }
        
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
    }
}
  • 其實就在原來的基礎上加一個 final 前綴,這樣不僅解決了編譯器警告的問題,還解決了我們還要重新想一個名稱來給變量命名的煩惱。

  • 那么肯定有人會問了,有沒有辦法像 Java 一樣改呢?關于這個問題我也糾結了一陣子,但是查閱了很多文檔和資料,最終發現并沒有辦法,所以只能妥協了,畢竟這個世界上沒有什么事物是完美的。

類方法擴展

  • 可以在不用繼承的情況下對擴展原有類的方法,例如對 String 類進行擴展方法
fun String.handle() : String {
    return this + "Android輪子哥"
}
// 需要注意,handle 方法在哪個類中被定義,這種擴展只能在那個類里面才能使用
print("HJQ = ".handle())
HJQ = Android輪子哥

函數變量

  • 在 Kotlin 語法中函數是可以作為變量進行傳遞的
var result = fun(number1 : Int, number2 : Int) : Int {
    return number1 + number2
}
  • 使用這個函數變量
println(result(1, 2))

內聯函數

  • 有人可能會問了,內聯函數是蝦米?我舉個栗子,用 Kotlin 編寫以下代碼
class Demo {

    fun test() {
        showToast("666666")
    }

    /**
     * 這個就是我們今天的主角:內聯函數了,用 inline 關鍵字來修飾
     */
    private inline fun showToast(message: String) {
        ToastUtils.show(message)
    }
}
  • 經過反編譯之后,會變成以下代碼:
/* compiled from: Demo.kt */
public final class Demo {

    public final void test() {
        ToastUtils.show("666666");
    }
}
  • 看到這里相信大家應該知道內聯函數的用法和作用了,內聯函數就是在編譯的時候將所有調用 inline 函數的代碼直接替換成方法里面的代碼,那么大家可能有疑問了,這樣做有什么實際好處呢?它其實提升了代碼的性能,這跟基本數據類型的常量會在編譯的過程中被優化一樣,但是如果 inline 函數被許多處地方調用,并且 inline 函數的實現代碼比較多的情況下,也會相應導致代碼量增加。

  • 另外上面的代碼示例中,編譯器在 inline 關鍵字上面有一個代碼警告,原話是這樣的:

Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types

內聯對性能的預期影響是微不足道的。內聯最適用于參數為函數類型的函數

  • 大致的意思是,上面的代碼示例中,內聯函數能起到的性能優化是微不足道的,它比較適合帶有 lambda 參數的函數,根據這個提示,將上面的代碼示例修改成下面這樣就不會報代碼警告了:
class Demo {

    fun test() {
    
        showToast({
            println("測試輸出了")
        }, "7777777")
    }

    private inline fun showToast(function: () -> Unit, message: String) {
        function.invoke()
        ToastUtils.show(message)
    }
}
  • 有人可能會好奇了,這樣就能有很大的性能提升?有什么判斷依據呢?接下來讓我們做一組實驗,加 inline 和不加 inline 反編譯出來的代碼有什么區別,先來看加 inline 之后反編譯出來的代碼長啥樣
/* compiled from: Demo.kt */
public final class Demo {

    public final void test() {
        System.out.println("\u6d4b\u8bd5\u8f93\u51fa\u4e86");
        ToastUtils.show("7777777");
    }
}
  • 一切都在預料之中,那么不加 inline 反編譯出來又是什么效果呢?
/* compiled from: Demo.kt */
public final class Demo {

    public final void test() {
        showToast(1.INSTANCE, "7777777");
    }

    private final void showToast(Function0<Unit> function, String message) {
        function.invoke();
        ToastUtils.show(message);
    }
}
/* compiled from: Demo.kt */
final class Demo$test$1 extends Lambda implements Function0<Unit> {
    public static final Demo$test$1 INSTANCE = new Demo$test$1();

    Demo$test$1() {
        super(0);
    }

    public final void invoke() {
        System.out.println("\u6d4b\u8bd5\u8f93\u51fa\u4e86");
    }
}
  • 很明顯,不加 inline 會導致多生成一個內部類,這個是 lambda 函數多出來的類,并且里面的示例還是靜態,這無疑會增加內存消耗,另外這樣還有另外一個好處,就是能少一層方法棧的調用。

  • 除了 inline (內聯)這個關鍵字,還有另外一個關鍵字:noinline(禁止內聯),大家可能到這里就摸不著頭腦了,這個有啥用?我不在方法上面寫 inline 不就是不會內聯了么?那么這個關鍵字是有什么作用呢?其實這個關鍵字不是修飾在方法上面的,而是修飾 在 lambda 參數上面的,假設一個 inline 函數上面有多個 lambda 參數,那么我只想對某個 lambda 參數內聯,其他 lambda 參數不內聯的情況下,就可以使用這個關鍵字來對不需要進行內聯的 lambda 參數進行修飾,大體用法如下:

private inline fun showToast(function1: () -> Unit, noinline function2: () -> Unit, message: String) {
    function1.invoke()
    function2.invoke()
    ToastUtils.show(message)
}

密封類

  • 大家看到這個詞語的時候,第一反應是,密封類是啥子?大家應該用過枚舉吧?先說枚舉類的幾個弊端,第一個枚舉值賦值是固定的(一旦賦值之后就不可變),第二個枚舉值的類型是固定的(類型只能是自己),密封類的出現正是為了解決這兩個問題,具體用法如下:
sealed class Result {

    // 定義請求成功
    data class SUCCESS(val data: String) : Result()

    // 定義請求失敗
    data class FAIL(val throwable: Throwable) : Result()
}
val result = if (AppConfig.isDebug()) {
    Result.SUCCESS("模擬后臺返回數據")
} else Result.FAIL(IllegalStateException("模擬請求失敗了"))

when (result) {
    is Result.SUCCESS -> {
        println(result.data)
    }
    is Result.FAIL -> {
        println(result.throwable)
    }
}
  • 從這里可以看到,密封類和枚舉類很類似,但是比枚舉類更加強大,枚舉可以是任意類型(SUCCESS 類型或者 FAIL 類型),一個枚舉值可以有很多種結果(FAIL 類中的 throwable 參數是外層傳入的,而不是像枚舉一樣只能固定在內部)。

委托機制

類委托
  • 先讓我們來看一段代碼
// 定義日志策略接口
interface ILogStrategy {

    fun log(message: String)
}
// 實現一個默認的日志策略類
class LogStrategyImpl : ILogStrategy {

    override fun log(message: String) {
        Log.i("測試輸出", message)
    }
}
// 創建一個日志代理類
class LogStrategyProxy(strategy: ILogStrategy) : ILogStrategy by strategy
  • 看到這里大家可能有一些疑惑

    • ILogStrategy by strategy 是蝦米操作?

    • LogStrategyProxy 這個類不去實現接口方法難道不會導致編譯不通過么?

  • 關于這兩個問題,我覺得都可以用同一個解釋,LogStrategyProxy 之所以不用實現 ILogStrategy 的 log 方法,是因為在 ILogStrategy 接口后面加了 by strategy,而 strategy 對象就是 LogStrategyProxy 構造函數中的變量,意思是讓這個接口的具體實現由 strategy 對象幫我實現就可以了,我(LogStrategyProxy 類)不需要再實現一遍了,這樣是不是跟 Java 中的靜態代理很像?只不過在 Kotlin 類委托特性上面編譯器幫我們自動生成接口方法的代碼,你可以把它想象下面這樣的代碼:

class LogStrategyProxy(val strategy: ILogStrategy) : ILogStrategy {

    override fun log(message: String) {
        strategy.log(message)
    }
}
  • 有人肯定會問了:口說無憑,我憑什么相信你就是這樣的代碼?

  • 這是個好問題,我提供一下反編譯之后的代碼,大家看一下就能明白了:

public final class LogStrategyProxy implements ILogStrategy {

    private final /* synthetic */ ILogStrategy $$delegate_0;
    
    public LogStrategyProxy(@NotNull ILogStrategy strategy) {
        Intrinsics.checkNotNullParameter(strategy, "strategy");
        this.$$delegate_0 = strategy;
    }

    public void log(@NotNull String message) {
        Intrinsics.checkNotNullParameter(message, "message");
        this.$$delegate_0.log(message);
    }
}
  • 是不是就立馬頓悟了?調用的話也很簡單,代碼如下:
val logStrategyImpl = LogStrategyImpl()

LogStrategyProxy(logStrategyImpl).log("666666")
  • 最后讓我們看看輸出的日志:
測試輸出: 666666
  • 這個我突然有一個大膽的想法,在使用類委托的情況下,再去重寫它的接口方法呢?例如下面的:
class LogStrategyProxy(strategy: ILogStrategy) : ILogStrategy by strategy {

    override fun log(message: String) {
        println("測試輸出 " + message)
    }
}
  • 關于這個問題我已經做過實踐了,是木有問題的,大家放心大膽搞。
屬性委托
  • 看過了上面的類委托,想必大家對委托有一定的了解了,那么屬性委托是什么呢?簡單來講,類委托是為了幫我們減少一些實現代碼,而屬性委托是為了幫我們控制變量的 Get、Set 的操作了,廢話不多說,下面演示一下用法,下面先創建一個委托類
class XxxDelegate {

    // 先給它一個默認值
    private var currentValue: String = "666666"

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("測試字段名為 ${property.name} 的變量被訪問了,當前值為 $currentValue")
        return currentValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        currentValue = newValue
        println("測試字段名為 ${property.name} 的變量被賦值了,當前值為 $currentValue" + ",新的值 $newValue")
    }
}
  • 使用代碼示例如下:
var temp: String by XxxDelegate()
println("測試輸出 " + temp)
temp = "55555"
println("測試輸出 " + temp)
  • 具體日志輸出如下:
System.out: 測試字段名為 temp 的變量被訪問了,當前值為 666666
System.out: 測試輸出 666666
System.out: 測試字段名為 temp 的變量被賦值了,當前值為 55555,新的值 55555
System.out: 測試字段名為 temp 的變量被訪問了,當前值為 55555
System.out: 測試輸出 55555
  • 看到這里你是否明白了,這個 XxxDelegate 類里面只有兩個方法,一個是 getValue,另外一個是 setValue,從方法命名上我們已經能大致得出它的作用了,這里就不再多講解了,var temp: String by XxxDelegate() 表示這個 temp 對象的創建會全權委托給 XxxDelegate 這個類來做。
懶委托
  • 什么是懶委托呢?大家知道單例模式中的懶漢式吧?這個跟它差不多,只不過我們不需要寫靜態方法和鎖機制了,只需要像下面這樣寫:
val temp: String by lazy {
    println("測試變量初始化了")
    return@lazy "666666"
}
  • 調用代碼如下:
println("測試開始")
println("測試第一次輸出 " + temp)
println("測試第二次輸出 " + temp)
println("測試結束")
  • 輸出日志如下:
System.out: 測試開始
System.out: 測試變量初始化了
System.out: 測試第一次輸出 666666
System.out: 測試第二次輸出 666666
System.out: 測試結束
  • 是不是真的跟懶漢式差不多?只不過這種寫法簡化了很多,另外在日常開發中我們可以用它來做 findViewById 是最適合不過的。
private val viewPager: ViewPager? by lazy { findViewById(R.id.vp_home_pager) }
  • 另外懶委托還提供了幾種懶加載模式供我們選擇,

    • LazyThreadSafetyMode.SYNCHRONIZED:同步模式,確保只有單個線程可以初始化實例,這種模式下初始化時線程安全的,當 by lazy 沒有指定模式的時候,就是默認用的這種模式。

    • LazyThreadSafetyMode.PUBLICATION:并發模式,在多線程下允許并發初始化,但是只有第一個返回的值作為實例,這種模式下是線程安全的,和 LazyThreadSafetyMode.SYNCHRONIZED 最大區別是,這種模式在多線程并發訪問下初始化效率是最高的,本質上面是用空間換時間,哪個的線程執行快就讓哪個先返回結果,其他線程執行的結果拋棄掉。

    • LazyThreadSafetyMode.NONE:普通模式,這種模式不會使用鎖來限制多線程訪問,所以是線程不安全的,所以請勿在多線程并發的情況下使用。

  • 具體使用的方式也很簡單,如下:

val temp: String by lazy(LazyThreadSafetyMode.NONE) {
    println("測試變量初始化了")
    return@lazy "666666"
}
  • 另外有一點需要注意,使用懶委托的變量必須聲明為 val(不可變的),因為它只能被賦值一次。

高階函數

  • 高階函數是 Kotlin 用于簡化一些代碼的寫法,提升代碼可讀性而產生的,其中有 let、with、run、apply、also 五個常用函數
let 函數
  • 在函數塊內可以通過 it 指代該對象。返回值為函數塊的最后一行或指定 return 表達式

  • 一般寫法

fun main() {
    val text = "Android輪子哥"
    println(text.length)
    val result = 1000
    println(result)
}
  • let 寫法
fun main() {
    val result = "Android輪子哥".let {
        println(it.length)
        1000
    }
    println(result)
}
  • 最常用的場景就是使用let函數處理需要針對一個可 null 的對象統一做判空處理
videoPlayer?.setVideoView(activity.course_video_view)
videoPlayer?.setControllerView(activity.course_video_controller_view)
videoPlayer?.setCurtainView(activity.course_video_curtain_view)
videoPlayer?.let {
   it.setVideoView(activity.course_video_view)
   it.setControllerView(activity.course_video_controller_view)
   it.setCurtainView(activity.course_video_curtain_view)
}
  • 又或者是需要去明確一個變量所處特定的作用域范圍內可以使用
with 函數
  • 前面的幾個函數使用方式略有不同,因為它不是以擴展的形式存在的。它是將某對象作為函數的參數,在函數塊內可以通過 this 指代該對象,返回值為函數塊的最后一行或指定 return 表達式

  • 定義 Person 類

class Person(var name : String, var age : Int)
  • 一般寫法
fun main() {
    var person = Person("Android輪子哥", 100)
    println(person.name + person.age)
    var result = 1000
    println(result)
}
  • with 寫法
fun main() {
    var result = with(Person("Android輪子哥", 100)) {
        println(name + age)
        1000
    }
    println(result)
}
  • 適用于調用同一個類的多個方法時,可以省去類名重復,直接調用類的方法即可,經常用于 Android 中 RecyclerView.onBinderViewHolder 中,數據 model 的屬性映射到 UI 上
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年齡:${item.age}"
}

override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    with(item){
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年齡:$age"
    }
}
run 函數
  • 實際上可以說是 let 和 with 兩個函數的結合體,run 函數只接收一個 lambda 函數為參數,以閉包形式返回,返回值為最后一行的值或者指定的 return 的表達式

  • 一般寫法

var person = Person("Android輪子哥", 100)
println(person.name + "+" + person.age)
var result = 1000
println(result)
  • run 寫法
var person = Person("Android輪子哥", 100)
var result = person.run {
    println("$name + $age")
    1000
}
println(result)
  • 適用于 let,with 函數任何場景。因為 run 函數是let,with兩個函數結合體,準確來說它彌補了 let 函數在函數體內必須使用 it 參數替代對象,在 run 函數中可以像 with 函數一樣可以省略,直接訪問實例的公有屬性和方法,另一方面它彌補了 with 函數傳入對象判空問題,在 run 函數中可以像l et 函數一樣做判空處理,這里還是借助 onBindViewHolder 案例進行簡化
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年齡:${item.age}"
}

override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    item?.run {
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年齡:$age"
    }
}
apply 函數
  • 從結構上來看 apply 函數和 run 函數很像,唯一不同點就是它們各自返回的值不一樣,run 函數是以閉包形式返回最后一行代碼的值,而 apply 函數的返回的是傳入對象的本身

  • 一般寫法

val person = Person("Android輪子哥", 100)
person.name = "HJQ"
person.age = 50
  • apply 寫法
val person = Person("Android輪子哥", 100).apply {
    name = "HJQ"
    age = 50
}
  • 整體作用功能和 run 函數很像,唯一不同點就是它返回的值是對象本身,而 run 函數是一個閉包形式返回,返回的是最后一行的值。正是基于這一點差異它的適用場景稍微與 run 函數有點不一樣。apply 一般用于一個對象實例初始化的時候,需要對對象中的屬性進行賦值。或者動態 inflate 出一個 XML 的 View 的時候需要給 View 綁定數據也會用到,這種情景非常常見。特別是在我們開發中會有一些數據 model 向 View model 轉化實例化的過程中需要用到
mRootView = View.inflate(activity, R.layout.example_view, null)
mRootView.tv_cancel.paint.isFakeBoldText = true
mRootView.tv_confirm.paint.isFakeBoldText = true
mRootView.seek_bar.max = 10
mRootView.seek_bar.progress = 0
  • 使用 apply 函數后的代碼是這樣的
mRootView = View.inflate(activity, R.layout.example_view, null).apply {
   tv_cancel.paint.isFakeBoldText = true
   tv_confirm.paint.isFakeBoldText = true
   seek_bar.max = 10
   seek_bar.progress = 0
}
  • 多層級判空問題
if (sectionMetaData == null || sectionMetaData.questionnaire == null || sectionMetaData.section == null) {
    return;
}
if (sectionMetaData.questionnaire.userProject != null) {
    renderAnalysis();
    return;
}
if (sectionMetaData.section != null && !sectionMetaData.section.sectionArticles.isEmpty()) {
    fetchQuestionData();
    return;
}
  • kotlin 的 apply 函數優化
sectionMetaData?.apply {

    // sectionMetaData 對象不為空的時候操作sectionMetaData

}?.questionnaire?.apply {

    // questionnaire 對象不為空的時候操作questionnaire

}?.section?.apply {

    // section 對象不為空的時候操作section

}?.sectionArticle?.apply {

    // sectionArticle 對象不為空的時候操作sectionArticle

}
also 函數
  • also 函數的結構實際上和 let 很像唯一的區別就是返回值的不一樣,let 是以閉包的形式返回,返回函數體內最后一行的值,如果最后一行為空就返回一個 Unit 類型的默認值。而 also 函數返回的則是傳入對象的本身
fun main() {
    val result = "Android輪子哥".let {
        println(it.length)
        1000
    }
    println(result) // 打印:1000
}
fun main() {
    val result = "Android輪子哥".also {
        println(it.length)
    }
    println(result) // 打印:Android輪子哥
}
  • 適用于 let 函數的任何場景,also 函數和 let 很像,只是唯一的不同點就是 let 函數最后的返回值是最后一行的返回值而 also 函數的返回值是返回當前的這個對象。一般可用于多個高階函數鏈式調用

運算符重載

  • 在 Kotlin 中使用運算符最終也會調用對象對應的方法,我們可以通過重寫這些方法使得這個對象支持運算符,這里不再演示代碼
運算符 調用方法
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
運算符 調用方法
a++ a.inc()
a-- a.dec()
運算符 調用方法
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b), a.mod(b) (deprecated)
a..b a.rangeTo(b)
運算符 調用方法
a in b b.contains(a)
a !in b !b.contains(a)
運算符 調用方法
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ..., i_n] a.get(i_1, ..., i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)
運算符 調用方法
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)
運算符 調用方法
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b), a.modAssign(b) (deprecated)
運算符 調用方法
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))
運算符 調用方法
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

下一篇:全民 Kotlin:協程特別篇

另外推薦一個 Kotlin 語言編寫的開源項目,大家感興趣可以看看:AndroidProject-Kotlin

Android 技術討論 Q 群:10047167

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容