新手上路,Kotlin学习笔记(六)---运算符重载和其它约定

        入行没几年的小码农,近期学习Kotlin,做一份笔记记录,此文依据《Kotlin实战》这本书的流程记录,部分示例内容均摘自《Kotlin实战》,记下自己的理解,本篇记录在Kotlin中我们如何对运算符进行重载,另外还有一些Kotlin中的约定。



        Kotlin学习笔记系列

        新手上路,Kotlin学习笔记(一)-- Kotlin入门介绍

        新手上路,Kotlin学习笔记(二)---方法(函数)部分

        新手上路,Kotlin学习笔记(三)---类、对象、接口

        新手上路,Kotlin学习笔记(四)---Lambda表达式在Kotlin中的使用

        新手上路,Kotlin学习笔记(五)---Kotlin中的类型系统

        新手上路,Kotlin学习笔记(六)---运算符重载和其它约定

        新手上路,Kotlin学习笔记(七)---Lambda作为形参和返回值的使用

        新手上路,Kotlin学习笔记(八)---泛型的使用

        新手上路,Kotlin学习笔记(九)---注解和反射



一、运算符的重载

        有时候我们会感慨,在拼接字符串的时候,直接用加号“+”我们会觉得好方便啊,如果添加集合也这样用就好了,不用去写add方法了。现在,在Kotlin中我们就可以这样做了!而且,不只是集合,其他类型我们也可以用这种简单的运算符替代方法!!那么,接下来我们来学习在Kotlin中怎么对运算符进行重载。

        <1>重载二元运算符

        二元运算符有 + - * / %五种,这五种符号在重载的时候,我们需要为类定义对应符号的方法,声明方式如下

class Point(var pointX : Int = 0 , var pointY : Int = 0) {
    operator fun plus(point : Point) : Point
    {
        return Point(pointX + point.pointX, pointY + point.pointY)
    }
}
    fun testPlus()
    {
        val point = Point(1,1)
        val poing2 = Point(2,1)
        val point3 = point + poing2 //此时调用Point的plus方法 point3的X和Y分别是3和2
    }

        看上面的示例,声明运算符的重载方法时,我们需要给方法增加一个operator关键字,然后方法名是加号“+”对应的名称plus,这样我们就声明了一个运算符的重载方法,非常简单,最后就可以直接用加号“+”替代方法了。

        五个二元运算符的对应方法名分别是

        +   plus

        -    minus

        *    times

        /    div   

        %    mod

    tips:我们也可以将运算符声明为扩展方法,这样我们就可以为一些第三方SDK中的类去重载运算符。

    tips:operator方法和普通的方法一样,也可以再次重载不同参数的形式,是被允许的。

        <2>复合运算符

        简单的二元运算符如何重载我们已经学会了,那么+=这样的复合运算符该怎么办呢?

        当我们已经重载二元运算符的时候,对于+=这样的二元运算符,也是直接支持的,效果还是 一样的,先相加,再赋值;

    fun testPlus()
    {
        var point = Point(1,1)
        val poing2 = Point(2,1)
        val point3 = point + poing2 //此时调用Point的plus方法 point3的X和Y分别是3和2
        point += poing2//此时point和上面point3结果相同 相当于调用了point = point + point2
    }
        同样的,复合运算符我们也可以重新重载,自己书写对应的实现而不是依赖于二元运算符,实现方式和二元运算符一样,方法名只需在二元运算符后面加上Assign即可,示例如下
    operator fun minusAssign(point : Point) 
    {
        pointX -= point.pointX
        pointY -= point.pointY
    }
        复合运算符的返回值必须是Unit的!!

        当我们同时重载了二元运算符和其对应的复合运算符,在调用的时候就会有两种方式,那么此时编译器会怎么处理呢?

        1、如果对象声明是val的,此对象就是不可变的,将不会使用复合运算符(因为复合运算符返回是Unit,对自己改变的,此时不会执行对应的方法)

        2、如果对象声明是var的,那么编译器将会报错,所以我们应该只去书写一种实现方式比较好,可以将另一种场景书写为普通的方法调用。

        <3>一元运算符和比较运算符

        一元运算符的重载方式和二元运算符基本一致,非常简单,其方法名和运算符的对照关系如下:

        +a                  unaryPlus

        -a                   unaryMinus

        !a                    not

        ++a,a++      inc

        --a,a--          dec

        对于比较运算符  == ,    != ,   >,  <,  >=,  <=这六种,其实只对应了两个方法,equals 和 compareTo,这两个方法我们都很熟悉了,并且equals不需要声明为operator,因为在Any中已经是operator的了,我们只需要override重写即可。而如果用到比较运算符,我们需要让类实现Comparable接口,然后compareTo方法和我们在Java中对集合进行排序的时候,原理是一样的,并且也不需要声明operator,因为接口中已经声明过了,不再过多描述举例,

        Any中的equals方法和Comparable中的compareTo方法源码:

