Kotlin运算符重载与约定

java中不支持运算符重载,而Kotlin支持操作符重载。这些操作符在Kotlin中是约定好的固定符号。(如:加号 + 乘号 * 等),和固定的优先级。而应用这样的操作符,我们也必须使用映射固定名字的成员函数或者扩展函数(加法 plus 乘法 times)。重载操作符的函数需要用operator修饰符来标记。

- 什么是运算符重载

运算符重载时对已有的运算符赋予新的含义,使一个运算符作用于不同类型的数据会有对应这个类型的行为。运算符重载实际上是函数重载,本质上是对运算符函数的调用,从运算符到对应函数的映射过程由编译器完成。

  • Kotlin中的运算符重载约定定义在org.jetbrains.kotlin.util.OperatorNameConventions中:
object OperatorNameConventions {
    @JvmField val GET_VALUE = Name.identifier("getValue")
    @JvmField val SET_VALUE = Name.identifier("setValue")
    @JvmField val PROVIDE_DELEGATE = Name.identifier("provideDelegate")

    @JvmField val EQUALS = Name.identifier("equals")
    @JvmField val COMPARE_TO = Name.identifier("compareTo")
    @JvmField val CONTAINS = Name.identifier("contains")
    @JvmField val INVOKE = Name.identifier("invoke")
    @JvmField val ITERATOR = Name.identifier("iterator")
    @JvmField val GET = Name.identifier("get")
    @JvmField val SET = Name.identifier("set")
    @JvmField val NEXT = Name.identifier("next")
    @JvmField val HAS_NEXT = Name.identifier("hasNext")

    @JvmField val COMPONENT_REGEX = Regex("component\\d+")

    @JvmField val AND = Name.identifier("and")
    @JvmField val OR = Name.identifier("or")

    @JvmField val INC = Name.identifier("inc")
    @JvmField val DEC = Name.identifier("dec")
    @JvmField val PLUS = Name.identifier("plus")
    @JvmField val MINUS = Name.identifier("minus")
    @JvmField val NOT = Name.identifier("not")

    @JvmField val UNARY_MINUS = Name.identifier("unaryMinus")
    @JvmField val UNARY_PLUS = Name.identifier("unaryPlus")

    @JvmField val TIMES = Name.identifier("times")
    @JvmField val DIV = Name.identifier("div")
    @JvmField val MOD = Name.identifier("mod")
    @JvmField val REM = Name.identifier("rem")
    @JvmField val RANGE_TO = Name.identifier("rangeTo")

    @JvmField val TIMES_ASSIGN = Name.identifier("timesAssign")
    @JvmField val DIV_ASSIGN = Name.identifier("divAssign")
    @JvmField val MOD_ASSIGN = Name.identifier("modAssign")
    @JvmField val REM_ASSIGN = Name.identifier("remAssign")
    @JvmField val PLUS_ASSIGN = Name.identifier("plusAssign")
    @JvmField val MINUS_ASSIGN = Name.identifier("minusAssign")

    // If you add new unary, binary or assignment operators, add it to OperatorConventions as well

    @JvmField
    val UNARY_OPERATION_NAMES = setOf(INC, DEC, UNARY_PLUS, UNARY_MINUS, NOT)

    @JvmField
    internal val SIMPLE_UNARY_OPERATION_NAMES = setOf(UNARY_PLUS, UNARY_MINUS, NOT)

    @JvmField
    val BINARY_OPERATION_NAMES = setOf(TIMES, PLUS, MINUS, DIV, MOD, REM, RANGE_TO)

    @JvmField
    internal val ASSIGNMENT_OPERATIONS = setOf(TIMES_ASSIGN, DIV_ASSIGN, MOD_ASSIGN, REM_ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN)
}

  • 运算符与操作符函数的映射关系定义在 org.jetbrains.kotlin.types.expressions.OperatorConventions.java
public static final ImmutableBiMap UNARY_OPERATION_NAMES = ImmutableBiMap.builder()
            .put(KtTokens.PLUSPLUS, INC)
            .put(KtTokens.MINUSMINUS, DEC)
            .put(KtTokens.PLUS, UNARY_PLUS)
            .put(KtTokens.MINUS, UNARY_MINUS)
            .put(KtTokens.EXCL, NOT)
            .build();

    public static final ImmutableBiMap BINARY_OPERATION_NAMES = ImmutableBiMap.builder()
            .put(KtTokens.MUL, TIMES)
            .put(KtTokens.PLUS, PLUS)
            .put(KtTokens.MINUS, MINUS)
            .put(KtTokens.DIV, DIV)
            .put(KtTokens.PERC, REM)
            .put(KtTokens.RANGE, RANGE_TO)
            .build();

    public static final ImmutableBiMap REM_TO_MOD_OPERATION_NAMES = ImmutableBiMap.builder()
            .put(REM, MOD)
            .put(REM_ASSIGN, MOD_ASSIGN)
            .build();

    public static final ImmutableBiMap ASSIGNMENT_OPERATIONS = ImmutableBiMap.builder()
            .put(KtTokens.MULTEQ, TIMES_ASSIGN)
            .put(KtTokens.DIVEQ, DIV_ASSIGN)
            .put(KtTokens.PERCEQ, REM_ASSIGN)
            .put(KtTokens.PLUSEQ, PLUS_ASSIGN)
            .put(KtTokens.MINUSEQ, MINUS_ASSIGN)
            .build();

