kotlin基础——重载

重载算术运算符

重载二元算术运算

使用operator定义plus()方法后,可以直接使用+号求和

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}
val p1 = Point(1, 2)
val p2 = Point(3, 4)
print(p1 + p2)

若定义为扩展函数,也可实现

data class Point(val x: Int, val y: Int) 

operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

可供选择的重载运算有如下,不会自动支持交换性

在这里插入图片描述
不要求两个运算数是相同类型

data class Point(val x: Int, val y: Int)
operator fun Point.times(scale: Double): Point {
    return Point((x * scale).toInt(), (y * scale).toInt())
}
val p = Point(10, 20)
println(p * 1.5)

返回类型也可不同于任一运算数类型

operator fun Char.times(count: Int): String {
    return toString().repeat(count)
}
println('a' * 3)

Kotlin中的位运算符如下

  • shl一一带符号左移
  • shr一一带符号右移
  • ushr一一无符号右移
  • and一一与
  • or 一一或
  • xor一一异或
  • inv一一取反
println(0x0F and 0xF0)
println(0x0F or 0xF0)

重载复合赋值运算符

可通过定义plusAssign()重写+=

data class Point(var x: Int, var y: Int)
operator fun Point.plusAssign(other: Point): Unit {
    x += other.x
    y += other.y
}
val p = Point(1, 2)
p += Point(3, 4)
println(p)

系统自带函数库已经重写了该方法,将一个元素添加到可变集合

val number = ArrayList<Int>()
number += 1
number += 2
for (i in number) {
    println(i)
}

当使用+=时,plus()和plusAssign()都可能被调用,解决办法是

  • 替换运算符为普通函数调用
  • 用val代替var修饰变量,这样不能调用plusAssign()
  • 若类是不可变的,则提供plus()即可,若类是可变的则提供plusAssign()

重载一元运算符

如下获取负坐标

data class Point(var x: Int, var y: Int)
operator fun Point.unaryMinus(): Point {
    return Point(-x, -y)
}

val p = Point(1, 2)
println(-p)

可重载的一元运算符有

在这里插入图片描述

重载比较运算符

等号运算符:equals

在Kotlin中使用==会被转换成equals,其可用于可空类型

a == b

相当于

a?.equal(b) ?: (b == null)

equals在Any类中且已经标记为operator,所以只需要用override复写,不能实现为扩展函数,因为继承自Any类的实现始终优先于扩展函数

class Point(var x: Int, var y: Int){
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as Point
        if (x != other.x) return false
        if (y != other.y) return false
        return true
    }
    override fun hashCode(): Int {
        var result = x
        result = 31 * result + y
        return result
    }
}

排序运算符:compateTo

比较运算符 (<、> 、<= 和 >=)将转换成compateTo,Java中实现了Comparable的类在Kotlin中都可以使用运算符

class Person(val name: String, val age: Int) : Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other, Person::age, Person::name)
    }
}
val p1 = Person("A",1)
val p2 = Person("B",1)
println(p1 < p2)

如上按照字母表顺序比较,p1 < p2等价于p1.compateTo(p2) < 0,

集合和区间

下标运算符访问元素:get和set

data class Point(var x: Int, var y: Int)
operator fun Point.get(index: Int): Int {
    return when (index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("Invalid coordinate")
    }
}
operator fun Point.set(index: Int, value: Int) {
    when (index) {
        0 -> x = value
        1 -> y = value
        else -> throw IndexOutOfBoundsException("Invalid coordinate")
    }
}

val p = Point(1, 2)
println(p[0])
p[0] = 3
println(p[0])

in和contains

如下重写contains,使用in判断点是否在矩形中

data class Point(var x: Int, var y: Int)
data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x
            && p.y in upperLeft.y until lowerRight.y
}

val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect)

…和rangeTo

使用1…10创建一个[1,10]的区间,可通过rangeTo为类定义该运算符,其是Comparable的扩展函数,若一个类实现了Comparable可直接使用rangeTo

val now = LocalDate.now()
val vacation = now..now.plusDays(10)
println(now.plusWeeks(1) in vacation)

rangeTo优先级低于算术运算符,使用时最好使用括号

val n = 9
println(0..(n + 1))

(0..n).forEach { println(it) }

