3.1 命名参数和默认参数
val strings = listOf("1", "2", "3", "4", "5")
println(strings)
>> [1, 2, 3, 4, 5]
上面一段代码打印了集合strings中的元素,kotlin对打印api做了处理,结果看起来很友好。那么如果我们要自定义打印的格式该怎么办?当然是写一个方法并对集合循环拼接打印,如下代码段
fun printStrings(prefix: String, suffix: String, split: String, strings: List) {
val sb = StringBuilder(prefix)
//带下标的for循环
for ((index, element) in strings.withIndex()) {
if (index > 0)
sb.append(split)
sb.append(element)
}
sb.append(suffix)
println(sb)
}
printStrings("{", "}", ";", strings)
>>{1;2;3;4;5}
上面功能是实现了,但我们发现还是有问题的。
-
首先,调用者往往不知道具体参数的含义,用kotlin中的命名参数即可解决。
//使用命名参数指定函数含义(即方法的形参名) printStrings("{",suffix = "}",split = ".",strings = strings)
命名参数的使用
规则:如果在掉用一个参数时,显示的指明了一个参数的名称,为了避免混淆,那它之后的所有参数都要标明名称。
再次,我们调用函数时往往并不需要指定每个参数的值。在java中我们可以写一大堆重载函数解决,工作繁琐而重复。现在kotlin中有了更好的方式,那就是使用命名参数和默认参数来优化函数的调用。如下代码
//为 prefix suffix split 均指定默认值
fun printStrings2(prefix: String = "{", suffix: String = "}", split: String = "*",strings: List) {
val sb = StringBuilder(prefix)
for ((index, element) in strings.withIndex()) {
if (index > 0) {
sb.append(split)
}
sb.append(element)
}
sb.append(suffix)
println(sb.toString())
}
默认参数值定义
kotlin在声明函数时,可以为参数赋值,即指定这个参数的默认值。这就叫做默认参数值
这样做的好处是?还是这个函数,看一下调用方式就知道了
//使用命名参数指定 集合
printStrings2(strings = strings)
//使用命名参数指定 前缀 后缀 以及需要使用的集合
printStrings2(prefix = "X",suffix = "C",strings = strings)
如上我们在给定了默认参数值后,便可以省略一些参数。具体规则如下
- 常规方式调用时,必须按照函数声明中定义的参数顺序来给定参数,可以省略排在末位的参数
- 使用命名参数时,可以省略调中间的参数,并且可以按照任意顺序给定需要的参数
3.2 顶层函数和属性
顶层函数
在安卓开发中,我们会将一些通用的功能提取封装成静态方法写在工具类中,项目中会有不少的xxUtils.java类。而现在在Kotlin中我们不必在写这种工具类了,取而代之的就是用顶层函数,顾名思义就是直接写在kotlin文件中的函数。
Kotlin中调用,顶层函数是包内成员,包外访问时只要导入即可
-
Java中调用
Kotlin文件在编译时,会编译成Kotlin文件名的类,而其顶层函数会编译成这个类的静态函数
依然以上一节的函数为例,在Java代码中调用方式为
Fundemo1Kt.printStrings("(",")",";",datas);
Kotlin文件编译生成的类的名字在包名前用以下语句指定
@file:JvmName("ChangeNameTest")
package com.m1Ku.kt03
此时Java文件中再调用
ChangeNameTest.printStrings("(",")",";",datas);
顶层属性
顶层属性放在Kotlin文件的顶层,可以用来计算函数执行的次数,以及定义常量等等
以下写法会把一个常量以public static final 的形式暴露给Java
const val BASE_URL = "www.baidu.com"
3.3 扩展函数和属性
扩展函数
扩展函数,顾名思义即可以对类进行扩展,为其添加成员函数。但这个扩展函数是定义在这个类外面的。
fun Context.toast(msg:String,length:Int = Toast.LENGTH_SHORT) {
Toast.makeText(this,msg,length).show()
}
上面代码中,我们为Context定义了一个扩展函数,在Activity中我们便可以直接调用这个弹土司的函数
toast("扩展函数弹的toast")
定义语法
把要扩展的类或者接口的名称,放在即将添加的函数的前面,这个类的名称被称为接受者类型;用来调用这个扩展函数的那个对象,称为接受者对象
扩展函数可以调用类中的其他方法和属性,但是不能访问私有的成员和受保护的成员
在Java中的调用
扩展函数定义在文件顶层的,同样会被编译成静态函数。java文件中调用时把接收者对象作为第一个参数传入即可
public class OneActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//第一个参数就是接收者对象,上面的扩展函数接受者类型为Context
MainActivityKt.toast(this,"haha", Toast.LENGTH_LONG);
}
}
注意:由于Kotlin把扩展函数编译为静态函数,所以扩展函数是不能被重写的
扩展属性
可以为类定义一个扩展属性,并为其提供getter和setter方法,这样我们可以像访问接收者普通成员属性一样访问它
为String定义属性,并定义getter方法获取字符串的最后一个字符
val String.lastChar: Char
get() = this.get(length - 1)
当然定义为var可变类型的属性我们可以为其提供setter方法
var StringBuilder.lastChar: Char
get() = this.get(length - 1)
set(value) = setCharAt(length - 1, value)
3.4 可变参数和中缀调用
可变参数
Kotlin中可变参数用vararg修饰符来修饰
fun talk(vararg values:String){
}
注意:java的可变参数可以传入数组,但是Kotlin中必须显示的解包数组传入,在对应的参数前使用解包操作符*既可完成该操作
fun main(args: Array) {
talk(*args)
}
解构声明
将一个对象解构成多个变量,这样的形式叫解构声明
//解构对象
data class Animal(val type:String = "2",val age:Int = 20)
val(type,age) = Animal()
//解构pair
val (number,name) = 1 to "one"
//解构list with index
val strings = listOf("1","3","5","8","9")
for ((index,element) in strings.withIndex()){
println("index = $index -> element = $element")
}
3.5 局部函数
函数内部定义函数,这样可以解决常见的代码重复问题。在局部函数中,可以访问所在函数所有参数和变量
如下代码,把检查非空的操作提取成局部函数,节省了重复代码
fun saveAnimal2Db(animal: Animal) {
fun validateBeforeSave(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("${animal.id}的$fieldName 值为空")
}
}
validateBeforeSave(animal.type, "type")
validateBeforeSave(animal.height, "height")
//保存
println("保存成功")
}
再结合扩展函数的使用,效果更佳:)
fun saveAnimal2Db(animal: Animal) {
animal.validateBeforeSave()
//保存
println("保存成功")
}