那些会上瘾的Kotlin语法
Kotlin有毒,用过就上瘾……略有夸张,其实就用来写了个记录POI和轨迹的demo而已,不过感觉语法确实很简洁。在此总(板)结(书)一下Kotlin与Java的不一样,或者说比Java更简洁、优雅的语法和特性。
1.字符串模板
相比于Java的字符串拼接,Kotlin的字符串模板更紧凑:
fun main(args:Array<String>){
val name = if(args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
除了通过 {} 引用更复杂的表达式
fun main(args:Array<String>){
if(args.size > 0){
println("Hello, ${args[0]}!")
}
}
相比Java的字符串拼接,一连串的+,或者StringBuilder的append,Kotlin的字符串模板是不是显得优雅了许多。而且编译后的代码创建了一个StringBuilder对象,并把常量部分和变量部分附加上去,效率一样。
2.有值的表达式
在Kotlin中,if是表达式,不是语句,语句和表达式的区别在于,表达式有值,并且能作为另一个表达式的部分使用;而语句总是包围着它的代码块的顶层元素,并没有自己的值。在Java中,所有的控制结构都是语句,而在Kotlin中,除了循环(for、do和do/while)以外,大多数控制结构都是表达式。这种结合控制结构和其他表达式的能力,让你可以简明扼要地表示许多常见的模式。
另一方面,Java中的赋值操作是表达式,在Kotlin中反而变成了语句。这有助于避免比较和赋值之间的混淆,而这种混淆是常见的错误来源。
fun max(a: Int, b: Int): Int{
return if(a > b) a else b // 类似与Java的三目运算符 (a > b) ? a : b
}
有值的表达式非常有趣,你不需要像Java那样在每个if-else中返回一个值,或者设置一个值,它表达式本身就是一个值,没有了更多的赋值,看起来更加清爽。
3.比switch更简洁的when
与Java的switch语句对应的是when,与switch相比,when的语法不需要一个一个的case-break,when可以接受任意对象作为参数,也可以不带参数,同时when是有值的表达式:
fun getColor(color: Int) = when(color){
1 -> "Red"
2 -> "Green"
3 -> "Blue"
}
4.区间和数列
在Kotlin中,没有常规的Java for循环,而是用区间的概念代替这种最常见的循环用法。区间的本质就是两个值之间的间隔,这两个值通常是数字,一个起始值,一个结束值,使用..运算符来表示区间:
val oneToTen = 1..10
迭代区间的值:
for(i in 1..10){
println(i)
}
如果你能迭代区间中所有的值,这样的区间被称作数列
创建一个递减数列,步长为2:
for(i in 100 downTo 1 step 2){
}
创建一个半闭合区间:
for(x in 0 until 100){
}
// 等同于
for(x in 0..99){
}
5.命名参数和默认参数值
命名参数:
当调用一个Kotlin定义的函数时,可以显式地表明一些参数名称。如果在调用一个函数时,指明了一个参数的名称,为了避免混淆,那它之后的所有参数都需要表明名称。
默认参数值就是在声明函数的时候可以给参数指定一个默认值,这样可以避免创建重载的函数。这里举一个创建简单对话框的例子,考虑点击对话框之外的区域能关闭对话框,用Java实现,那么先写一个函数,有一个Boolean型的参数canceledOnTouchOutside,然后重载该函数,可以关闭对话框则赋值true,这样就多了一个重载函数。而有了Kotlin的默认参数值,只需要给参数默认值true就可以了:
/**
* 一个没有标题,只有提示和确定按钮的对话框,点击边缘会消失
* @param context
* @param question
* @param onOk
* @param canceledOnTouchOutside 默认点击对话框外部消失
*/
fun createSimpleDialog(context:Context,
question: String,
onOk: DialogInterface.OnClickListener?,
canceledOnTouchOutside: Boolean = true): AlertDialog{
val dialog = AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog_Alert)
.setMessage(question)
.setPositiveButton(context.getString(R.string.ok), onOk)
.create()
dialog.setCanceledOnTouchOutside(canceledOnTouchOutside)
return dialog
}
调用:
createSimpleDialog(activity, "Lambda实现",
DialogInterface.OnClickListener { dialog, which ->
}).show()
在调用的时候省略了canceledOnTouchOutside ,编译通过,函数使用了默认值。
当使用常规的调用语法时,必须按照函数声明中定义的顺序来给定参数,可以省略的只有排在末尾的参数。如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定你需要的参数。
6.顶层函数和属性
代替Java的静态方法是顶层函数,顶层函数不属于任何一个类,依然是包内的成员,通过import即可从包外访问。顶层属性的定义和顶层函数也一样,放到顶层文件即可。
7.扩展函数
扩展函数就是一个类的成员函数,不过定义在类的外面,还是按照正常的方法调用,如代码所示,String就是要扩展的类,称为接收者类型,用来调用这个扩展函数的那个对象,叫做接收者对象,接收者对象成员也可以不用this来访问:
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
// 调用
println("Kotlin".lastChar())
就是这么简单,为String类增加了一个lastChar()方法。
要使用扩展函数,需要像其它类或者方法一样进行导入
import strings.lastChar
val c = "Kotlin".lastChar()
实质上,扩展函数时静态函数,它把调用对象作为了它的第一个参数。调用扩展函数,不会创建适配的对象或者任何运行时的额外消耗。这使得从Java中调用Kotlin的扩展函数变得非常简单。
假设lastChar声明在一个叫StringUtil.kt的文件中:
char c = StringUtilKt.lastChar("Java")
最后,值得注意的是,扩展函数是不支持重写的。
扩展函数使不改变原有代码的情况下,增加类的功能成为可能,调用扩展函数就像调用类的函数一样。
8.局部函数和扩展
局部函数可以理解为在函数中定义的函数,使用局部函数可以减少很多重复的代码:
使用局部函数前:
class User(val id: Int, val name: String, val address: String)
fun saveUser(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")
}
// 保存user到数据库
}
使用局部函数:
fun saveUser(user: User){
fun validate(user: User, value: String, fieldName: String){
if(value.isEmpty()){
throw IllegalArgumentException("Can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// 保存user到数据库
}
将验证代码抽取出来,作为局部函数之后,看起来好看多了。不过还可以优化,那就是把验证逻辑提取到扩展函数中:
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforSave(){
fun User.validate(value: String, fieldName: String){
if(value.isEmpty()){
throw IllegalArgumentException("Can't save user $id: empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User){
user.validateBeforSave()
// 保存user到数据库
}
这样,验证函数完全不用写在User类中,如果你能遵守,类的API只能包含必需的方法,那么就可以让类保持精炼的同时,也让你的思路更加清晰。
9.安全调用运算符:”?.“
终于到了最喜欢没有之一的安全调用运算符:?.,方法和属性调用时再也不需要一连串的null判断了。假设这样一个类 User,有一个address属性,属于Address类,有一个属性cityName,现在有一个User类的引用,需要访问cityName,Java代码如下:
if(user != null && user.addrss != null){
println(user.address.cityName)
}
而Kotlin代码看起来则舒服多了:
println(user?.address?.cityName ?: "")
10.Elvis运算符(null合并运算符):”?:“
Elvis运算符接收两个运算数,如果第一个运算数不为null,则运算结果就是第一个运算数,否则时第二个运算数:
fun foo(s: String?){
val t: String = s ?: ""
}
Elvis运算符经常和安全调用运算一起使用,用一个值代替对null对象调用方法时返回的null:
fun strLenSafe(s: String?): Int = s?.length ?: 0
暂时到此为止,更多好用的Kotlin语法有待进一步发现!