1-kotlin基本语法及编码规范

本文最后更新于:2 个月前

本文参考kotlin官方开发手册

本文参考kotlin开发手册翻译版本

基本语法

包名在源文件顶部定义 :

package my.demo

import java.util.*

// ...

包名不需要和目录相匹配,源文件可以存在文件系统的任何位置。

函数

两个 Int 参数,返回值是 Int 的函数 :

kotlin中,fun是函数的标志,是固定使用的。这一点和很多解释语言相似。

fun sum(a: Int, b: Int): Int {
    return a + b
}

表达式作为函数体,自动推断返回值类型的函数 :

fun sum(a: Int, b: Int) = a + b

返回无意义值的函数 :

在PHP中也有这种以$标定变量的形式。

fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}

返回类型是 Unit 时,可省略 :

fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

局部变量

常量( assign-once read-only ) :

使用关键字val标志常量

val a: Int = 1
val b = 1   // 自动推断 Int 类型
val c: Int  // 没有提供初始值,必须声明类型
c = 1       // 延迟赋值

变量 :

var可以自动匹配数据类型

var x = 5 // 自动推断 Int 类型
x += 1

注释

和 Java , JacaScript 一样,Kotlin 也支持行注释和块注释 :

// This is an end-of-line comment

/* This is a block comment
   on multiple lines. */

与 Java 不同的是,Kotlin 的块注释可以嵌套。

使用字符串模板

var a = 1
// simple name in template:
val s1 = "a is $a"

a = 2
// arbitrary expression in template:
val s2 = "${s1.replace("is", "was")}, but now is $a"

使用条件表达式

fun maxOf(a: Int, b: Int): Int {
    if (a > b) {
        return a
    } else {
        return b
    }
}

使用 if 作为表达式 :

fun maxOf(a: Int, b: Int) = if (a > b) a else b

使用可空变量以及空值检查

当引用可能为空时必须显式标识这个引用是可空的。

如果 str 不是数字,则返回空 :

kotlin可以使用null匹配,java中使用equals进行匹配

fun parseInt(str: String): Int? {
    // ...
}

返回可空值的函数 :

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // 直接使用 `x * y` 可能会报错,因为它们可能持有空值
    if (x != null && y != null) {
        // x , y 经过空值检查后会自动转化为非空值
        println(x * y)
    }
    else {
        println("either '$arg1' or '$arg2' is not a number")
    }    
}

或者

// ...
  if (x == null) {
      print("Wrong number format in '${args[0]}'")
      return
  }
  if (y == null) {
      print("Wrong number format in '${args[1]}'")
      return
  }

  // x , y经过空值检查后会自动转化为非空值
  print(x * y)

使用类型检测和自动类型转换

is 操作符用来检测某个表达式是否是某个类型的实例。如果一个不可变的局部变量或者属性值经过类型检测为特定类型后,就没有必要再显示转换 :

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // obj 自动转换为 `String` 类型
        return obj.length
    }

    //在类型检查分支外,obj仍然是 `Any` 类型
    return null
}

or

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null

    // obj 自动转换为 `String` 类型
    return obj.length
}

or even

fun getStringLength(obj: Any): Int? {
    // 在`&&`之后的语句中,obj已经自动转化为 `String` 类型
    if (obj is String && obj.length > 0) {
        return obj.length
    }
    return null
}

使用for循环

val items = listOf("apple", "banana", "kiwi")
for (item in items) {
    println(item)
}

or

indices指索引,返回值为Int类型,相当于c语言数组的下标值。

val items = listOf("apple", "banana", "kiwi")
for (index in items.indices) {
    println("item at $index is ${items[index]}")
}

使用while循环

val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
}

使用when表达式

fun describe(obj: Any): String =
when (obj) {
    1          -> "One"
    "Hello"    -> "Greeting"
    is Long    -> "Long"
    !is String -> "Not a string"
    else       -> "Unknown"
}

使用 ranges

使用 in 操作符检测某个数字是否在指定范围内 :

val x = 10
val y = 9
if (x in 1..y+1) {
    println("fits in range")
}

判断数字是否在指定范围之外 :

val list = listOf("a", "b", "c")

if (-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if (list.size !in list.indices) {
    println("list size is out of valid list indices range too")
}

遍历 :

for(x in 1..5){
  print(x)
}

over a progression :

for (x in 1..10 step 2) {
    print(x)
}
for (x in 9 downTo 0 step 3) {
    print(x)
}

使用集合

迭代一个集合 :

for(name in names){
    println(name)
}

使用 in 操作符判断一个对象是否在集合中 :

when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

使用 lambda 表达式来过滤(filter)和变换(map)集合 :

fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }

创建类和实例 :

val rectangle = Rectangle(5.0, 2.0) //no 'new' keyword required
val triangle = Triangle(3.0, 4.0, 5.0)

习惯写法

惯用语法

Kotlin 中的一些常用惯用语法。

创建数据传输对象 (POJOs/POCOs)

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

Customer 类具有以下函数 :

  • 对所有属性的 getters(对于变量提供 setters 方法)
  • equals()
  • hashCode()
  • toString()
  • copy()
  • component1() , component2() , … , for all properties

函数参数默认值

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

过滤 list

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

even shorter :

val positives = list.filter { it > 0 }

字符串插入

println("Name $name")

类型检查

when (x) {
    is Foo -> ...
    is Bar -> ...
    else   -> ...
}

遍历 map/键值对集合

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

k , v 可以任意命名

使用 ranges

for (i in 1..100) { ... }  // 闭合 range: 包括100
for (i in 1 until 100) { ... } // 半开 range: 不包括100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
if (x in 1..10) { ... }

只读 list

val list = listOf("a", "b", "c")

只读 map

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

访问 map

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

懒属性

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

扩展函数

fun String.spaceToCamelCase() { ... }

"Convert this to camelcase".spaceToCamelCase()

创建单例

object Resource{
   val name="Name"
}

if not null 简写

val files = File("Test").listFiles()

println(files?.size)

if not null and else 简写

val files = File("Test").listFiles()

println(files?.size ?: "empty")

为 null 时执行

val data = ...
val email = data["email"] ?: throw IllegalStateException("Email is missing!")

不为null时执行

val data = ...

data?.let {
    ... // not null 时执行
}

不为 null 时返回非 null map

val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValueIfValueIsNull

返回 when 表达式

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

try/catch 表达式

fun test() {
    val result = try {
        count()
    } catch (e: ArithmeticException) {
        throw IllegalStateException(e)
    }

    // Working with result
}

if 表达式

fun foo(param: Int) {
    val result = if (param == 1) {
        "one"
    } else if (param == 2) {
        "two"
    } else {
        "three"
    }
}

Builder-style usage of methods that return Unit

fun arrayOfMinusOnes(size: Int): IntArray {
    return IntArray(size).apply { fill(-1) }
}

单一表达式函数

fun theAnswer() = 42

等价于

fun theAnswer(): Int {
    return 42
}

可以和其他习惯用法有效结合,简化代码,例如when表达式 :

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

使用 with 调用一个对象的多个方法

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}

val myTurtle = Turtle()
with(myTurtle) { //draw a 100 pix square
    penDown()
    for(i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

Java 7’s try with resources

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

需要泛型类型信息的泛型函数的简便形式

//  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 值

val b: Boolean? = ...
if (b == true) {
    ...
} else {
    // b为false  或者 null
}

编码规范

包含了 Kotlin 语言当前代码风格。

命名风格

如有不确定,默认使用 Java 的编码风格,例如 :

  • 使用驼峰命名法(命名避免使用下划线)
  • 类型名称首字母大写
  • 方法和属性名首字母小写
  • 缩进使用4个空格
  • 公共函数需要写文档,以便在Kotlin Doc中查看

冒号

类型和父类型之间的冒号前需要有空格,实例和类型之间的冒号前不需要空格 :

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

Lambdas

在 lambda 表达式中,花括号附近需要使用空格,分离函数体中参数和表达式的箭头两侧需要空格,尽可能把表达式写在圆括号外。

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

在简短非嵌套的 lambda 表达式中,通常建议不要显示声明参数,而使用 it 代替。反之,在嵌套的Lambdas表达式中,总是应该显式声明属性。

类头格式化

仅有几个参数的类可以缩写为一行代码 :

class Person(id: Int, name: String)

头很长的类需要进行格式化。每一个主构造器参数单独一行并缩进,右括号也需单独一行。如果我们使用继承,父类构造器的调用或者实现的接口列表应和右括号处于同一行 :

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name) {
    // ...
}

Unit

如果一个函数返回 Unit ,应该被省略 :

fun foo() { // ": Unit" is omitted here

}

函数 vs 属性

某些情况下无参函数和只读属性是可以互换的。尽管语义上相似,但是在具体取舍上还是有格式上的规范的。

底层算法更偏向于属性 :

  • 不会抛出异常
  • O(1) 复杂度
  • 计算廉价 (或者第一次运行时进行缓存)
  • 多次调用返回相同的结果

1-kotlin基本语法及编码规范
https://tanzicai.github.io/2021/09/26/1-kotlin基本语法及编码规范/
作者
Zicai Tan
发布于
2021年9月26日
更新于
2022年12月20日
许可协议