for和iterator

在for中使用in会调用iterator方法,如遍历String,其父类CharSequence已经实现了iterator

for (c in "abc") {
    println(c)
}

如下在LocalDate的闭区间上定义iterator

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
    object : Iterator<LocalDate> {
        var current = start
        
        override fun hasNext() =
            current <= endInclusive
            
        override fun next() = current.apply {
            current = plusDays(1)
        }
    }
    
val newYear = LocalDate.ofYearDay(2017, 1)
val dayOff = newYear.minusDays(1)..newYear
for (day in dayOff) {
    println(day)
}

解构声明

解构声明允许展开单个复合值,并使用它来初始化多个单独的变量,如下

data class Point(var x: Int, var y: Int)

val p = Point(10, 20)
val (x, y) = p
println(x)
println(y)

解构声明初始化变量,相当于调用名为ComponentN的函数,N是声明中变量的位置,如下

class Point(var x: Int, var y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

解构声明主要用于展开函数返回的数据类,如下将文件和扩展名分割

data class NameComponents(val name: String, val extension: String)

fun splitFilename(fullName: String): NameComponents {
    val result = fullName.split(".", limit = 2)
    return NameComponents(result[0], result[1])
}
val (name, ext) = splitFilename("demo.kt")
println(name)
println(ext)

解构声明可以在数组和集合上使用,修改上面的splitFilename方法

fun splitFilename(fullName: String): NameComponents {
    val (name, ext) = fullName.split(".", limit = 2)
    return NameComponents(name, ext)
}

解构声明可用于循环中的变量声明,如遍历map

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) {
        println("$key -> $value")
    }
}
val map = mapOf("1" to "A", "2" to "B")
printEntries(map)

委托

by lazy

惰性初始化直到第一次访问该属性时才创建对象,常规的做法如下

class Email()

fun loadEmails(person: Person): List<Email> {
    Thread.sleep(5000)
    return listOf(Email(), Email())
}

对于上述加载耗时的操作,使用_emails来存储,而另一个email用于返回

class Person(val name: String) {
    private var _emails: List<Email>? = null

    val email: List<Email>
        get() {
            if (_emails == null) {
                _emails = loadEmails(this)
            }
            return _emails!!
        }
}

上面代码繁琐且非线程安全,使用by lazy可以让代码简单

class Person(val name: String) {
    val email by lazy { loadEmails(this) }
}

实现委托

实现一个例子,当对象改变时通知监听器,Java通过PropertyChangeSupport和PropertyChangeListener实现

open class PropertyChangeAware {
    protected val changeSupport = PropertyChangeSupport(this)

    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)
    }
}

Person继承上面的PropertyChangeAware,在setter时通知listener

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    var age: Int = age
        set(newValue) {
            val oldValue = field
            field = newValue
            changeSupport.firePropertyChange("age", oldValue, newValue)
        }
    var salary: Int = salary
        set(newValue) {
            val oldValue = field
            field = newValue
            changeSupport.firePropertyChange("salary", oldValue, newValue)
        }
}

Person添加listener,属性改变时响应

val p = Person("Tom", 20, 1000)
p.addPropertyChangeListener(
    PropertyChangeListener { event ->
        println(
            "Property ${event.propertyName} change" +
                    "from ${event.oldValue} to ${event.newValue}"
        )
    }
)
p.age = 21

可以优化代码,将属性变化的值过程封装到ObservableProperty

class ObservableProperty(
    val proName: String,
    var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {
    fun getValue(): Int = propValue
    fun setValue(newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(proName, oldValue, newValue)
    }
}

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    val _age = ObservableProperty("age", age, changeSupport)
    var age: Int
        get() = _age.getValue()
        set(value) = _age.setValue(value)
    val _salary = ObservableProperty("age", age, changeSupport)
    var salary: Int
        get() = _salary.getValue()
        set(value) = _salary.setValue(value)
}

Kotlin中委托的原理也是如此,将属性的getter和setter委托给ObservableProperty,不同的是

  • setter和getter标记为operator,一个参数接受对象实例,用于读写属性,另一个用于表示属性本身,类型为KProperty
  • 使用by代替之前代码中调用setter和getter的步骤
class ObservableProperty(
    var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {
    operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
    operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }
}

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}

