2017-10-03 by Liuqingwen | Tags: Kotlin | Hits
一、前言
读书笔记的上部分请参考:【读书笔记】《Kotlin in Action》学习笔记(上)
另外,关于我在 mobilehub 微信留言中免费获赠中文版《 Kotlin 实战》书籍的留言我也贴上,当时我回答的时候一方面想着能意外收获一本书,另一方面还是非常想推荐这边书给读者朋友们!
二、笔记
1、 操作符重载要注意的
a += b
与 a = a.plus(b)
或者 a.plusAssign(b)
两者都完全等同( + - * / % 一样)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]
plus
和 plusAssign
两个都有定义,参数也一样,那么会出现编译模糊问题( + - * / % 一样)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
方法)就是画蛇添足,会出现问题:
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
}
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 则作为输出而不能作为参数传入,下面两个接口就是这样,弄反了就出问题:
interface IOutParameter<out T> {
fun takeOut():T
}
interface IInParameter<T> : IOutParameter<T> {
fun takeIn(`in`: T)
}
再看类的构造函数,这是不受形参限制的,注意参数的位置:
// 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、 使用形参的一个正确姿势
这是一个非常简单的问题,对于大部分人来说,由于缺乏经验,我把这一条也作为书签记录下来,提醒自己可以如何优化(下面是官方例子)。首先看原始版本,拷贝一个列表到另一个:
fun <T> copyDataVersion1(source: MutableList<T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
上面的代码其实不合理(后面有说明),难道一定要同类型才能复制吗? T
的子类不能被复制过去吗?那么根据这个问题有了下面的改进:
fun <T: R, R> copyDataVersion2(source: MutableList<T>, destination: MutableList<R>) {
for (item in source) {
destination.add(item)
}
}
上面的代码搞定了子类的数据复制,到此结束!?当然没有, Kotlin 提供了一个更加优雅的解决方案,不信你看看下面的代码:
fun <T> copyDataVersion3(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
什么叫做优化?什么叫做改进?学习了!下面是测试代码:
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 程序也是给力极了!
看下面一句话,还是来自教材:
"kotlin" should start with "kot"
Sorry ,说错了,不是一句话,是一段代码!对,这段代码没啥稀奇的了,不就是中缀函数拼凑起来吗?
"kotlin".should(start).with("kot")
没错,但是他的精髓你发现了没?精髓在于 start
的妙用!它是一个 object
单例,那么既然是单例为啥不直接使用,还要去作为 should
函数的参数呢?这不是毫无意义吗? No !这是 DSL 哦,它并不是作为数据参数传递给函数,而是作为语法的一部分!!!因此你可以有很多 object
,作为不同的语法使用,这就是精髓之处啊!
我相信,看了下面的代码你就能一目了然、豁然开朗了!
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")
}
激动的我赶紧写下了几行流利的英语:
"kotlin" should start with "kot"
"kotlin" should end with "in"
"kotlin" should have substring "otl"
5、 Bonus: 使用 inline 属性
对,你没看错,这是额外加的一个新姿势,并不是从《 Kotlin in Action 》书中学到的,看到了我就马上记下来了,写到一起作为学习笔记吧。
参考以下代码,扩展一个属性非常简单:
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 代码(无关省略):
if(GlobalKt.isVisible((View)button)) {
ToastsKt.toast(this, (CharSequence)"I am visible!");
}
很正常啊, Kotlin 的风格,使用静态方法完成扩展呀。但是,我就是没想到为啥不用 inline 呢?省去静态方法,不是更快更方便吗?
val View.isVisible
inline get() = this.visibility == View.VISIBLE
反编译后:
View $receiver$iv = (View)button;
if($receiver$iv.getVisibility() == 0) {
ToastsKt.toast(this, (CharSequence)"I am visible!");
}
是不是更加得体了呢?反正我是这么认为的,省去了没必要的静态类方法。另外, inline 也可以写得更加优雅,也有需要注意的地方哦:
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