Kotlin杂谈系列八
-
重载运算符
重载运算符的特性允许用户定义的类可以利用运算符操作
要重载运算符 要满足两个条件
- 一个运算符 对于一个专用的函数名
- 该函数的定义必须使用operator来修饰
-
重载运算符的注意事项
-
请谨慎使用
-
只有当用途对读者来说看起来很明显时才重载
-
遵守运算符的通常理解的行为
-
对变量使用有意义的名称,这样更容易理解重载的上下文
-
-
重载运算符的专用名对应表
运算符 | 专用函数名 | 注意事项 |
---|---|---|
+x | x.unaryPlus() | |
-x | x.unaryMinus() | |
!x | x.not() | |
x + y | x.plus(y) | |
x - y | x.minus(y) | |
x * y | x.times(y) | |
x / y | x.div(y) | |
x % y | x.rem(y) | |
++x | x.inc() | x必须是可赋值的 |
x++ | x.inc() | x必须是可赋值的 |
–x | x.dec() | x必须是可赋值的 |
x– | x.dec() | x必须是可赋值的 |
x == y | x.equals(y) | |
x != y | !(x.equals(y)) | |
x < y | x.compareTo(y) | |
x[ i ] | x.get(i) | |
x[ i ] = y | x.set(i,y) | |
y in x | x.contains(y) | |
x … y | x.rangeTo(y) | |
x() | x.invoke() | |
x(y) | x.invoke(y) |
-
以上的操作必须保持是纯操作 除了那几个自增的运算 其他的运算符都不得改变操作数的状态
-
复合运算符 即使在专用名称的后面加上 Assign 如 += : plusAssign()
-
但是你如果实现了plus的方法 就不用实现plusAssign的方法了 因为在调用 += 的时候会适当的使用 plus方法
class Num(val data : Int){
operator fun plus(num : Num) : Num{
return Num(this.data + num.data)
}
override fun toString(): String {
return this.data.toString()
}
}
fun main() {
var num = Num(1)
num += Num(3)
println(num)
}
//输出 : 4
class Num(val data : Int){
operator fun plus(num : Num) : Num{
return Num(this.data + num.data)
}
override fun equals(other: Any?): Boolean {
return this.data == (other as Num).data
}
override fun toString(): String {
return this.data.toString()
}
}
fun main() {
var num = Num(1)
num += Num(3)
println(num != Num(4))
}
-
同样的实现了重载等于的运算符 就可以使用不等于(!=)了
-
扩展函数
我们可以在类的外部给他们注入一个方法和属性的假象本质是一个静态方法 因此属性没有幕后字段 因为是一个相对于类外部的静态方法 所以就不能像实例方法一样可以访问到全部的属性和方法了
fun Num.getData() = this.data //Error
- 所以这就是扩展方法的局限性
- 另外呢 如果扩展方法和实例方法发生冲突时 优先实例方法
- 但是有例外 那就是第三方类 不是我们定义的类是引入不是直接在我们的应用程序中的类(我的理解可能不对)
fun String.toLowerCase() = toUpperCase()
fun main() {
"xixi".toLowerCase().run(::println)
}
//输出 : XIXI (是不是很意外)
-
这个不是绝对不能用的,只是这里提一下 要是你用了的话就得完犊子了 看你代码的人不骂死你
-
扩展方法在一个类里面
如果扩展方法在一个类里面的话 那么他的作用域就只能在这个类里面了 在类的外部是访问不到的
class Person(val name : String){
fun Person.desc(name : String){
println("$name ~~~")
}
override fun toString(): String {
desc(name)
return super.toString()
}
}
fun main() {
Person("json").desc()//报错: Unresolved reference: desc
}
- 其实函数类型(其实例是函数)在kotlin中也是一种类型 既然是类型就是类 那么就可以像类中注入扩展方法一样在函数中注入扩展方法
fun ((Int)->Int).desc(){
println("~${this(1)}~")
}
fun f1(data : Int) : Int{return 1}
fun main() {
val f2 = ::f1
f2.desc()
}
//输出 : 1
-
注入静态方法
回想一下 kotlin 实现静态方法的是依赖于什么 没错当然就是伴生对象所以我们只要在一个类的伴生对象中添加方法不就是静态方法了吗
fun String.Companion.hi(){
println("hi~:$this~~")
}
fun main() {
String.hi()
}
- 注意如果方法不是注入到对应类的对应的伴生对象中的话扩展方法就是实例的方法
- 想要扩展方法到伴生对象里前提得有伴生对象
public class String : Comparable<String>, CharSequence {
/*这个就是伴生对象*/
companion object {}
/**
* Returns a string obtained by concatenating this string with the string representation of the given [other] object.
*/
public operator fun plus(other: Any?): String
public override val length: Int
/**
* Returns the character of this string at the specified [index].
*
* If the [index] is out of bounds of this string, throws an [IndexOutOfBoundsException] except in Kotlin/JS
* where the behavior is unspecified.
*/
public override fun get(index: Int): Char
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence
public override fun compareTo(other: String): Int
}
-
这是kotlin对java中String 的扩展
-
中缀函数让kotlin的流畅性更佳
-
中缀函数很简单 就是将调用函数的点和括号去掉了 再在普通的函数前面加上infix就可以了 而且是能是成员方法或者扩展方法
-
但是作为简单的代价是他有且仅有有一个参数 而且不能是vararg修饰的参数
class test10 {
infix fun f1(name : String){
println("$this $name")
}
}
fun main() {
test10() f1 "async"
}
-
这里简单演示语法并不代表可以这么用
-
四个作用域函数给我们的代码的好处
我们先从表面看起
先看看 run 和 apply
//run 源码
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
//apply 源码
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
-
看看源码可以看到他们的共同点
- lambda 表达式是带接收者 T (lambda中的T)这个就是接收者对象 (接收者的意思是 这个函数看成一个消息 而这个对象接收这个消息然后执行一些行为)
- lambda表达式的都是无参(表面的无参等下会详细解释)
-
不同点 :
apply忽略lambda的返回值 而是继续返回原来的实例
run 接收lambda的返回值并返回给调用者
再来看看 let also
//let源码
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
//also源码
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
-
let 和 also函数的共同点
- lambda表达式的是有一个参数的
- 且这个参数的实参就是接收者对象 block(this) 这行代码表名了
-
不同点
- also函数返回原来的实例忽略调lambda表达式的结果
- let函数返回lambda的结果给调用方
-
实质
无论是 T.() 还是 (T) 他们的底层的函数类型都是一样的
fun Int.f1(){ println(this) } fun main() { val f1 : Int.() -> Unit = Int::f1 val f2 : (Int) -> Unit = {num-> println(num) } println(f1 is (Int) -> Unit) println(f2 is Int.() -> Unit) } //输出 : true true
- 带接收者的函数 其实就是类中方法
- 底层的实现是将 接收者作为函数的第一个参数 其他参数往后移
public final class ExpandKt {
public static final void f1(int $this$f1) {
System.out.println($this$f1);
}
public static final void main() {
Function1 f1 = (Function1)null.INSTANCE;/*这里*/
Function1 f2 = (Function1)null.INSTANCE;/*这里*/
boolean var2 = TypeIntrinsics.isFunctionOfArity(f1, 1);
System.out.println(var2);
var2 = TypeIntrinsics.isFunctionOfArity(f2, 1);
System.out.println(var2);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
- 可以发现 f1 和 f2 的类型都是被强制转换成了 Function1
注意这里验证的都是lambda表达式里的参数关系 这四个方法都是带接收者的函数
-
接收方
带接收方的lambda表达式 kotlin会将其视为接收方的扩展函数
-
lambda表达式实际上只能有一个接收者 但是可以有过个作用域 例如嵌套的lambda表达式
fun top(func : String.() -> Unit) = "java".func()
fun nested(func : Int.()->Unit) = (-2).func()
fun main() {
top{
println("In outer lambda $this and $length")
nested{
println("in inner lambda $this and ${toDouble()}")
println("from inner through receiver of outer: $length")
println("from inner to outer receiver ${this@top}")
}
}
}
//输出:
In outer lambda java and 4
in inner lambda -2 and -2.0
from inner through receiver of outer: 4
from inner to outer receiver java
- 什么是词法作用域 : 就是定义函数或类时就能确定的作用域