Kotlin基礎篇:初階函數的使用,命名參數、局部函數、頂層函數、擴展函數

前言

上篇文章我們簡單的和大家介紹了一下變量和函數的聲明,這章我們繼續和大家嘮嘮函數的一些簡單使用。

參數默認值

Java有一個很普遍存在的問題是,有些類的重載函數太多了,這些重載函數往往是為了往后兼容,在實際開發中,也是有著同樣的問題,隨著需求的迭代,重載函數越來越多,使得我們維護成本和使用成本都在不斷的增加,下面讓我們看個具體的例子。

使用場景: 多數情況下,參數為固定值
例子:商品信息保存,商家在點擊保存按鈕后,我們需要把相關參數傳遞給后臺,以便下次用戶進來數據是被存儲的。
版本1.0 產品只需要保存商品名稱、簡介、價格:

private void saveProduct(String name, String price, String introduction) {
   //todo 上傳數據給后臺
}

saveProduct("短褲", "20.00", "YYYYY");
saveProduct("鴨舌帽", "10.00", "");

版本2.0產品需要在之前的基礎上,保存商品類型,且如果商品有優惠的話,需要保存商品折扣,那我們的代碼需要修改如下:

private void saveProduct(String name, String price, String introduction) {
    saveProduct(name, price, introduction, "", 1);
}
private void saveProduct(String name, String price, String introduction, String type, double discount) {
    //todo 上傳數據給后臺
}

 saveProduct("短褲", "20.00", "YYYYY");
 saveProduct("鴨舌帽", "10.00", "");
 saveProduct("短裙", "30.00", "ZZZZZ", "衣服", 0.8);

可以看到,對于沒有分類且沒有優惠的產品,我們調用的還是1.0版本的函數,有分類和優惠的產品我們使用2.0版本的函數,但對于只有(名稱、價格、折扣)的商品,我們有2種方式去調用,要不再寫一個重載函數(維護成本高),要不調用參數最全的2.0版本函數(會有很多無用參數,閱讀性很差

 saveProduct("鴨舌帽", "10.00", "","",0.5);

如果產品繼續迭代,每次迭代都會新增參數,那么迭代成本之高,我們是無法想象的,我們只能每次迭代都重載函數,如果不重載只修改原函數的話,那代價更高,我們就需要修改所有調用函數的地方。這種重載的問題真的是令人頭大,Java沒有好的處理方式,那Kotlin有嗎?老答案了,有的!下面就來看下Kotlin的默認參數值是如何優雅的解決這種問題的。

private fun saveProduct(
    name: String = "",
    price: String = "",
    introduction: String = "",
    type: String = "",
    discount: Double = 1.0
) {
    //todo 上傳數據給后臺
}

saveProduct("短褲", "20.00", "YYYYY") //等價于 saveProduct("短褲", "20.00", "YYYYY","",1.0)
saveProduct("鴨舌帽", "10.00", "","",0.5)
saveProduct("短裙", "30.00", "ZZZZZ","衣服",0.8)

我們給所有的參數都賦值了默認值,在Koltin中,如果我們使用常規的調用方法使用函數時,處在函數尾部的有默認值的參數,我們都是可以省略的,如商品“短褲”,調用函數時我們只傳入了3個參數,但實際調用剩余的參數都使用了函數中參數的默認值了。看了kotlin中的寫法,是不是要比Java方便了很多,完全沒有那么多的重載函數,我們只需修改原函數,給參數設置默認值,那么我們在調用時,就可以省略那些沒必要的參數了。

看到這兒,我們貌似用默認參數值就解決了函數重載的問題,但難免有些特殊場景的,比如后面迭代的需求中,

  • 特定場景1只有指定位置的參數是需要傳值的,別的位置上的參數使用默認值
  • 特定場景2我們需要新加的參數是沒有默認值的

這時候我們沒辦法使用省略具有默認值參數的方法來處理的,那我該如何處理呢,這時候,我們就可以使用Kotlin的命名參數來解決這種需求了。

命名參數

命名參數的主要功能就是提高代碼的可讀性,以及讓多參數函數的調用更簡易高效,使用方法也很簡單,在我們調用函數傳參的時候,帶上參數的名字即可,下面我們接著用上述的例子來看下如何用命名參數解決那些特殊場景。

使用場景: 參數過多且含義不清楚時;含有多個默認值參數時

還用商品“鴨舌帽”舉例,它只有商品名稱、價格、折扣三種屬性,如果我們用下面這種寫法

saveProduct("鴨舌帽", "10.00", "","",0.5)

是不是中間的2個空串參數是沒有任何意義的,因為我們已經給它們賦值了默認值,這里我們引用的時候,傳入的值也是和默認值一樣的,既然毫無意義,那我們是不是可以在調用函數時把它們隱藏呢?我們用命名參數試一下。

saveProduct("鴨舌帽","10.00",discount = 0.5)

我們會發現,使用命名參數,真的可以解決上述特定場景1的問題,省去一些無意義的傳參。

我們接著看下特定場景2的問題,如果我們使用常規的調用語法,因為最后一個參數是沒有默認值的,我們不能省略入參,那我們只能調用時候,把所有的參數都傳遞進去,這種情況我們就必須修改所有引用函數的地方了,這太麻煩了,但如果我們使用了命名參數給迭代后填加的沒有默認值的參數賦值的話,就完全避免了這種情況的發生。

private fun saveProduct(
    name: String,
    price: String,
    introduction: String = "",
    type: String = "",
    discount: Double
) {}

saveProduct("鴨舌帽","10.00",discount = 0.5)

命名參數除了讓我們函數調用更簡潔高效以外,另一大優點就是代碼可讀性了,

saveProduct("短裙", "30.00", "ZZZZZ", "衣服", 0.8)

這段代碼我們剛寫的時候,可能還是知道每個參數對應的商品屬性,但過段時間或者其他人讀代碼的時候,除了點擊函數查看你的參數命名,很難通過上面代碼知道每個參數的含義,但如果我們用了命名參數,代碼的可讀性就大大提高了。

saveProduct(
    name = "短裙",
    price = "30.00",
    introduction = "ZZZZZ",
    type = "衣服",
    discount = 0.8
)

//當然你也可以打亂參數順序,但不建議這么做
saveProduct(
    price = "30.00",
    name = "短裙",
    introduction = "ZZZZZ",
    discount = 0.8,
    type = "衣服"
)

這樣,別人在閱讀我們代碼的時候,通過參數的命名提示,也基本可以猜到每個參數的作用了。

有一點需要大家牢記一下:在調用函數時,如果使用了命名參數指明了一個參數的名稱,為了避免混淆,那它之后的所有參數都必須使用命名參數。

頂層函數

Java中,我們有很多Util類,這種工具類里面放了很多的靜態函數,我們會在不同的類中,引用這些靜態函數,Kotlin中我們不需要創建這種容器類,直接把函數放在代碼文件的頂層就可以了,即頂層函數,這些函數是和類不相關的。

使用場景: 一些類不相關的操作函數,且被多個類引用時,可聲明為頂層函數

Java寫法
class XxxUtil{
    public static void xxx(){   
    }
}

//引用
XxxUtil.xxx()


Kotlin寫法(只需放在文件的頂層就可以了)
fun xxx(){
}

//引用(在需要引用的文件中,包內直接引用,包外import這個方法就可以)
xxx()

Kotlin的寫法看起來只是比Java的少了XxxUtil這個歸屬類而已,那為什么更推薦使用頂層函數,那是因為,當我們項目越來越大時,協作的人員也會越來越多,相同的代碼,A同事可能會寫在XxxUtil里面命名xxx(),而B同事可能會放在YyyUtil里面命名xxx(),這樣,我們的項目就會有越來越多這種重復的代碼存在于不同的類中,造成不統一,當xxx()里面的邏輯發生變更時,我們不僅要修改XxxUtil還需要修改YyyUtil,但如果我們使用頂層函數xxx(),A創建了xxx()函數,B在使用時發現xxx()已經存在了,就不會再去創建一遍xxx()函數了,保證了代碼的簡潔和高效的使用。 頂層屬性頂層函數的設計原理&聲明、使用方法是一樣的,就不贅述了。

擴展函數

簡單的說,擴展函數就是一個類的成員函數,只不過它是定義在類的外部,它是類相關的。
Java 我們都寫過StringUtil,里面放了一系列的和String變量相關的操作,Kotlin中我們使用擴展函數來代替這些容器類。

使用場景: 一些類相關的操作函數,被多處頻繁引用時,可聲明為擴展函數

Java寫法
class StringUtil {
    public static char lastChar(String str) {
        return str.charAt(str.length() - 1);
    }
}
//引用
StringUtil.lastChar("張三")

Kotlin寫法(可以看到擴展方法是和當前類的其他成員函數沒有區別的,可以訪問類的成員變量、函數(私有的和受保護的除外)等,eg:length)
fun String.lastChar(): Char = this[length - 1]
//引用
"張三".lastChar()

擴展函數

擴展屬性擴展函數一個道理,但需要我們自己必須定義getter函數,因為原始類中沒有此字段,所以沒有默認的getter實現,如果你擴展的是var類型的,你同樣需要定義setter函數

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

var String.lastChar: Char
    get() = get(length - 1)
    set(value) {//todo 賦值}

擴展函數是靜態函數,所以是不可重寫的,要記住了。

局部函數

簡答點來說就是函數里面嵌套函數,嵌套的函數就叫局部函數,且局部函數可以訪問所在函數中的參數和變量,主要用途也是去除冗余重復的代碼。

使用場景:只在同一個方法中,使用了某一段重復代碼,可將重復代碼聲明為局部函數

Java寫法
 private void checkIsBlank(){
     if (TextUtils.isEmpty(textviewOne.getText())){
         throw new IllegalArgumentException("");
     }
     if (TextUtils.isEmpty(textviewTwo.getText())){
         throw new IllegalArgumentException("");
     }
     if (TextUtils.isEmpty(editText.getText())){
         throw new IllegalArgumentException("");
     }
 }

Java優化后代碼
 private void checkIsBlank(){
     checkTextView(textviewOne);
     checkTextView(textviewTwo);
     checkTextView(editText);
 }
 private void checkTextView(TextView view){
     if (TextUtils.isEmpty(view.getText())){
         throw new IllegalArgumentException("");
     }
 }

Kotlin寫法
fun checkIsBlank(){
    fun checkTextView(view: TextView){
        if (view.text.isNullOrBlank())
            throw IllegalArgumentException("")
    }
    checkTextView(textviewOne)
    checkTextView(textviewTwo)
    checkTextView(editText)
}

可以看到,Kotlin的寫法和Java優化后代碼相比,代碼量并沒有減少,那為什么我們推薦使用局部函數,而不推薦把重復代碼提取成一個獨立的函數呢?那是因為,在當前代碼文件中,我們只有checkIsBlank一個函數使用到了這段重復的代碼,別的函數并沒有任何相關邏輯代碼,所以使用局部函數的話,不僅讓重復代碼的用途和用處更明確了,函數相關性也大大提高了。

總結

  • 使用默認參數值和命名參數,大大降低了重載函數的必要性,且讓多參數函數的調用更加簡潔易讀。
  • 代碼片段A
    • A和具體對象類型(eg:String)相關,且被多個文件引用時,建議聲明為具體對象類型的擴展函數。
    • A和具體對象類型無關,且被多個文件引用時,建議聲明為頂層函數。
    • A和具體對象類型無關,只在一個文件的一個函數中多次引用,建議聲明為局部函數。
    • A和具體對象類型無關,只在一個文件的多個函數中多次引用,建議聲明為成員函數。

Kotlin中函數的初階使用,我們就先說到這啦,有什么地方我沒說明白的,可以留言哦!我是小院里栽顆樹

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。