约定的意义:就是让函数调用更加简洁。语法糖的一部分吧。
用一个是更简洁的符号调用,一个是特殊命名的函数。特殊命名是指Kotlin指定了和符号相对于的名字。
约定的方法都有一个关键字:operator。
举例:在类中定义了一个名为plus的方法,并且有operator关键字修饰,那么按照约定,你就可以在该类的实例上使用+运算符。
不知道有没有对你造成困扰。operator不是操作符重载的关键字吗?怎么又变成了什么“约定”的关键字了呢?
其实kotlin中的约定应用,包含以下几种,都用operator关键字定义。
1. 操作符号重载约定
2. 属性委托约定
3. 解构约定
4. invoke约定
5. Iterator约定
1. 操作符号重载约定
这个不再细讲了,有一点要注意,不像C++重载时重载函数的名字就是符号,但是kotlin是有映射表的。什么符号对应什么函数名字,比如,“+”对应的是“plus”。另外还有一些特殊一点的重载,比如,[]/get, contains
2. 属性委托约定
委托的关键词是by。by右边的对象,必须实现一个委托函数。这个函数用operator修饰。
委托函数需要返回一个初始化的对象。
具体详见上一篇:Kotlin学习笔记(八)by的作用,属性委托和类的委托,和Lazy的关系
3. 解构约定
可以把一个对象的属性值,一次性赋值给外面多个变量。
fun test() {
val point = Point(1,2)
val(x,y) = point //此时x=1, y=2
}
实现上面的效果,需要在Point类实现约定函数:
public final operator fun component1(): Float{ return x }
public final operator fun component2(): Float{ return y }
或者
把Point类声明称Data数据类,因为数据类在编译期,除了塞入:
equals()/hashCode() /toString() , 还会把属性按声明顺序对应一一声明componentN() 。
同时数据类必须满足以下要求:
- 主构造函数需要至少有一个参数(可以使用默认参数来实现无参主构造函数)
- 主构造函数的所有参数需要标记为 val 或 var
- 数据类不能是抽象、开放、密封或者内部的
如果说解构就上面举例那个作用,那就太莫名其妙了。一些有意义的作用在于:
- 遍历map
for ((key, value) in map) {
// 直接使用该 key、value
}
- 从函数中返回多个变量
创建返回信息的数据类,在调用方法获取返回信息。如果使用解构声明将其分成不同的值:
data class Result(val errorCode: Int, val message: Int,val result:String)
fun getHttpResponse(): Result {
return Result(resultCode, status,josnBody)
}
------------------------------------------------------------------
//获取返回值
val(errorCode, message, result) = getHttpResponse()
- 在 lambda 表达式中解构
和map遍历相识,就是将lambda中的Map.Entry参数进行解构声明:
val map = mapOf(1 to 1)
map.mapValues { (key, value) ->
"key = $key ,value = $value "
}
在lambda中,时常会看到_的使用。就是说这个参数,我现在不需要使用。这其实就是解构中的用法。如果需要时用下划线替代,要不然会得到不想要的属性值。
4.invoke约定
如果类使用operator声明了invoke(),则该类的对象就可以当做函数一样调用,即在变量后加上()。就相当于调用指定的方法了。
如果按照上面定义的意思,似乎也看不出来也有啥有意义的作用。
体现有意义关键在于,inovke函数可接受的参数类型也包括函数类型。
下面的代码:
class TestInvoke() {
operator fun invoke() {
println("invoke()")
}
operator fun invoke(value: Int) {
println("invoke value")
}
operator fun invoke(func: ()->Unit) {
func()
}
}
val test = TestInvoke()
test()
test(1)
test{
println("似乎比较有用的")
}
函数类型其实就是实现了FunctionN接口的匿名类,然后当函数类型是函数类型时,这时传递给它一个lambda,lambda就会被编译成FunctionN的匿名内部类(当然是非内联的),然后调用lambda就变成了一次FunctionN接口的invoke调用。就像上面的例子一样。
@SinceKotlin("1.3")
interface FunctionN<out R> : Function<R>, FunctionBase<R> {
/**
* Invokes the function with the specified arguments.
*
* Must **throw exception** if the length of passed [args] is not equal to the parameter count returned by [arity].
*
* @param args arguments to the function
*/
operator fun invoke(vararg args: Any?): R
/**
* Returns the number of arguments that must be passed to this function.
*/
override val arity: Int
}
5. Iterator约定
for
循环中可以使用in
运算符来表示执行迭代。这意味着Kotlin的for循环将被转换成list.iterator()
的调用,然后反复调用hasNext
和 next
方法。
iterator
方法也是Kotlin中的一种约定,这意味iterator()
可以被定义为扩展函数。例如:Kotlin标准库中为Java的CharSequence定义了一个扩展函数iterator
,使我们能遍历一个常规的Java字符串。
以上就是kotlin的约定。
引用: