Kotlin修煉指南(三)——奇技淫巧

Kotlin做爲Android開發的首選語言,爲開發者提供了大量的語法糖和技巧,讓開發者能夠專一於需求開發,而將語言所帶來的影響減小到最少。Java和Kotlin最大的區別,實際上在於Kotlin的函數式編程思想以及語法,特別是lambda表達式,這是Kotlin效率高於Java開發的核心武器,在以前的文章中,已經有比較詳細的講解了。android

下面我將從幾個方面分別來給你們演示下Kotlin到底是如何提升開發效率的。git

語法糖

所謂語法糖,實際上就是對Java原生寫法進行的封裝,雖然不用也能寫,可是用了絕對回不去。github

字符串模版

"${xxxBean.type}"

字符串模版保證了String的完整性,這也是大部分現代語言都會有的功能,有了字符串模板,就能夠再也不使用+進行拼接,不但更方便,也讓字符串的語義更加明確。web

Raw string

val str1 = "abc"
val str2 = """value1\n
    value2
    value3
    "
value4"
    "
"".trimMargin()

三引號表明Raw string,即三引號內全部內容均爲string,即便有須要轉義的字符,也不用特殊處理。編程

使用trimMargin來去除每行開頭的空格設計模式

強化Switch

Kotlin中的when函數,解除了Java中的switch的不少限制,而且拓展了不少方便的功能。安全

fun getValue(a: Int) = when {
    a > 0 -> "A"
    a < 0 -> "N"
    a.hashCode() == 0x108 -> "XYS"
    else -> "ZJ"
}

在使用上更加靈活,同時讓選擇分支語句更加容易理解。微信

語句 Vs 表達式

kotlin中大部分關鍵字都是表達式。app

  • 表達式有值,而且能做爲另外一個表達式的一部分使用,這是函數式編程的基礎
  • 語句老是包圍着它的代碼塊中的代碼元素,而且沒有本身的值,例如Java中的if\else\Switch等。

在Kotlin中,一個if語句是能夠直接給一個變量賦值的,這就是表達式,它有返回值。編程語言

val status = when {}
    xxx -> {}
    xxx -> {}
    else -> {}
}

這種方式比Java節省了太多的代碼,因此Kotlin中再也不須要三目表達式了,直接經過if/else便可。

fun max(a: Int, b: Int) = if (a > b) a else b

延遲初始化

在Kotlin中,成員變量的值被嚴格區分可空和非可空,其中非可空的變量值,要麼在聲明的時候進行初始化,要麼經過延遲加載的方式進行初始化,通常來講,有兩種方式來進行延遲加載。

lazy

經過lazy函數,能夠實如今首次使用到的時候纔去實例化。

private val xxxxFragment by lazy {
    XXXXFragment().apply {
        arguments = Bundle().apply {
        }
    }
}

lateinit

經過lateinit,本身控制變量的初始化。

private lateinit var iv: ImageView

這兩種方式各有各的使用場景:

  • by lazy 修飾val的變量
  • lateinit 修飾var的變量,且變量是非空的類型

data class

data class是Kotlin中一個用來生成模板代碼的語法糖,在Java中,定義的實體類,一般會有不少的模板代碼,大部分狀況下,咱們都是經過一個工具插件來生成,而在Kotlin中,則更加簡單。

第一種方式其實是Kotlin對構造函數的優化,省略了構造函數的實體,直接經過參數聲明的方式進行了建立。

// Kotlin會爲類的參數自動實現get set方法
class User(val name: String, val age: Int, val gender: Int, var address: String)

第二種方式則是藉助data關鍵字,生成Kotlin中定義好的實體類。

// 用data關鍵詞來聲明一個數據類,除了會自動實現get set,同時還會自動生成equals hashcode toString
data class User(val name: String, val age: Int, val gender: Int, var address: String)

object

object在Kotlin中是一個比較難理解的概念,和Java中的Object徹底不一樣,後面會有單獨的文章來介紹object,這裏先簡單的看下Kotlin經過object提供的語法糖。

object,其實能夠把它理解成:定義一個類並建立該類的一個實例。

因此object的一個功能,就是快速建立一個單例模式。

例如在代碼中常常寫的:

object ThreadUtil {

    fun onMainThread(runnable: Runnable) {
        val mainHandler = Handler(Looper.getMainLooper())
        mainHandler.post(runnable)
    }
}

簡化下實際上就是下面的代碼。

object Singleton {

    fun xxx() {
    }
}

反編譯後看生成代碼,這就是一個典型的餓漢式單例,藉助靜態代碼塊初始化的鎖,初始化單例實例,從而實現單例效果。

public final class Singleton {
   public static final Singleton INSTANCE;

   public final void xxx() {
   }

   private Singleton() {
   }

   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

經過object代替匿名內部類

這是object的另外一個比較經常使用的地方,也符合了object的語義,定義一個類,並生成該類的實例,也就是須要建立的匿名內部類。

viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
    override fun onPageScrollStateChanged(state: Int) {}

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

    override fun onPageSelected(position: Int) {}
});

companion object

因爲Kotlin中沒有靜態函數,因此在Kotlin中,可使用companion object替代Java中的static修飾。

編譯器會自動生成了一個叫作Companion的靜態內部類。

在Java中調用伴生對象,可使用User.Companion.isMale(1)

class User {

    companion object {
        const val DEFAULT_USER_AGE = 30
    }
    
    fun test(){}
}

// later, accessed like you would a static variable:
user.age = User.DEFAULT_USER_AGE

Kotlin函數

在Kotlin的基礎庫中,系統提供了大量針對函數的優化,解決了不少在Java代碼中寫起來不太爽的地方。

顯式參數

在Java中,當一個函數的參數值太多時,須要一個個對齊參數,雖然能夠經過IDE的快捷提示等功能來展現,但始終用起來不太方便,而在Kotlin中,除了像Java中那樣按順序的傳遞參數外,還能夠經過指定參數名的方式進行參數傳遞。

fun test(name: String, age: Int) {
}

test(name = "xys", age = 18)

參數含義一目瞭然,提升了代碼的可讀性。

參數默認值

fun test(name: String = "xys", age: Int) {
}

fun a() {
    test(age = 18)
}

經過參數默認值,能夠避免Java下大量參數下的重載函數,當某個參數可使用默認值時,就不用顯示的聲明瞭,相似Java中的不一樣參數的重載函數。

在Java、Kotlin混編的時候,沒法避免的會混合調用,能夠經過@JvmOverloads註解,給Java代碼生成重載的函數。

拓展函數

拓展函數能夠說是Kotlin最爲重要的黑魔法之一了,經過拓展函數,能夠給一些系統類添加本來沒有的函數,極大的提升了函數的可拓展性。

fun Activity.toast(msg: String) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}

拓展屬性

與拓展函數相似,拓展屬性能夠給現有屬性拓展自定義的實現。

val String.lastChar: Char
    get() = get(length - 1)

拓展功能看上去比較神奇,但你們能夠經過查看Kotlin生成的class代碼,反編譯成的Java代碼來看它具體的實現方法。

對於擴展函數來講,轉化爲Java代碼的時候,其實就是生成一個靜態的函數,這個靜態函數的第一個參數就是該類的實例對象,因此這樣把類的實例傳入函數之後,函數內部就能夠訪問到類的公有方法。

擴展屬性也是相似,獲取的擴展屬性會生成爲一個靜態的get函數,同時這個靜態函數的第一個參數就是該類的實例對象,設置的擴展屬性會轉化爲一個靜態的set函數,同時這個靜態函數的第一個參數就是該類的實例對象。函數內部能夠訪問公有的方法和屬性。

在瞭解了其實現原理後,能夠發現,拓展函數必定是static的,且不能被override,也不存在運行時類型,其類型在編譯時就已經肯定,同時擴展函數和擴展屬性內只能訪問到類的公有方法和屬性,私有的和protected一樣是不能訪問的。

拓展函數和拓展屬性只是Kotlin語法的障眼法,並無實際的去修改一個類

嵌套函數

函數是Kotlin中的第一公民,因此函數能夠出如今Kotlin中的任何一個地方,包括在一個函數中。

在一個函數中定義另外一個函數,能夠很好的將這個函數的使用限制在當前的外層函數中,避免對外暴露沒必要要的接口,同時還能避免重複的模板代碼,例以下面這個例子。

class User(val id: Int, val name: String, val address: String, val email: String)

fun check(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Address")
    }
    if (user.email.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Email")
    }
    // ...
}

經過嵌套函數實現。

fun saveUser2(user: User) {
    fun validate(value: String, fildName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty $fildName")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
    validate(user.email, "Email")
    // ...
}

工具類函數

因爲在Kotlin中,函數能夠脫離類而獨立存在,因此這對於工具類函數來講,就很是方便了,不用再定義一個ToolUtil類,而能夠直接寫在文件中。

做用域函數

做用域函數在Kotlin修煉指南(一)中已經有詳細介紹了。

設計模式

設計模式最先是在面向對象編程的基礎上提出來的編程範式,可是對於函數式編程來講,有不少定義都過於教條了,因此,現代式的編程語言,經過不少語法上的定義,就已經實現了不少種設計模式。

單例模式

前面已經提到了,經過object class,就能夠很輕鬆的實現一個線程安全的單例類。

靜態工廠模式

藉助運算符重載,能夠很方便的實現靜態工廠模式。

interface Car {
    val brand: String

    companion object {
        operator fun invoke(type: CarType): Car {
            return when (type) {
                CarType.AUDI -> Audi()    
                CarType.BMW -> BMW()
            }
        }
    }
}

經過重載了invoke()函數,在調用Car(CarType.BMW)的時候,就建立好了對應的工廠實例。

代理模式 策略模式

代理模式,或者說策略模式,均可以經過Kotlin中的類委託來實現。

interface BaseTTS {
    fun doTTS()
}

class BaiDuTTS : BaseTTS {
    override fun doTTS() {
        print("BaiDu")
    }
}

class TencentTTS : BaseTTS {
    override fun doTTS() {
        print("Tencent")
    }
}

class TTSCategory(tts: BaseTTS) : BaseTTS by tts

fun doTest() {
    TTSCategory(BaiDuTTS()).doTTS()
}

經過類委託,將tts的實現代理出來。

更進一步,能夠經過匿名類的方式,直接建立代理類的實現。

interface BaseTTS {
    fun doTTS()
}

class TTSCategory(tts: BaseTTS) : BaseTTS by tts {
    override fun doTTS() {
        print("Do tts")
    }
}

而當策略中只有一個函數的時候,還能夠進一步簡化,把策略直接封裝成Lambda表達式。

class TTSCategory(val strategy: () -> Unit) {
    fun doTTS() {
        strategy.invoke()
    }
}

fun test() {
    TTSCategory { print("Do tts") }.doTTS()
}

裝飾器模式

一樣是經過類委託功能,還能夠實現裝飾器模式。

裝飾器模式是爲了解決繼承致使類行爲變動的問題產生的。若是須要在使用一個類的同時,又要修改該類的一些函數的實現,這時候就可使用裝飾器模式,建立一個裝飾器類,實現與原始類同樣的接口並將原來的類的實例做爲一個成員變量。裝飾器類與原始類擁有相同行爲的方法不用修改,只須要直接轉發給原始類的實例,須要修改的函數,實現新的功能便可。

但這裏的問題是,當一個原始類須要實現的函數不少時,而裝飾器類又只須要修改不多的函數時,就會產生大量的模板代碼,因此這個時候,藉助類委託,就能夠極大的減小這種模板代碼的產生。

class ListDecorator<T>(val innerSet: List<T> = listOf()) : List<T> by innerSet {
    override fun contains(element: T): Boolean {
        print("Do other thing")
        return innerSet.contains(element)
    }
}

fun test() {
    val contains = ListDecorator(listOf("ss")).contains("s")
}

經過反編譯代碼能夠發現,實際上編譯器幫助咱們重寫了全部的未修改函數。

後續計劃

Kotlin有趣的地方還有不少,一篇文章很難所有寫完,因此後面的計劃以下。

  • 集合與惰性序列
  • Kotlin DSL
  • 操做符重載
  • sealed class
  • KTX

修仙

Flutter Dojo開源至今,受到了不少Flutter學習者和愛好者的喜好,也有愈來愈多的人加入到Flutter的學習中來,因此我建了個Flutter修仙羣,可是人數太多,因此分紅了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指東】三個羣,對Flutter感興趣的朋友,能夠添加個人微信,註明加入Flutter修仙羣,或者直接關注個人微信公衆號【Android羣英傳】。

感興趣的朋友能夠加我微信【Tomcat_xu】,我拉你入羣。

項目地址:

https://github.com/xuyisheng/flutter_dojo


本文分享自微信公衆號 - Android羣英傳(android_heroes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息