最后,Kotlin自带了Delegates.observable,代替我们写的ObservableProperty类,只需要传递一个Lambda通知属性值的修改

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    private val observer = { prop: KProperty<*>, oldValue: Int, newValues: Int ->
        changeSupport.firePropertyChange(prop.name, oldValue, newValues)
    }
    var age: Int by Delegates.observable(age, observer)
    var salary: Int by Delegates.observable(salary, observer)
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/290936.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spark三:Spark SQL

Spark SQL 数据的分析方式、数据分类和SparkSQL适用场景、数据抽象&#xff08;DataFrame, DataSet, RDD&#xff09;&#xff0c;SparkSQL读取数据和处理数据的两种风格&#xff1a;SQL风格和DSL风格 学习链接 https://mp.weixin.qq.com/s/caCk3mM5iXy0FaXCLkDwYQ 一、数据分…

【GNN】经典GNN的数学原理之美

目录 引言 背景 图神经网络 节点 消息传递 聚合 更新 整合在一起 使用邻接矩阵 GNN层堆叠 以节点分类为例训练GNN。 训练和测试数据 1、Transductive 2、Inductive 反向传播和梯度下降 流行图神经网络总结 1、消息传递神经网络 2、图卷积 3、图注意力网络 …

旧硬盘插电脑上显示要初始化怎么办?了解原因和解决方案

在使用旧的硬盘插入电脑时&#xff0c;有时会遇到需要进行初始化的情况。这种情况可能是由于多种原因引起的&#xff0c;而初始化硬盘将会导致所有数据丢失&#xff0c;给用户造成不便和损失。因此&#xff0c;本文将介绍解决旧硬盘需要初始化的问题的方法&#xff0c;并提供一…

毕业设计:基于机器学习xgboost lgbm adaboost 的空气质量预测pm2.5‘, ‘so2‘, ‘no2‘ 完整代码数据-可直接运行

项目详细视频讲解介绍: 基于机器学习xgboost lgbm adaboost 的空气质量预测-完整代码数据可直接运行_哔哩哔哩_bilibili 数据展示: 运行结果展示: 项目代码: from sklearn import preprocessing import random from sklearn.model_selection import train_test_split fr…

软件测试第二部分:白盒测试

概念与定义 白盒测试&#xff1a;侧重于系统或部件内部机制的测试&#xff0c;类型分为分支测试&#xff08;判定节点测试&#xff09;、路径测试、语句测试。 控制流分析(基于程序结构)&#xff1a;控制流分析是一类用于分析程序控制流结构的静态分析技术&#xff0c;目的在于…

ModuleNotFoundError: No module named ‘numpy.testing.decorators‘

文章目录 报错信息报错原因解决方案 关注公众号&#xff1a;『AI学习星球』 算法学习、4对1辅导、论文辅导或核心期刊可以通过公众号或➕v&#xff1a;codebiubiubiu滴滴我 报错信息 ModuleNotFoundError: No module named numpy.testing.decorators 报错原因 新版本已经去…

面试算法88:爬楼梯的最少成本

题目 一个数组cost的所有数字都是正数&#xff0c;它的第i个数字表示在一个楼梯的第i级台阶往上爬的成本&#xff0c;在支付了成本cost[i]之后可以从第i级台阶往上爬1级或2级。假设台阶至少有2级&#xff0c;既可以从第0级台阶出发&#xff0c;也可以从第1级台阶出发&#xff…

pip install 安装模块包位置及设置Anaconda为默认版本python

01问题 pycharm运行代码找不到模块包pip install不知道安装到哪里了jupyter使用不同版本python 02产生原因 安装了多个版本pythonanaconda本身也带有python 03解决办法 (1)查看当前默认python版本 打开运行窗口Winr&#xff1b; 输入cmd回车&#xff1b; 输入python回车…

初识Web服务器

一、web服务器 1、什么是web服务器&#xff1f; web服务器就是web项目的容器&#xff0c;我们将开发好的web项目部署到web容器中&#xff0c;才能使用网络中的用户通过浏览器进行访问。 一张图带你了解web服务器有啥作用&#xff1a; 在我的电脑上有一个已经做好的项目&#…

linux centos 添加临时ip

