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中函数的初阶使用,我们就先说到这啦,有什么地方我没说明白的,可以留言哦!我是小院里栽颗树

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351