/**
     * Indicates whether some other object is "equal to" this one. Implementations must fulfil the following
     * requirements:
     *
     * * Reflexive: for any non-null reference value x, x.equals(x) should return true.
     * * Symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
     * * Transitive:  for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true
     * * Consistent:  for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
     *
     * Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the
     * operator are not null.
     */
    public open operator fun equals(other: Any?): Boolean
/**
 * Classes which inherit from this interface have a defined total ordering between their instances.
 */
public interface Comparable<in T> {
    /**
     * Compares this object with the specified object for order. Returns zero if this object is equal
     * to the specified [other] object, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public operator fun compareTo(other: T): Int
}


二、集合、区间的约定

        在Kotlin中,对于集合或者区间,我们知道可以通过下标来直接访问指定的某个元素

        val people : ArrayList<Person> = arrayListOf()
        people += Person(age = 20)
        people[0].age
        那么我们先来看一下这个是如何实现的
   /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
     * DEFAULT_CAPACITY when the first element is added.
     *
     * Package private to allow access from java.util.Collections.
     */
    transient Object[] elementData;
    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }

    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }

        在编译期中点击方括号,指向的是ArrayList的get方法,然后我们看到了get方法的实现,就是从内部存储数据的数组取出值,可以看出,方括号也可以理解为一种运算符的重载,方法名是get和set。

        接着,我们就可以让自己的类也实现下标表示这种方式,还以刚才的Point为例

operator fun Point.get(index : Int):Int{
    return when(index)
    {
        0 -> pointX
        1 -> pointY
        else -> throw IndexOutOfBoundsException("error index : $index" )
    }
}
        var point = Point(1,1)
        val pointX = point[0] //此处取到的值为1 
operator fun Point.set(index  : Int , value : Int)//set方法的最后一个参数为等号右边的值
{
    when(index)
    {
        0 -> pointX = value
        1 -> pointY = value
        else -> throw IndexOutOfBoundsException("error index : $index")
    }
}

        tips:get和set的方法,其参数不需要一定是Int类型的,其可以是任何类型的。

        in和...

        在区间中,我们前面还介绍了in 和 ... 这两种运算符,它们也可以被重载,in 对应的方法名是 contains,而 ... 对应的方法名是rangeTo,并且Comparable的扩展函数实现了rangeTo的方法,所以如果我们的类实现了Comparable接口,可以直接使用...这个运算符。

        刚才提到的in是检测某个对象是否在这个区间中,前面我们还看到了in的另一种使用方式,在for循环中,我们可以用in来进行遍历,这又是怎么做的呢?

        其实,在for循环中,我们使用的in会转换成list.iterator()方法,然后调用next和hasNext,进行遍历,既然如此,我们也可以为一个类定义名为iterator的扩展方法,然后实现next和hasNext方法,这样in在for循环中的使用,我们也进行了对应的重载。


三、解构声明

        当我们将一个类声明为data类型的时候,将允许我们通过下面这种方式将对象展开获取多个变量

        var point = Point(1,2)
        val (x,y) = point //此处,x的值为1,y的值为2

        上面这种展开的方式叫做解构声明,就是将point的两个成员变量,赋值给了x和y,这个是data类自动帮我们实现的。其实际原理是调用了point对象的component1()和component2()方法,和名称一样,component1就是获取第一个参数的值,另外,Kotlin标准库只允许返回前五个值,并不是返回无限多的。


四、委托属性

        前面我们有使用by关键字来将类委托,减少我们包装时的代码量,这里我们将继续学习by关键字。

class Book
{
    var name : String by BookName(name)
}

class BookName(var value : String) {
    operator fun getValue(book : Book, prop : KProperty<*>) = value
    operator fun setValue(book : Book, prop : KProperty<*>,  newValue : String) 
    {
        value = newValue
    }
}

        上述示例就是将Book类的成员变量name委托给BookName进行处理,我们不再需要对name书写对应的实现方法,在set、get的时候均会调用BookName的getValue和setValue方法。而需要被委托的类,要求我们实现这两个方法,并且第一个参数为对应的对象,第二个参数为固定的,后面我们学习到再做分析解释。

        tips;在Kotlin中,Map已经实现了setValue和getValue方法,所以我们可以直接将变量委托给一个Map。这样也是非常方便的。

        惰性初始化:在初始化数据的时候,我们还可以使用by lazy的方式,使用这种方式,该值将在第一次调用的时候通过预定好的方式获取数据,如果有数据,就直接返回,使用方式如下:

data class Client(val username : String , val password : String)
{
    val age : Int by lazy { getAgeFirst() } //lazy后面是一个Lambda表达式

    fun getAgeFirst():Int
    {
        return 0
    }
}


        到此为止,本章的内容就结束了,下一章我们将继续重温Lambda表达式,学习Lambda在Kotlin中更加高大上的使用方式!

        // 下一章 新手上路,Kotlin学习笔记(七)---Lambda作为形参和返回值的使用


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值