### 1.添加ip ip addr add IP/mask dev 网络设备 例&#xff1a;ip addr add 172.104.210.247/24 dev ens5f1 ### 2.启动网卡 ip link set up 网络设备 例&#xff1a;ip link set up ens3f0 ### 3.设置默认路由 ip route add default via GATEWAY 例&#xff1a;ip route add …

python3ide手机安卓版下载,python3下载手机安卓版

大家好&#xff0c;给大家分享一下python3ide手机安卓版下载&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 大家好&#xff0c;给大家分享一下python3ide安卓版官网下载&#xff0c;很多人还不知道这一点。下面详细解释一下python编程代码…

C#编程-描述内存分配

描述内存分配 分配给变量的内存通过两种方式引用&#xff1a;值类型和引用类型。内置数据类型&#xff0c;诸如int、char和float都是值雷兴国。当您声明int变量时&#xff0c;编译器会分配一个内存块以保持该整数值。请思考以下语句&#xff1a; int Num 50;上述语句为保存值…

手机怎么边看视频边记笔记或备忘录?

在这个信息爆炸的时代&#xff0c;我们经常需要通过看培训视频、听网课来不断充实自己。但是&#xff0c;手机屏幕那么小&#xff0c;如何才能在做笔记的同时&#xff0c;又不错过视频的每一个细节呢&#xff1f; 以前&#xff0c;我总是为此头疼。一手拿着手机看视频&#xf…

电脑视频需要分屏怎么做

在当今数字时代&#xff0c;人们对于视频的需求越来越高。有时候&#xff0c;我们可能想在同一屏幕上同时播放多个视频&#xff0c;进行对比、观看、剪辑或者其他目的。那么&#xff0c;视频分屏应该怎么做呢&#xff1f; 在本篇文章中&#xff0c;我们将会详细的为你介绍视频分…

可狱可囚的爬虫系列课程 09:通过 API 接口抓取数据

前面已经讲解过 Requests 结合 BeautifulSoup4 库抓取数据&#xff0c;这种方式在抓取数据时还是比较方便快捷的&#xff0c;但是这并不意味着所有的网站都适合这种方式&#xff0c;并且这也不是抓取数据的最快方式&#xff0c;今天我们来讲一种更快速的获取数据的方式&#xf…

Python selenium模块的安装和配置教程

一、selenium的安装以及简单应用 我们以谷歌浏览器的chromedriver为例 1、在Python虚拟环境中安装selenium模块 pip/pip3 install selenium 2、下载版本符合的webdriver 以chrome谷歌浏览器为例 查看谷歌浏览器的版本 鼠标点击右上角的竖排的三个点&#xff0c;然后选择“…

P1192 台阶问题————C++

目录 台阶问题题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 解题思路Code运行结果 台阶问题 题目描述 有 N N N 级台阶&#xff0c;你一开始在底部&#xff0c;每次可以向上迈 1 ∼ K 1\sim K 1∼K 级台阶&#xff0c;问到达第 N N N 级台阶有多少种不同方…

华为设备命令最全大合集(2024新版),赶紧收藏!

01、华为交换机基础配置命令 01 常用命令视图 02 创建VLAN //用户视图&#xff0c;一般display命令查看信息比较多。 system-view //准备进入系统视图。 [Huawei]vlan 100 //创建vlan 100。 [Huawei-vlan100]quit //退回系统视图。 03 将端口加入到vlan中 [Huawei] inter…

【C语言】字符串 和 ctype.h 中的函数 练习

前面总结了有关字符串和ctype.h的文章&#xff0c;接下来就以几个例子来练习一下&#xff0c;以巩固之前的基础概念。注意&#xff1a;以下示例都有更简单更高效的解决方法&#xff0c;但本次仅以巩固基础为目的&#xff0c;所以方法可能稍作繁琐 Leetcode 344.反转字符串 编…

Spring Boot 整合多 Redis 数据源配置及操作

Spring Boot 整合多 Redis 数据源配置及操作 简介 本文档介绍了如何在Spring Boot应用程序中配置和操作多个Redis数据源。通过配置多个RedisConnectionFactory和RedisTemplate&#xff0c;可以实现对多个Redis数据源的整合&#xff0c;以便在应用程序中灵活地使用不同的Redis…
最新文章