Kotlin笔记07(延迟初始化,密封类,扩展函数,运算符重载)

  1. 1. 延迟初始化
  2. 2. 密封类
  3. 3. 扩展函数
  4. 4. 运算符重载
  5. 5. 扩展函数和运算符重载的应用

延迟初始化

Kotlin的判空等特性,都是为了保证程序安全所设计的,但是有时这些设计会变得比较麻烦。

对于类中存在很多全局变量,为了保证满足kotlin的空指针检查,不得不写上很多非空判断保护才行。问题解决办法则是,对全局变量进行延迟初始化。

1
2
3
class TestClass{
private lateinit var value:String
}

这里lateinit关键字,告诉kotlin我会晚些对这个变量初始化。此时value就不用在一开始就赋值为null,并且也不用做判空处理。

注意:使用lateinit也是有风险的,如果在没有初始化的时候使用它,同样会崩溃。所以使用lateinit的前提是,必须确保在他被任何地方调用之前,已经完成初始化工作。

Android例子:

1
2
3
4
5
6
7
8
class MainActivity : AppCompatActivity(), View.OnClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
adapter = MsgAdapter(msgList)
...
}
}

判断变量是否被初始化

1
2
3
4
5
6
7
8
9
10
class MainActivity : AppCompatActivity(), View.OnClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
if(!::adapter.isInitialized){
adapter = MsgAdapter(msgList)
}
...
}
}

::adapter.isInitialized可以用来判断变量是否初始化,这个是固定用法。

密封类

密封类关键字:sealed class

场景:

创建一个Result.kt

1
2
3
interface Result
class Success(val msg:String) : Result
class Failure (val error:Exception):Result

定义Result接口,Success 和Failure实现这个接口。然后使用:

1
2
3
4
5
6
7
fun getResultMsg(result:Result){
when(result){
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
}

这里的else是必须写的,否则Kotlin认为缺少分支条件无法编译通过,实际上Result的结果只可能是Success和Failure,这里的else完全是为了通过kotlin的检查语法而已。另一个缺点就是,当需要添加新的类UnKnown类,也是实现Result接口,用来实现未知情况的处理,但是忘了修改getResultMsg方法,此时编译器也不会提示,而是进入else。

利用 sealed class

1
2
3
sealed class Result
class Success(val msg:String) : Result()
class Failure (val error:Exception):Result()

密封类是可以被继承的,因此需要添加括号。

此时getResultMsg方法可以写成

1
2
3
4
5
6
fun getResultMsg(result:Result){
when(result){
is Success -> result.msg
is Failure -> result.error.message
}
}

当when语句传入密封类时,kotlin会自动检查这个密封类有哪些子类,并强制要求每个子类对应的条件全部处理,这样能够保证没有else条件,也不可能出现漏写分支的情况。

扩展函数

扩展函数:即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数。

语法结构:

1
2
3
fun ClassName.methodName(param1 : Int,param2 : Int):Int{
return 0
}

情景:一段字符串可能有字母、数字、特殊符号,我们希望统计其中的字母数量

1
2
3
4
5
6
7
8
9
10
11
object StringUtil{
fun lettersCount(str:String):Int{
var count = 0
for(char in str){
if(char.isLetter()){
count++
}
}
return count
}
}

这是常见的实现过程,使用扩展函数

1
2
3
4
5
6
7
8
9
fun String.lettersCount():Int{
var count = 0
for(char in this){
if(char.isLetter()){
count++
}
}
return count
}

将lettersCount方法定义为String的扩展函数,他就自动带有了String上下文,不再需要接收参数了。定义好扩展函数之后,就可以直接这么用了

1
val count = "ABC1234#xyz!@#".lettersCount()

扩展函数能够让API更加简洁,String类是一个final类,任何一个类都不能继承他,但是在kotlin就不一样了,可以向String扩展任何函数,让他的api更加丰富。让编程更加简便。

运算符重载

语法结构:

1
2
3
4
5
class Obj{
operator fun plus(obj:Obj):Obj{
//处理逻辑
}
}

Kotlin的运算符重载允许我们让任意两个对象进行相加

举个栗子:grin:

1
class Money(val value:Int)

Money类,默认赋值value

接下来重载实现两个Money相加

1
2
3
4
5
6
7
8
9
10
class Money(val value:Int){
operator fun plus(money : Money):Money{
return Money(value + money.value)
}
}
//调用
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
println(money3.value)

那么我想Money直接和数字相加呢,Kotlin是允许运算符多重重载的。

1
2
3
4
5
6
7
8
9
10
11
12
class Money(val value:Int){
operator fun plus(money : Money):Money{
return Money(value + money.value)
}
operator fun plus(money : Int):Money{
return Money(value + money)
}
}
//调用
val money1 = Money(5)
val money2 = money1 + 10
println(money2.value)

附一下运算符的对照表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
运算符对照表
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++ a.inc()
a-- a.dec()
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a == b a.equals(b)

a > b
a < b
a >= b a.compareTo(b)
a <= b

a..b a.rangeTo(b)
a[b] a.get(b)
a[b] = c a.set(b,c)
a in b b.contains(a)
*/

扩展函数和运算符重载的应用

这里比较好玩的东西就是这俩结合起来。

让String类型的字符串用上乘法表达式,让他重复n遍。

1
2
3
4
5
6
7
operator String.times(n:Int):String{
val builder = StringBuilder()
repeat(n){
builder.append(this)
}
return builder.toString()
}

使用:

1
2
val str = "abc" * 3
println(str)

kotlin也提供了重复n遍的repeat函数,因此可以这么写

1
operator String.times(n:Int) = repeat(n)