目录
写在前面
一、常量和变量
二、分支表达式
三、运算符与中缀表达式
3.1、运算符
3.2、中缀表达式
四、Lambda表达式
4.1、匿名函数
4.2、Lambda表达式
五、综合案例——为Person实现equals和hashCode
六、综合案例——为String实现四则运算
上一篇中介绍了关于类、抽象类、接口、扩展方法和属性、空类型安全以及智能类型转换等知识点的基本概念和使用,文章详情见《Kotlin真香系列第二弹:类型初探》,今天来学习一下Kotlin中的表达式的概念以及使用方式。
①、变量
Kotlin中定义变量直接var关键字搞定:
②、只读变量
Kotlin中使用val定义只读变量,这玩意如果定义在函数中作为局部变量的话就可以把它当成常量来看,因为它的值不能改变嘛,那它为什么要叫只读变量呢?
因为如果val作为一个属性或者是一个顶级的变量出现,它是可以定义一个getter()方法的,一旦定义了getter()之后,这个变量的值在每次读的时候有可能返回的就是不一样的:
③、常量值
那如果我就想定义一个常量该咋办呢?也是有办法的哈,这个定义还是比较严格的,相当于Java中的static final了:
需要使用const关键字修饰,并且有以下几点要求:(字符串也是支持的)
④、常量引用
自定义类的对象的引用就是一个常量引用,对象里面的成员是可以被重新赋值的,所以常量引用这东西严格意义上来说不能算是常量,因为它里面的东西可以被修改
⑤、编译期和运行时常量
比如下面这种的对值进行延迟初始化:
①、if...else表达式
分支表达式最常见的应该就是if...else了,如下图中所示咱们可以这么写:
但是需要注意在Java中这玩意可不是表达式,它叫语句,但是在Kotlin中它就是表达式了,比如在Java中我们可以使用三元表达式如下图左侧部分,等同的在Kotlin中可以直接写成if...else这种表达式:
②、when表达式
Kotlin中的when表达对应到Java中就是switch...case了,仔细看其实就是把switch变成了when,把case变成了“—>”,default变成了else,默认的break都给干掉了:
既然when也是表达式,所以咱们还可以这么写,把c提到外面,每个分支最后的部分作为表达式的值,这样就可以得到c的值:
对于when表达式还有一些其它的写法,下面再来看几种变形:
比如下面这种,首先括号给干没了,啥意思呢?就是把判断条件转移到分支上面去,分支中还给你搞了智能类型转换,看着是不是还不错:
既然它也是个表达式,所以咱还可以直接把“c=”提到外面,每个分支的最后作为表达式的值:
从1.3开始when表达式括号里面还可以进行赋值,比如下图中的代码,使用input去接收控制台的输出:
③、try...catch表达式
没想到吧,在kotlin中try...catch居然也是表达式,如下图中Java里面的代码在Kotlin中可以一模一样的写出来,除了Exception是放后面的,还有不写分号,其它没有任何差别,所以在Kotlin中try...catch这部分是不是搞定了,一样的嘛你肯定会写了:
所以关于写法不多说了,其实也没啥说的,主要来看一下作为表达式的写法:try里面是一个分支,catch里面也是一个分支,这两个东西相当于是if...else,所以try...catch也是可以作为表达式来用的:
官方指定了哪些运算符可以重载呢?我们可以看一下官方文档:
更多更详细的内容请参考官方文档:https://kotlinlang.org/docs/reference/operator-overloading.html
这里就挑几个重点的来说一下,剩下的大家可以在使用的时候参考官方文档的说明:
①、==与equals
上面的写法跟下面的写法是完全等价的
②、+与plus
2+3就可以写成2.plus(3)
③、in与contains
val list = listOf(1,2,3,4)
//2 in list 等价于 list.contains(2)
if (2 in list){
}
if (list.contains(2)){
}
④、[]与get/set
[]在之前说数组和集合的时候提到过,它到底是表示set还是get主要是在于它出现在“=”的哪一边?如果出现在等号的右边,相当于是表达式的取值,那就是get了,中括号里面的就相当于是key:
如果[]是出现在等号的左边,那么就相当于是set,等号右边的就是它set的值:
⑤、>与compareTo
2大于3就相当于2.compareTo(3)>0,如果是2小于3,那么相对应的就是2.compareTo(3)<0:
⑥、()与invoke
这个东西可能不太熟悉哈,但是你要知道它也是一个运算符,左侧是一个匿名函数,右侧就是定义了一个变量就func,我想调用这个函数咋调用呢?直接func()加个括号就行,它就等价于func.invoke():
2 to 3
这个东西咱们之前在构造Map的时候已经见过了,它其实不是个运算符,它等价于2.to(3),这样一看就明白了,整型有个方法叫to,接收一个参数,这其实就是一个中缀表达式,那中缀表达式究竟是如何定义的呢?
上图中:A是个泛型,泛型到后面会说,不过熟悉Java的应该也都知道泛型的概念,然后A.to这不就是个扩展方法吗,它接收一个参数,参数的类型是另一个泛型类型B,定义完了之后发现它有一个receiver(就是例子中的Int类型的2),并且只有一个参数,那这就是中缀表达式,注意前面还有一个infix关键字,加上这个关键字可以告诉编译器我可以直接简化成2 to 3的这种形式。
再来举个栗子吧,我们来随便写个中缀表达式:
fun main() {
println("I Love" joint "China")
}
infix fun String.joint(str:String):String{
val stringBuilder = StringBuilder()
return stringBuilder.append(this).append(str).toString()
}
执行结果如下:
很简单,其实就是个字符串拼接的操作,但是这样定义了之后咱就可以直接写:str1 funname str2。
首先来看一个普通函数:就长下面这个样
如果是匿名函数,就把名字去掉呗,匿名嘛不署名的函数:
那么这个匿名函数该怎么使用呢?可以把它赋值给一个变量,因为函数可以有类型可以传递它的值:
想清楚了这些之后,再来看一下它该如何调用呢?其实上面也讲过了,就是func(),这个括号就是个运算符:
说完了这些咱们再来看一下匿名函数的类型:对于这个函数来说它就是接收0个参数返回Unit的类型,只是少了个名字而已,和普通的函数其实是一样的:
说了这么多的匿名函数,其实就是为了引出接下来的Lambda表达式,因为lambda表达式本质上就是个匿名函数。
①、Lambda表达式的定义
Java里面从Java8开始也有lambda表达式,对比一下和kotlin中的有啥区别?Java中如果一个参数都没有的话,这个"()—>"小括号和箭头也是要写的,但是在kotlin里面就不用写了,大括号里面就是表达式体,最后一行就是表达式的返回值:
②、Lambda表达式的类型
这个lambda表达式的类型就和刚刚看到的匿名函数的类型是一模一样的,一个参数都没有并且返回Unit,因为lambda表达式的返回值就是表达式体里面最后一行的返回值,这里面println它本身就是返回Unit的:
当我们加了一个参数之后,来看看类型又变成了什么呢?如下图,可以看到此时类型变成了(Int)->Unit:
如下图所示,它的类型还有一种写法就是使用Function1
因为在等号前面定义了f1的类型是Function1
当然了类型推导还可以进一步变成下面这样,就是说如果在lambda表达式里面可以明确推断出来它的类型的话,前面的f1就可以不用再声明出来它的类型了,注意看刚刚p后面是没有Int的,现在有了,因为上面那种写法你是在前面泛型参数中声明了,所以后面可以不写,但是现在你前面省略掉了,那你后面肯定要把Int写出来了,不然编译器推断不出来你这个表达式到底是接收什么类型的参数了:
刚才在上面也已经说过了表达式体的最后一行表示整个表达式返回值的类型,如下图中,则表示这个Lambda表达式此时的返回值类型已经变成了String类型了:
③、Lambda表达式的参数省略形式
省略参数形式这是啥意思呢?看下面这张图:
如果lambda表达式只有一个参数的话,它可以写成下面这种形式,嗯?这个it是不是很熟悉感觉在哪见过啊?之前咱们在给数组初始化的时候是不是有用过it+1来表示每个位置是index+1的写法啊,没错就是它了。当lambda表达式只有一个参数并且你没写出来那么默认就是it:
对于Lambda表达式咱们来写点代码具体感受一下它的用法吧:
fun main() {
//匿名函数
val func: () -> Unit = fun() {
println("Hello Kotlin")
}
//lambda表达式
val lambda: () -> Unit = {
println("Hello Lambda")
}
//入参Int,返回值为String
val f1 = { p: Int ->
println(p)
"Hello f1"
}
func.invoke()
lambda()
println(f1(1)) //打印的结果应该是1和Hello f1
//构建一个Int类型数组长度为5,it表示下标0-4,打印出的结果应该是1-5
IntArray(5) {
println(it+1)
it + 1
}
}
执行结果如下:
首先来看一下我们要实现一个什么效果:定义一个HashSet,它接收Person类型的实例,然后0..5从0到5一共6个对象添加到这个HashSet里面,如果实现了Person类的equals和hashCode那么输出的结果应该是1,如果没有实现采用Kotlin中Any默认的定义的话,那么输出结果一定是6。看下面这段代码,运行一下看下结果是几呢?
class Person(val age: Int, val name: String){
}
fun main() {
val persons = HashSet()
(0..5).forEach {
persons += Person(20, "Jarchie")
}
println(persons.size)
}
执行结果:
毫无疑问的输出了6,因为这个Person类它的父类里面会实现一个equals和hashCode,此时比较的是对象的引用,因为引用不一样,所以添加到hashSet的时候它是有多少要多少,那结果显而易见的就是6了,那咱们现在是想让它输出1,那该怎么办呢?
答案也是很简单的:自行实现equals和hashCode,这个东西其实都是父类中已经定义过的,咱们就直接覆写一下吧:
//注意添加到集合中作为HashMap的key的,或者是HashSet自己对象本身的,它的equals和hashCode在对象存续
//期间一定不要发生变化,所以这里使用val
class Person(val age: Int, val name: String) {
override fun equals(other: Any?): Boolean {
//给other做一个安全的类型转换,如果能够转换就返回一个Person对象
//否则返回null,咱们使用elvis运算符,直接返回false
val other = (other as? Person) ?: return false
//如果相等则是同一个对象
return other.age == age && other.name == name
}
//判断hashcode如果是一样的不一定是同一个对象,如果不一样,一定不是同一个对象
override fun hashCode(): Int {
return 1 + 3 * age + 5 * name.hashCode()
}
}
fun main() {
val persons = HashSet()
(0..5).forEach {
persons += Person(20, "Jarchie")
}
println(persons.size)
}
此时再次执行就会得到1这个结果了:
这一部分咱们要实现的案例效果是什么样的呢?如下图所示:减法和乘法都很好理解,来说一下这个除法,这里的除法定义是咱们自己给String定义的,所以不用纠结为啥是这样的,因为它本身也没有这个定义,咱们自己捏造出来的,明白了吧,大致意思就是:判断除数在被除数中出现的次数,比如3在HelloWorld中一次都没有出现过,那么得到的商就是0,l的话出现了3次,那么结果就是3:
下面就具体的来实现一下这几个运算符,代码里面都加了注释了,应该也很容易能看懂:
//减法,替换第一个,right表示运算符右侧的运算数
operator fun String.minus(right: Any?) = this.replaceFirst(right.toString(), "")
//乘法,right表示重复几次,jointToString连接方法,分隔符为""
operator fun String.times(right: Int): String {
return (1..right).joinToString("") { this }
}
//除法
operator fun String.div(right: Any?): Int {
val right = right.toString() //转成String
//windowed窗户,在this上面滑动,每次滑动1个
return this.windowed(right.length, 1, transform = {
it == right
}) // [false, false, false, false ... false, true, ..., true]
.count { it } //只计数返回值为true的情况
}
fun main() {
val value = "HelloWorld"
println(value - "World")
println(value * 2)
println("*" * 20)
println(value / 3)
println(value / "l")
println(value / "ld")
}
执行结果:
OK,今天主要是介绍Kotlin中的常量和变量、分支表达式、运算符和中缀表达式以及Lambda表达式,内容不多不少刚刚好,另外两个综合案例大家也可以好好看看,学语言就得多写多练,熟能生巧嘛!
好了,废话不多说了,就到这里吧,咱们下期再会!
祝:工作顺利!