【读书笔记】《Kotlin in Action》学习笔记(下)

2017-10-03 by Liuqingwen | Tags: Kotlin | Hits

一、前言

读书笔记的上部分请参考:【读书笔记】《Kotlin in Action》学习笔记(上)

另外,关于我在 mobilehub 微信留言中免费获赠中文版《 Kotlin 实战》书籍的留言我也贴上,当时我回答的时候一方面想着能意外收获一本书,另一方面还是非常想推荐这边书给读者朋友们! grin

kotlin_in_action_wx.jpg

二、笔记

1、 操作符重载要注意的

  • a += ba = a.plus(b) 或者 a.plusAssign(b) 两者都完全等同( + - * / % 一样)
1
2
3
4
val list = arrayListOf(1, 2)
list += 3 //list = [1, 2, 3]
var newList = list + 4 //newList = [1, 2, 3, 4]
newList = list + listOf(5, 6) //newList = [1, 2, 3, 5, 6]
  • 如果 plusplusAssign 两个都有定义,参数也一样,那么会出现编译模糊问题( + - * / % 一样)
1
2
3
4
5
6
7
8
9
10
11
12
data class Point(var x:Int = 0, var y:Int = 0) {
operator fun plus(otherPoint: Point):Point {
return Point(otherPoint.x + this.x, otherPoint.y + this.y)
}
}

fun main(vararg parameters:String) {
var p_var = Point()
val p_val = p_var + Point(1, 1)
p_val += p_var //Error: val cannot be reassigned.
p_var += p_val //OK!
}

上面的代码很显然是没问题的,注意 val 变量不能赋值。但是,如果添加下面的代码( 通过扩展给 Point 类新增 plusAssign 方法)就是画蛇添足,会出现问题:

1
2
3
4
5
6
7
8
9
10
11
operator fun Point.plusAssign(otherPoint:Point) {
this.x += otherPoint.x
this.y += otherPoint.y
}

fun main(vararg parameters:String) {
var p_var = Point()
val p_val = p_var + Point(1, 1)
p_val += p_var
p_var += p_val //Error: Assignment operators ambiguity
}
  • 把上面的 plusAssign 方法签名(参数类型)改一下可以使用,但意义已经改变
1
2
3
4
5
6
7
8
9
operator fun Point.plusAssign(otherInt:Int) {
this.x += otherInt
this.y += otherInt
}

fun main(vararg parameters:String) {
var p_var = Point()
p_val += 99
}

2、 型变和协变( in 和 out )参数在构造函数中不受约束

这又是一个特例!我们知道,使用 in 的参数是不能作为输出返回的,而使用 out 则作为输出而不能作为参数传入,下面两个接口就是这样,弄反了就出问题:

1
2
3
4
5
6
interface IOutParameter<out T> {
fun takeOut():T
}
interface IInParameter<T> : IOutParameter<T> {
fun takeIn(`in`: T)
}

再看类的构造函数,这是不受形参限制的,注意参数的位置:

1
2
3
4
// Note that constructor parameters are in neither the [in] nor [out] position.
// Even if a type parameter is declared as out, you can still use it in a constructor parameter declaration
open class Animal
class Herd<out T: Animal>(vararg animals: T)

3、 使用形参的一个正确姿势

这是一个非常简单的问题,对于大部分人来说,由于缺乏经验,我把这一条也作为书签记录下来,提醒自己可以如何优化(下面是官方例子)。首先看原始版本,拷贝一个列表到另一个:

1
2
3
4
5
fun <T> copyDataVersion1(source: MutableList<T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}

上面的代码其实不合理(后面有说明),难道一定要同类型才能复制吗? T 的子类不能被复制过去吗?那么根据这个问题有了下面的改进:

1
2
3
4
5
fun <T: R, R> copyDataVersion2(source: MutableList<T>, destination: MutableList<R>) {
for (item in source) {
destination.add(item)
}
}

