前言
上篇文章我们简单的和大家介绍了一下变量和函数的声明,这章我们继续和大家唠唠函数的一些简单使用。
参数默认值
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中函数的初阶使用,我们就先说到这啦,有什么地方我没说明白的,可以留言哦!我是小院里栽颗树