Kotlin学习_惯用语法和代码风格(Idioms And Coding Conventions)

Concise

本文是学习Kotlin的惯用语法和代码风格,与Java的语法和代码风格有一些相同,也有一些不同。

创建DTO’s(POJOs/POCOs)数据类(Creating DTOs (POJOs/POCOs))

1
data class Customer(val name: String, val email: String)

这里的Customer是数据类,在Kotlin中,会自动为Customer类生成以下方法

  • 给所有属性添加getter方法,如果是var类型的话会添加setter方法
  • equals()
  • haseCode()
  • toString()
  • copy()
  • 给所有属性添加component1()component2(),…

函数参数默认值(Default values for function parameters)

可以为函数的参数设置默认值

1
fun foo(a: Int = 0, b: String = "") { ... }

过滤list(Filtering a list)

使用扩展函数filter来过滤list

1
val positives = list.filter { x -> x > 0 }

或者用it来替换x

1
val positives = list.filter { it > 0 }

判断实例(Instance Checks)

通过用is来判断是否属于某个实例

1
2
3
4
5
when (x) {
is Foo -> ...
is Bar -> ...
else -> ...
}

遍历map/list(Traversing a map/list of pairs)

1
2
3
for ((k, v) in map) {
println("$k -> $v")
}

其中k和v可用任意命名

只读list/map(Read-only list/map)

只读就是设置为常量

1
2
val list = listOf("a", "b", "c") 
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

访问map(Accessing a map)

对map的使用

1
2
println(map["key"])
map["key"] = value

延时加载(Lazy property)

lazy 用于延时加载,即第一次使用时才执行初始化的操作。

1
2
3
val p: String by lazy {
// compute the string
}

扩展函数(Extension Functions)

1
2
fun String.spcaceToCamelCase() { ... }
"Convert this to camelcase".spcaceToCamelCase()

前面有提到filter扩展函数,扩展函数就是创建可直接使用的方法,例如我要创建一个可直接使用的toast方法,可以这样写

1
2
3
4

fun Context.toast(content: String?) {
Toast.makeText(this, content, Toast.LENGTH_SHORT).show()
}

调用的时候

1
2
3
4
// activity里面
toast("扩展函数")
// 有context的时候
mContext.toast("扩展函数")

创建单例(Creating a singleton)

Kotlin创建单例模式就比较简单了

1
2
3
object Resource {
val name = "Name"
}

如果不为空则…的简写(If not null shorthand)

1
2
3
val files = File("Test").listFiles()
// 对files加了不为空的判断(?)
println(files?.size)

如果不为空则…否则…的简写(If not null and else shorthand)

1
2
3
val files = File("Test").listFiles()
// 对files加了不为空的判断(?),同时加了为空的判断(?:)
println(files?.size ?: "empty") // 如果files为空则打印empty

如果为空,则执行语句(Executing a statement if null)

使用?:符号,为空时执行语句

1
2
3
4
5
val data = ...
val email = data["email"] ?: throw IllegalStateException("Email is missing!")
val name = data["name"] ?:let{
...
}

如果不为空,则执行语句(Execute if not null)

使用?符号,加非空判断,.let{ }是不为空的时候执行

1
2
3
4
5
val data = ...

data?.let {
... // 如果不为空,请执行此块操作
}

返回 when 判断(Return on when statement)

返回的时候可以直接用when表达式返回值

1
2
3
4
5
6
7
8
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}

如果一个函数只有一个并且是表达式函数体并且是返回类型自动推断的话,可以直接等于返回值

1
2
3
4
5
6
fun transform(color: String): Int = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}

try / catch表达式(‘try/catch’ expression)

Kotlin的try/catch表达式同Java类似,Kotlin可以直接赋值

1
2
3
4
5
6
7
8
9
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}

// 处理result
}

方法使用Builder模式返回Unit(Builder-style usage of methods that return Unit)

1
2
3
4
fun arrOfMinusOnes(size: Int): IntArray{
// 生成一个大小为size,每个值为-1的数组
return IntArray(size).apply{ fill(-1) } // fill(-1)设置为值为-1
}

单表达式函数(Single-expression functions)

前面说过如果一个函数只有一个并且是表达式函数体并且是返回类型自动推断的话,可以直接等于返回值

1
fun theAnswer() = 42

等同于

1
2
3
fun theAnswer(): Int {
return 42
}

使用with调用一个对象实例的多个方法(Calling multiple methods on an object instance (‘with’))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
// 如果正常的写
myTurtle.penDOwn()
for(i in 1..4) {
myTurtle.forward(100.0)
myTurtle.turn(90.0)
}
myTurtle.penUp()
// 使用with将对象实例引用,代码减少很多
with(myTurtle) {
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}

同样,在Adapter里面可以直接使用with来替换holder.itemView

1
2
3
4
5
6
7

with(holder.itemView) {
// 名称
new_item_text.text = mDatas?.get(position)?.desc
// 作者
new_item_user.text = mDatas?.get(position)?.who.let { Constant.NEW_ITEM_USER_NULL }
}

Java 7的try with resources(Java 7’s try with resources)

try-with-resources语句是一个声明一个或多个资源的 try语句。一个资源作为一个对象,必须在程序结束之后随之关闭。
在Java 7 之前, 可以使用finally块来确保资源被关闭,不管try语句正常地完成或是发生意外。而try-with-resources语句确保在语句的最后每个资源都被关闭 。

1
2
3
4
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}

泛型转换(Convenient form for a generic function that requires the generic type information)

1
2
3
4
5
6
//  public final class Gson {
// ...
// public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
// ...

inline fun <reified T: Any> Gson.fromJson(json): T = this.fromJson(json, T::class.java)

使用可空的Boolean(Consuming a nullable Boolean)

添加?符号可以让变量可为空

1
2
3
4
5
6
val b: Boolean? = ...
if (b == true) {
...
} else {
// `b` 是false或者为null
}

命名风格(Naming Style)

Kotlin的命名风格与Java的类似

  • 使用骆驼命名法(在命名中避免下划线)
  • 类型名称首字母大写
  • 方法和属性首字母小写
  • 缩进用四个空格
  • ……

冒号(Colon)

在冒号之前有一个空格,冒号分隔类型和超类型(fun foo(a: Int): T),没有空格,冒号分隔实例和类型(var bar: String):

1
2
3
interface Foo<out T : Any> : Bar {
fun foo(a: Int): T
}

Lambdas

Kotlin支持Lambdas表达式,在 Lambdas 表达式中,大括号与表达式间要有空格,箭头与参数和函数体间要有空格。尽可能的把 Lambda 放在括号外面传入

1
list.filter { it > 10 }.map { element -> element * 2 }

在短的且不嵌套的Lambdas中,建议使用it替换参数,有嵌套而已有多个参数的Lambdas中,就不能用it来替换参数,必须明确声明参数。