上面的代码搞定了子类的数据复制,到此结束!?当然没有, Kotlin 提供了一个更加优雅的解决方案,不信你看看下面的代码:

1
2
3
4
5
fun <T> copyDataVersion3(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}

什么叫做优化?什么叫做改进?学习了!下面是测试代码:

1
2
3
4
5
6
7
fun main(vararg parameters:String) {
val source = arrayListOf(1, 2, 3)
val destination = arrayListOf<Any>()
//copyDataVersion1(source, destination) //Error! Cannot compile!
copyDataVersion2(source, destination) //Fine.
copyDataVersion3(source, destination) //Nice!
}

4、 Kotlin 中 DSL 使用带有 object 参数的中缀函数

我只想说,“厉害了,我的 Kotlin 哥”! Kotlin 中 DSL 真的很好用,像大名鼎鼎的 anko 库,使用 DSL 实现 Android Layout 非常给力啊,还有 SQL 数据库操作,另外用过一段时间的 TornadoFX ,用 DSL 写 GUI 程序也是给力极了!

看下面一句话,还是来自教材:

1
"kotlin" should start with "kot"

Sorry ,说错了,不是一句话,是一段代码!对,这段代码没啥稀奇的了,不就是中缀函数拼凑起来吗?

1
"kotlin".should(start).with("kot")

没错,但是他的精髓你发现了没?精髓在于 start 的妙用!它是一个 object 单例,那么既然是单例为啥不直接使用,还要去作为 should 函数的参数呢?这不是毫无意义吗? No !这是 DSL 哦,它并不是作为数据参数传递给函数,而是作为语法的一部分!!!因此你可以有很多 object ,作为不同的语法使用,这就是精髓之处啊!

我相信,看了下面的代码你就能一目了然、豁然开朗了!

1
2
3
4
5
object start
infix fun String.should(x: start): StartWrapper = StartWrapper(this)
class StartWrapper(val value: String) {
infix fun with(prefix: String) = if (!value.startsWith(prefix)) throw AssertionError("String does not start with $prefix: $value") else println("OK")
}

激动的我赶紧写下了几行流利的英语: joy

1
2
3
"kotlin" should start with "kot"
"kotlin" should end with "in"
"kotlin" should have substring "otl"

5、 Bonus: 使用 inline 属性

对,你没看错,这是额外加的一个新姿势,并不是从《 Kotlin in Action 》书中学到的,看到了我就马上记下来了,写到一起作为学习笔记吧。

参考以下代码,扩展一个属性非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
inline var View.isVisible
get() = visiblity = Visible

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_activity_main)
val button = this.findViewById<Button>(R.id.button)
if(button.isVisible) {
toast("I am visible!")
}
}
}

毫无疑问代码是没有问题的,那么我们看下反编译 Kotlin 后的 Java 代码(无关省略):

1
2
3
if(GlobalKt.isVisible((View)button)) {
ToastsKt.toast(this, (CharSequence)"I am visible!");
}

很正常啊, Kotlin 的风格,使用静态方法完成扩展呀。但是,我就是没想到为啥不用 inline 呢?省去静态方法,不是更快更方便吗?

1
2
val View.isVisible
inline get() = this.visibility == View.VISIBLE

反编译后:

1
2
3
4
View $receiver$iv = (View)button;
if($receiver$iv.getVisibility() == 0) {
ToastsKt.toast(this, (CharSequence)"I am visible!");
}

是不是更加得体了呢?反正我是这么认为的,省去了没必要的静态类方法。另外, inline 也可以写得更加优雅,也有需要注意的地方哦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline val View.isVisible
get() = this.visibility == View.VISIBLE

inline var View.someVarProperty:String
get() = "OK"
set(value) {
println("Value was set!")
}

//Error! Won't compile!
var upperCaseString:String = ""
inline get() = field.toUpperCase()
inline set(value) {
println("Field set!")
}

更多可以参考原文: Inlining Kotlin Properties

三、完


Comments: