怎么样编写地道的Kotlin代码
Posted ihrthk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎么样编写地道的Kotlin代码相关的知识,希望对你有一定的参考价值。
怎么样编写地道的Kotlin代码
今天我打算谈论一下怎么编写地道的(Idimo)Kotlin代码,『地道』意思就符合Kotlin代码的习惯。我今天要讲的是如何摆脱Java编码习惯,开启你的Kotlin的编码风格。
好,我准备了一个代码Demo。
1.类的声明Bean(String name, int age)
这是用Java声明了一个Bean,它包含两个域name和age
public class Bean
private final String name;
private final int age;
public Bean(String name, int age)
this.name = name;
this.age = age;
public String getName()
return name;
public int getAge()
return age;
对应的Kotlin代码应该这样写
class Bean2
private var name: String
private var age: Int
constructor(name: String, age: Int)
this.name = name
this.age = age
fun getName(): String
return name
fun getAge(): Int
return age
我们中许多人是通过Java编程语言建立的知识来编程的。Kotlin是受到多种语言的启发,包括Java语言,所以这么写是可以正常工作的。但我们『绝对不应该』这么写Kotlin代码。
那么你是实际上应该做的是把所有的不必要的东西。我想说的是这个类,有两个属性,仅此而已。(这个类有一个主构造器,两个属性参数)
class Bean3(val name: String, val age: Int)
Kotlin演示了声明一个类是如此的简单。
2.数据类data class
看下这个代码,它表示解析一个名字,把它的firstName和lastName放到容器里。因为我没有办法,在一个函数里返回两个值,我不得不把它打包放进一个对象里。
fun parseName(name: String): List<String>
val space = name.indexOf(" ")
return listOf(
name.substring(0, space),
name.substring(space + 1)
)
fun main(args: Array<String>)
val name = parseName("Jane Doe")
val first = name[0]
val last = name[1]
println("$first $last")
if (name == parseName("Jane Doe"))
println("Equals is work")
我在这里滥用List容器,然后笨拙地把firstName和lastName都放进容器里。
事实上,即使用任何编程语言,你不应该这么写代码。
但是有一些现实的原因,我们可能会这样做。那就是:声明一个类的成本是很高的!
你需要找到一个合适package,然后创建一个文件,最后在里面写很多代码。
在Kotlin中,你不在需要这样做。你仅仅通过这样一行代码,
声明一个FullName类,它有first和last两个属性就可以了。
class FullName(val first: String, val last: String)
写一个类是简单的,节约了你很多时间,也节约了你的脑力劳动。
你可以在一个文件里声明多个类,它也不不会花费你任何东西。
我现在运行这个代码,发现它们是不相等的,因为我没有重写equals方法和hashCode方法。它是如此繁琐的,在Kotlin中,你真的不需要这样做。
因为你可能知道有一种叫做数据类的东西,在class前面加一个data就可以了。
编译器会帮我们生成equals和hashCode方法(还有copy和toString方法)。
写一个类是麻烦的这样的想法,在Kotlin是需要改变一下了。
3.属性
我们已经谈论了类,现在我们讲属性,然后是讲函数。
private var prop: String = "..."
fun getProp() = prop
fun setProp(v: String)
println("New value $v")
prop = v
刚才我已经给你展示是了,用一行代码生成get和set方法。也就是在Kotlin中不需要写getXXX和setXXX方法。
如果你想要自定义seter方法,你也不需要为此单独声明一个函数,因为你可以使用『属性语法』。代码如下:
private var prop: String = "..."
set(value)
println("New value $value")
field = value
在这个自定义里面,你有filed,但是不需要域去存储,你也不用声明额外的名字,是的,就是这么简单直接!
4.属性委托
看一下这个代码:
private var _os: String? = null
val os: String
get()
if (_os == null)
println("Computing...")
_os = "android 12"
return _os!!
这里已经有一些合理的逻辑,我有两个属性,一个是可空私有,另一个是不可空公有。
我第一次访问时,我检查它是否为空,然后计算一个值并写入其中,然后我输出它。
因此,这是什么呢?是的,这就是属性懒加载。
大家在Java语言中,写过很多这样的代码,这样的代码是很无聊的,这就是为什么Kotlin有一种机制叫做属性委托。所以委托属性让你摆脱这些的重复懒加载的逻辑。
代码如下:
val os: String by lazy
println("Computing...")
"Android 12"
这个属性不是简单的初始化。这是委托给这个lazy。在第一次访问时,这个
Lambda将被执行。然后这个值会被储存起来,以便下次访问会使用。
这个lazy不是关键字,它仅仅是一个库函数,你可以定义你自己的。
5.自定义属性委托
这里有一种像观察者的属性,有些东西被修改,你将会被通知到。
你可以使用库函数,也可以自己实现。在这里使用了一个delegates.observable的库函数去完成这个工作。
var observeMe by Delegates.observable("a") p, old, new ->
println("$p.name goes $old -> $new")
fun main(args: Array<String>)
println("Observable property:")
observeMe = "bb"
observeMe = "ccc"
observeMe = "dddd"
但是如果你喜欢,你不想这么写代码,你可以自己实现一个。
自定义属性委托,就是声明一个类它封装了你的属性的逻辑,比如geter方法和setter方法。
你现在可以简单地引用这个类在许多属性中,获取您的业务逻辑数据库访问,以及各种验证。
你可以把这个做成一个库,重用到在你的项目上。
这里举一个库例子:
https://github.com/MarcinMoskala/PreferenceHolder
var canEatPie: Boolean by bindToPreferenceField(true)
你实际上应该这么做,我确定你能够从中收益的。
6.函数
现在,我们开始讨论一下函数,函数是非常重要的,因此看一下下面这个代码:
class StringUtil
fun getFirstWorld(s: String, separator: String): String
val index = s.indexOf(separator)
return if (index < 0) s else s.substring(0, index)
fun getFirstWorld(s: String) = getFirstWorld(s, " ")
fun main(args: Array<String>)
println(StringUtil().getFirstWorld("Jane Doe"))
这个代码是非常糟糕的,请不要在Kotlin中这样写代码。
这是受我们Java编程习惯的启发,我们不得不把所有的东西都放进class里,因此就有了StringUtil。
你们写过有自己的StringUtil吗?oh,如果没有的话,那一定是一个新项目。
但是在Kotlin中,这里有点不同。你不需要使用一个类把这些方法或者属性包裹起来。
- 首先,Kotlin类没有静态,因此为了使用这些函数,我们不得不每次都new一个StringUtil对象。我不想每次都new一个对象,所以类转换为对象。这个代码有了一点提高。
- 事实上,我不需要一个对象,通过一个类就可以调用这个函数。在Kotlion中,我们有顶层函数。现在已经很像Kotlin代码了。
- 但是它不是很好的Kotlin代码,因为这里有两个重载。所以getFirstWord假设要解析字符串,找到第一个空格,然后取第一个单词,然后返回它。但是如果分隔符不是空格,而是逗号什么的?所以,这里有全功能的版本。我想表达的是这里只是一个默认值。在Java中,我们不得不使用重载来模拟。在Kotlin中,直接指定默认值就好了。当你有很多默认参数不同的值,比如多个布尔值,等等。你可以只使用命名参数语法来表示您实际需要的。并且剩余其他的继续使用默认值。现在代码更少了,有更强的表现力。
- 现在的代码,介于Java风格和Kotlin风格中间的状态,因为,这个函数的范围应该是String,而不是全局的。最好是把这个函数,放进String类里面去。Oh不,你不能控制String,你无法把任何东西放进这个类里。并且,你真的应该保持String类的api最小化。但是我真想要String有一个函数叫做getFirstWorld,那怎么办呢?在Kotlin中,有扩展函数去完成。这个只是在语法上给String添加了额外的函数,实际上你没有改变JDK里面String类的源码。我现在手动演示一下,它是怎么工作的!因为,我有一个接受者类型String,我不在需要这个参数了。我可以说这个点这里,或者省略所有在左边这些。现在我能使用它了。
- 同时我也能扩展属性来去做。事实上,用扩展属性来做会非常好。
就是这样,因此,扩展函数和扩展属性,是非常重要的。这不仅仅是方便,它可以让你保持你类的API最小化,并且充满无限想象。
现在你看下Kotlin的String类,它仅有5个函数,相比较Java一屏又一屏的方法,简洁的太多了。并且所有的工具函数可以都可以是扩展,可以把这些函数放在不同的库里面,这样就把String类也做到了模块化。
这样做是对于工具类api设计是非常重要的。
7.遍历树
现在看一下这个代码:在这里,我编写的一个非常典型遍历树的代码。我有个容器和叶子元素。容器可以嵌套其他的,而叶子元素持有文字。我要取出这个结构中所有的的文本。
因此我定义三个类,Element,Container和Text。Container可以包含很多Element,而Text可以持有文本。
现在,我要遍历这个。所以我使用扩展函数。我用的是顶级函数,我前面说过的。
这都是正确的,但是我不喜欢这个代码,为什么呢?
fun main(args: Array<String>)
val root = Container(
Text("a"),
Container(
Text("b"),
Container(
Text("c"),
Text("d")
),
Text("e")
),
Text("f")
)
println(root.extractText())
abstract class Element
class Container(vararg val children: Element) : Element()
class Text(val text: String) : Element()
fun Element.extractText(): String
return extractText(this, StringBuilder()).toString()
fun extractText(e: Element, sb: StringBuilder): StringBuilder
if (e is Text)
val text = e as Text
sb.append(text.text)
else if (e is Container)
val container = e as Container
for (child in container.children)
extractText(child, sb)
else
throw Exception("Unrecognized element:$e")
return sb
- 这里,为了遍历层次结构,我需要递归。我需要传递stringBuilder沿着堆栈向下。
- 然后是顶层函数,这个不是在函数内部之外的任何地方都需要。所以我想
就是把它放进里面去。
把这两个问题修改好,就是这样的。
fun Element.extractText(): String
val sb = StringBuilder()
fun extractText(e: Element): StringBuilder
if (e is Text)
val text = e as Text
sb.append(text.text)
else if (e is Container)
val container = e as Container
for (child in container.children)
extractText(child)
else
throw Exception("Unrecognized element:$e")
return sb
extractText(this)
return sb.toString()
现在有一点提高,你看见哪些灰色的代码了吗?哪些是无用的。
fun Element.extractText(): String
val sb = StringBuilder()
fun extractText(e: Element): StringBuilder
if (e is Text)
sb.append(e.text)
else if (e is Container)
e.children.forEach(::extractText)
else
throw Exception("Unrecognized element:$e")
return sb
extractText(this)
return sb.toString()
我们if else 替换成when
fun Element.extractText(): String
val sb = StringBuilder()
fun extractText(e: Element): StringBuilder
when (e)
is Text ->
sb.append(e.text)
is Container ->
e.children.forEach(::extractText)
else ->
throw Exception("Unrecognized element:$e")
return sb
extractText(this)
return sb.toString()
使用密封类,把when里面的else去掉
fun Element.extractText(): String
val sb = StringBuilder()
fun extractText(e: Element): StringBuilder
when (e)
is Text -> sb.append(e.text)
is Container -> e.children.forEach(::extractText)
return sb
extractText(this)
return sb.toString()
我们用Kotlin写同样逻辑,代码更少,逻辑逻辑更清晰,表现力更强。
现在,局部函数,扩展函数、顶级函数、默认参数——使用这些。
它们将使您的代码更好。
8.表达式
看一下,下面这些例子表达方式写起来就像在脑海中旧的习惯一样。我们会尝试变换让他们变得更好。
class Example(val a: Int, val b: String?, val c: Boolean)
fun main(args: Array<String>)
var ex = Example(1, null, true)
val a = ex.a
val b = ex.b
val c = ex.c
println("a = " + a + ", b = " + b + ", c = " + c)
val map = HashMap<String, Int>()
map.put("k1", 1)
map.put("k2", 2)
map.put("k3", 3)
for (e in map.entries)
val key = e.key
val value = e.value
println(key + " -> " + value)
var s: String
if (System.currentTimeMillis() % 2L == 0L)
println("Yal!")
s = "Luck!"
else
s = "Not this time"
println(s)
fun test(e: Example): String
when (e.a)
1 -> return "Odd"
3 -> return "Odd"
5 -> return "Odd"
2 -> return "Even"
4 -> return "Even"
6 -> return "Even"
else -> return "Too big"
fun test2(str: String?): String?
println(str!!.length)
if (str != null)
str.forEach it ->
println(it)
if (str == null) return null
if (str == null) throw RuntimeException("Exception")
if (str == null) error("error")
return ""
第一件事这里要说的是var。我不能说永远不要使用var。var是有用的。可变变量可以用于许多事情上。但是它不是被鼓励的。如果你需要var,你需要一个很好的理由。
这里没有好的理由使用val,这是肯定的。
val ex = Example(1, null, true)
这里,让我们看看这三个。它是重复,重复是丑陋的。重复是容易出错,特别是当这不是单一的名字,但被许多事情所束缚。因此,我想要摆脱这种重复。这里我可以用with函数解决。它有个陌生的东西,这是一个建筑结构。在Kotlin中,它是一个函数。在这里,我们可以得到摆脱所有与ex有关的事情,就像这样。
with(ex)
val a = a
val b = b
val c = c
println("a = " + a + ", b = " + b + ", c = " + c)
但现在看起来是更愚蠢的,对吧?我只是在分配相同的变量。不要这样做。现在我打印了一行,字符串加某物,字符串加某物,字符串加东西。这是尴尬的。现在大多数语言都有『string interpolation』,Kotlin也有。使用字符串插值,它是很好。
with(ex)
println("a = $a, b = $b, c = $c")
现在,我在创建了一个map。我可以把它变的好一点,通过使用我的索引操作符。
val map = HashMap<String, Int>()
map["k1"] = 1
map["k2"] = 2
map["k3"] = 3
但是我可以使用builder function让它更好一点,因次我能做的是用Pairs替代所有的key和value。
val map = mapOf(Pair("k1", 1), Pair("k2", 2), Pair("k2", 2))
是的,所以map可以由Pair去构建。而map只是一系列的由键到值组成的Pair。但实际上,Pair在这里是有点多余。所以我们通常用to function去替代。它不是一个内置操作符,只是一个库函数。这里,这就是如何你创建一个Map。
val map = mapOf("k1" to 1,"k2" to 2,"k3" to 3 )
下面关于if语句的语法,是我非常讨厌的,因为它的声明和赋值是分开的。所以我很喜欢在Kotlin这样做。所以如果这样做的,你会发现还有很多这样的事情,实际上就是用『表达式』代替『语句』。这是对于C语言家族来说很陌生。我们习惯于把声明和赋值进行分开。你把表达式赋值给变量和写语句来指定一个值。
var s: String
if (System.currentTimeMillis() % 2L == 0L)
println("Yal!")
s = "Luck!"
else
s = "Not this time"
println(s)
当然,这里你就不用使用一个var,你不用另外写一行,你可以在一行指定的值。因此,表达式是更好的。顺便说一下,这个表达式的结果是语句块中的最后一项。
val s = if (System.currentTimeMillis() % 2L == 0L)
println("Yal!")
"Luck!"
else
"Not this time"
看下这个when的例子,when不像Java的switch那样呆板固执,它是很强大的,也很重要的。你也可以这么做像这样写代码,对吧?
fun test(e: Example): String
when (e.a)
1 -> return "Odd"
3 -> return "Odd"
5 -> return "Odd"
2 -> return "Even"
4 -> return "Even"
6 -> return "Even"
else -> return "Too big"
但是这里如果只有一个返回值会更好一些,像这样
fun test(e: Example): String
return when (e.a)
1 -> "Odd"
3 -> "Odd"
5 -> "Odd"
2 -> "Even"
4 -> "Even"
6 -> "Even"
else -> "Too big"
你也不必重复这些case,你可以把他们合在一起。像这样
fun test(e: Example): String
return when (e.a)
1, 3, 5 -> "Odd"
in setOf(2, 4, 6) -> "Even"
else -> "Too big"
是的,所以这个可以进一步简化。再一次,你在努力消除干扰。
fun test(e: Example) = when (e.a)
1, 3, 5 -> "Odd"
in setOf(2, 4, 6) -> "Even"
else -> "Too big"
顺便说一下,如果你想检查奇偶数,不要像我这样做。这只是为了演示目的。
9.函数类型
让我们来看一些函数风格。
fun main()
val numbers = (1..100).toList()
val list = mutableListOf<String>()
for (it in numbers)
if (it % 16 == 0)
list.add("0x" + it.toString(16))
println(list)
repeat(6)
println(it)
fun repeat(times: Int, body: (Int) -> Unit)
for (index in 0 until times)
body(index)
所以人们经常将Kotlin称为函数式语言。事实上,我不这么认为。我认为Kotlin是一个支持函数式风格的多范式语言。
你用Kotlin不用一定要写函数风格的代码,但它通常这这样做会更好的。
fun main()
val list = (1..100).toList()
.filter
it % 16 == 0
.map
"0x" + it.toString(16)
println(list)
repeat(6)
println(it)
fun repeat(times: Int, body: (Int) -> Unit)
for (index in 0 until times)
body(index)
以上是关于怎么样编写地道的Kotlin代码的主要内容,如果未能解决你的问题,请参考以下文章