其中,KtTokens.kt中定义了+, -, *, / , == , ! , ++, – , *= , /= 等等运算符的符号。从源码中的这一句

public static final ImmutableSet NOT_OVERLOADABLE =
            ImmutableSet.of(KtTokens.ANDAND, KtTokens.OROR, KtTokens.ELVIS, KtTok

我们可以知道,Kotlin中的 && 、 || 、 ?: 、 === 、 !== 是不能被重载的。

有了操作符重载,我们可以将两个对象加起来变成另外一个对象。例如,我们自定义一个BoxInt类型。然后重载times和plus函数

class BoxInt(var i: Int) {
    operator fun times(x: BoxInt) = BoxInt(i * x.i)
    override fun toString(): String {
        return i.toString()
    }
}

operator fun BoxInt.plus(x: BoxInt) = BoxInt(this.i + x.i)

object Text {
    @JvmStatic
    fun main(args: Array) {
        val a = BoxInt(3)
        val b = BoxInt(7)
        println(a + b)
        println(a * b)
    }
}

实际上 a+b就是两个函数的调用 println(BoxInt(3).plus(BoxInt(7)))这样会好理解一些
a+b反编译成java

   public static final BoxInt plus(@NotNull BoxInt $receiver, @NotNull BoxInt x) {
    	//.....
      return new BoxInt($receiver.getI() + x.getI());
   }
- 重载二元算数运算符

二元运算符与重载函数名称映射关系如下:

二元运算符 重载函数名称 备注
a+b a.plus(b) 加法操作
a-b a.minus(b) 减法操作
a*b a.times(b) 乘法操作
a/b a.div(b) 除法操作
a%b a.rem(b) 取余操作
a…b a.rangeTo(b) 范围操作符

比如 1+1=2 本质上1.plus(1)

  • 自定义类型运算符重载的作用,与内置运算符的作用是同样的声明方式,但是具体的运算逻辑的实现是自定义的。

  • 设计一个类Complex,实现负数的基本 操作。如相加(1+2i)+(3+4i) = (4+6i);相减,相乘。

  1. 声明一个类Complex,在里面声明两个变量作为实部real,虚部 image,并生成两个构造函数。
class Complex {
    var real: Int = 0
    var image: Int = 0

    constructor()
    
    constructor(real: Int, image: Int) {
        this.real = real
        this.image = image
    }

}
  1. 实现加法、减法、乘法运算符重载函数

class Complex {
    var real: Int = 0
    var image: Int = 0

    constructor()

    constructor(real: Int, image: Int) {
        this.real = real
        this.image = image
    }

    /**
     * 加法  实部加上实部 ,虚部加上虚部
     */
    operator fun plus(c: Complex): Complex {
        return Complex(this.real + c.real, this.image + c.image)
    }

    /**
     * 减法 实部减去实部,虚部减去虚部
     */
    operator fun minus(c: Complex): Complex {
        return Complex(this.real - c.real, this.image - c.image)
    }

    /**
     * 乘法
     */
    operator fun times(c: Complex): Complex {
        return Complex(this.real * c.real - this.image * c.image, this.real * c.image + this.image * c.real)
    }

    override fun toString(): String {
        val img = if (image >= 0) {
            "+ ${image}i"
        } else {
            "${image}i"
        }
        return "${real} ${img}"
    }


}




测试代码如下

object Text {
    @JvmStatic
    fun main(args: Array) {
        val c1 = Complex(1, 2)
        val c2 = Complex(3, 4)
        val p = c1 + c2
        val m = c1 - c2
        val t = c1 * c2

        println(p)
        println(m)
        println(t)
    }
}

输出如下:

4 + 6i
-2 -2i
-5 + 10i

重载比较运算符
  • 在java中,“<、>、>=、<=、==、!=、”运算符只能作用于基本数据类型的比较。而在对象类型上是不允许使用这些比较符进行比较的。而原则上只要给定一个比较规则,对象之间也是可以进行比较的。在Kotlin中,一切类型皆为引用类型,所以对象之间的比较是必然的。
    比如BigDecimal的比较,java会报错
    Kotlin运算符重载与约定_第1张图片
    在Kotlin中是允许的
    Kotlin运算符重载与约定_第2张图片

其中 < 会被映射成b1 compareTo(BigDecimal) < 0的值

表达式 翻译成函数调用
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a >= b a.compareTo(b) >= 0
  • 两个 “= =” 会映射成调用equals()方法。需要注意的是 a==b 表达式中,就算a 、b是null,也可以安全调用。因为 a == b 会被Kotlin编译器翻译成带可空性判断的equals方法的调用
  • 而三个等号 === 是Kotlin中自己实现的运算符,这个运算符不能被重载,它不仅会比较值的相等,还会去比较对象的引用是否相等。
重载计算赋值运算符
表达式 翻译成运算符重载函数的调用
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

《Kotlin从入门到进阶实战》

你可能感兴趣的:(Kotlin)