Kotlin相关tips

November 15, 2018 2 min read Author: Yu

Tips1<使用kotlin语法糖让java代码更简洁>

自从Kotlin1.3发布之后,协程脱离了实验环境成为了新特性其中的一员,但今天我们不讨论这个新的特性(对于JVM来说),而是关注那些自Kotlin1.0就存在并被使用的特性。

扩展方法是一个非常重要的特性。在Java中,当我们需要为一个类添加新的方法时,往往需要继承这个类并且码出这个方法,如果仅仅需要添加很少的方法(但将会很常用,有添加的必要),往往会选择写一个方法并将该类作为参数,以便实现一些操作。这样很麻烦,而且在这种工具方法比较多的时候管理起来非常混乱,所以一般还是会选择通过继承来完成。

Kotlin扩展函数允许我们直接向一个类中写入并使用这个方法,例如:需要一个方法,功能将一个字符串输出指定次数,在Java中:

void print(String s, int count) {
for (int i = 0; i < count; i++) {
System.out.println(s);
}
}

而在Kotlin中,可以为String类直接添加一个方法:

fun String.print(count: Int) {
for (i in 1..count) {
print(this)
}
}

这样就为String类新增了一个方法,调用此方法会变得非常方便:

val a: String = "no no no"
a.print(2)
"hello world".print(5)

手头刚好有一段WebMagic代码,功能是抓取指定网页:

Spider.create(new AmazonListProcessor())
.
addUrl(WEB_SITE.LIST_PAGE_FORWARD+"?pg=1")
.
addUrl(WEB_SITE.LIST_PAGE_FORWARD+"?pg=2")
.
thread(5)
.
addPipeline(new AmazonListPipeline())
.
run();

其中WEB_SITE.LIST_PAGE_FORWARD是一个静态字符串常量,是某网站的网页url,代码中用该url和参数拼出两个url并爬取,现在用扩展方法来实现:

fun String.runSpiderForUrl(processor: PageProcessor, thread: Int, pipeline: Pipeline) {
Spider.create(processor)
.addUrl(this)
.thread(thread)
.addPipeline(pipeline)
.run()
}

这样调用就会变得非常简单:

(WEB_SITE.LIST_PAGE_FORWARD + "?pg=1").runSpiderForUrl(AmazonListProcessor(), 5, AmazonListPipeline())
(WEB_SITE.LIST_PAGE_FORWARD + "?pg=2").runSpiderForUrl(AmazonListProcessor(), 5, AmazonListPipeline())

基于这个方式,还可以将代码变得更简单,我们是将url手动拼接后使用的,而拼接这个过程也可以用扩展方法实现,流程为:将一个String类型的url拼接不同的参数,得到多个url,然后进行爬取。这里至少需要两个扩展方法:一个String的扩展方法,用于为目标String拼接多个参数/后缀,将结果保存在ArrayList中并返回;还有一个ArrayList 的扩展方法,用于为ArrayList中的每个url都调用一次runSpiderForUrl方法。具体如下:

fun String.extend(vararg string: String): ArrayList<String> {
val urlList = ArrayList<String>()
for (s in string) {
urlList.add(this + s)
}
return urlList
}
fun ArrayList<String>.runSpiderForUrl(processor: PageProcessor, thread: Int, pipeline: Pipeline) {
for (string in this) {
string.runSpiderForUrl(processor, thread, pipeline)
}
}

这样,具体的爬取代码就可以改写为:

WEB_SITE.LIST_PAGE_FORWARD.extend("?pg=1", "?pg=2")
.runSpiderForUrl(AmazonListProcessor(), 5, AmazonListPipeline())

简洁又清爽。

Tips2<在kotlin中使用运算符重载>

Kotlin作为兼容Java的,提供了一个Java没有的特性:运算符重载;大家可能在C++中使用或者耳闻,却在Java中没有见过,因为Java并不支持运算符重载,可能和Java严格的OOP思想有关,有的人可能会说Java中的字符串拼接难道不是运算符重载吗?剥开编译后的.class文件可知,String类型的a+b的实现其实是new StringBuilder(a).append(b).toString() 实现的,并不是真正意义上的运算符重载。

Kotlin中的运算符重载的本质是方法重载;格式很简单,与扩展方法的写法类似:

operator fun String.plus(target: Array<String>): ArrayList<String> {
return this.extend(*target)
}

以上就是对String的加法运算进行重载,其中operator为关键字,表示这是一个重载方法,plus为重载的符号对应的方法名,显然+对应的是plus,其他符号也有对应的方法,下面列举的是一些常用的方法:

符号方法
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.mod(b)
a..ba.rangeTo(b)
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()

实战:实现ArrayList的重载

有时候,我们需要在多个字符串中同时追加一段相同的内容,这个当然可以用循环实现并且封装为一个扩展方法,为了使最终代码更直观,我们使用运算符重载来实现:

operator fun ArrayList<String>.plus(target: String): ArrayList<String> {
val res = ArrayList<String>()
this.forEach { i -> res.add(i + target) }
return res
}

在方法内部,初始化一个新的ArrayList,并将原ArrayList中的字符串遍历拼接然后添加进新的ArrayList中,返回新的ArrayList。

快速初始化一个包含字符串hello0到hello9的ArrayList来测试:

fun main(args: Array<String>) {
val a = ArrayList<String>()
a.addAll(Array(10) { i -> "hello$i" })
print(a + " world")
}

同理,可以用运算符重载简化更繁杂的代码,但同时要注意:不要滥用运算符重载,这可能导致很多问题,例如你覆盖了原本某个类的运算符的含义但是却忘记了此事,或者你的小组内其他成员不知道你在某处重载了运算符,这也许也是Java为什么没有提供运算符重载的原因。