一、Kotlin 简介

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。
Kotlin 可以编译成Java字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。
在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。

为什么选择 Kotlin?

  • 简洁: 大大减少样板代码的数量。
  • 安全: 避免空指针异常等整个类的错误。
  • 互操作性: 充分利用 JVM、Android 和浏览器的现有库。
  • 工具友好: 可用任何 Java IDE 或者使用命令行构建。
  • 函数式: Kotlin 是基于面向对象的语言,它使用了现下非常流行的函数式编程的概念,如使用lambda表达式很方便地解决问题。其中一个很棒的特性就是Collections的处理方式
  • 可以扩展函数:我们可以扩展类的更多的特性,甚至没有权限去访问这个类

二、基本语法

官网介绍

1、包名的定义

包名需要放在文件的开头部分。但是不需要匹配目录和包,源文件可以放在任意目录中。

package my.demo

import java.util.*

// ...

2、函数的定义

关键字(fun)+ 方法名,参数的声明(参数名 : 参数类型),返回值类型的声明跟在参数后面 (:返回值类型):

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

方便起见,我们也可以直接在方法后面直接加 = 表达式, 返回值的类型会自动推算出来:

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

当函数返回无意义值时,可以使用Unit类型(相当于Java中的void),也可以省略不写:

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

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

3、局部变量(成员变量)的定义

使用val关键字声明一个常量(只读,不可修改),使用var关键字声明一个变量:

val a: Int = 1  // 显示指定常量的类型

val b = 2   // 自动推断类型为‘Int’

val c: Int  //  声明一个不初始化的常量,必须显示指定类型

c = 3     // 赋值后,值不可再更改 
// 变量的声明方式同常量,唯一不同的是赋值后,值可改变

var x = 5 // 自动推断类型为‘Int’

x += 1

4、注释

kotlin的注释同Java/JavaScript:

// This is an end-of-line comment

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

5、字符串模板的使用

模板表达式以美元符( ( <script type="math/tex" id="MathJax-Element-1">)开头,由一个简单的名字构成(</script>a) 或者 用花括号括起来的任意表达式(${s1.replace(“is”, “was”)})

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"

// a was 1, but now is 2

原生字符串和转义字符串内部都支持模板。如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:

val price = """
    ${'$'}9.99
    """
// 求值结果为$9.99

6、条件表达式的使用

常规的条件表达式:

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

也可以简写成条件表达式

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

7、空值的使用以及空指针的检查

这可以说是Kotlin的语言特性之一,很好地避免了在Android开发时的空指针异常。
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加 !! 像Java一样抛出空异常,另一种字段后加 ? 可不做处理返回值为 null 或配合 ?: 做空判断处理.

//类型后面加?表示可为空
var age: String? = "23" 

//抛出空指针异常
val ages = age!!.toInt()

//不做处理返回 null
val ages1 = age?.toInt()

//age为空返回-1
val ages2 = age?.toInt() ?: -1

当一个变量可能为null时,引用必须被明确地标记为空。

fun parseInt(str: String): Int? {
    // ...
}
fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // Using `x * y` yields error because they may hold nulls.
    if (x != null && y != null) {
        // x and y are automatically cast to non-nullable after null check
        println(x * y)
    }
    else {
        println("either '$arg1' or '$arg2' is not a number")
    }    
}

or

// ...
if (x == null) {
    println("Wrong number format in arg1: '$arg1'")
    return
}
if (y == null) {
    println("Wrong number format in arg2: '$arg2'")
    return
}

// x and y are automatically cast to non-nullable after null check
println(x * y)

8、使用类型检查和自动类型转换

我们可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
Kotlin中的Any,相当于Java中的Object。所有的类型都继承于它。

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // 做过类型判断以后,obj会被系统自动转换为String类型
        return obj.length
    }

  // 在这里还有一种方法,与Java中instanceof不同,使用!is
  // if (obj !is String){
  //   // XXX
  // }

    // 这里的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
}

9、for循环

for 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:

for (item in collection) print(item)

循环体可以是一个代码块:

for (item: Int in ints) {
    // ……
}

如上所述,for 可以循环遍历任何提供了迭代器的对象。
如果你想要通过索引遍历一个数组或者一个 list,你可以这么做

for (i in array.indices) {
    print(array[i])
}

注意这种”在区间上遍历”会编译成优化的实现而不会创建额外对象。
或者你可以用库函数 withIndex:

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")

实例
对集合进行迭代:

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

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

输出结果:

apple
banana
kiwi
item at 0 is apple
item at 1 is banana
item at 2 is kiwi

10、while 循环与 do…while 循环

while是最基本的循环,它的结构为:

while( 布尔表达式 ) {
  //循环内容
}

do…while 循环 对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。
do…while 循环和 while 循环相似,不同的是,do…while 循环至少会执行一次。

do {
       //代码语句
}while(布尔表达式);

实例

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

var index = 0
do{
   println("item at $index is ${items[index]}")
   index ++
}while(index < items.size  )

输出结果:

item at 0 is apple
item at 1 is banana
item at 2 is kiwi
----do...while 使用-----
item at 0 is apple
item at 1 is banana
item at 2 is kiwi

11、when表达式的使用

when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。
when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
when 类似其他语言的 switch 操作符。其最简单的形式如下:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
        print("x 不是 1 ,也不是 2")
    }
}

在 when 中,else 同 switch 的 default。如果其他分支都不满足条件将会求值 else 分支。
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需 任何额外的检测。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

实例

fun main(args: Array<String>) {
    var x = 0
    when (x) {
        0, 1 -> println("x == 0 or x == 1")
        else -> println("otherwise")
    }

    when (x) {
        1 -> println("x == 1")
        2 -> println("x == 2")
        else -> { // 注意这个块
            println("x 不是 1 ,也不是 2")
        }
    }

    when (x) {
        in 0..10 -> println("x 在该区间范围内")
        else -> println("x 不在该区间范围内")
    }
}

输出结果:

x == 0 or x == 1
x 不是 1 ,也不是 2
x 在该区间范围内

when 中使用 in 运算符来判断集合内是否包含某实例:

fun main(args: Array<String>) {
    val items = setOf("apple", "banana", "kiwi")
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")
    }
}

输出结果:

apple is fine too

12、ranges的使用

区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。
区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:

step 关键字可以设置间隔。downTo ,可以从大到小循环

for (i in 1..4) print(i) // 输出“1234”

for (i in 4..1) print(i) // 什么都不输出

if (i in 1..10) { // 等同于 1 <= i && i <= 10
    println(i)
}

// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”

for (i in 4 downTo 1 step 2) print(i) // 输出“42”


// 使用 until 函数排除结束元素
for (i in 1 until 10) {   // i in [1, 10) 排除了 10
     println(i)
}

实例测试

fun main(args: Array<String>) {
    print("循环输出:")
    for (i in 1..4) print(i) // 输出“1234”
    println("\n----------------")
    print("设置步长:")
    for (i in 1..4 step 2) print(i) // 输出“13”
    println("\n----------------")
    print("使用 downTo:")
    for (i in 4 downTo 1 step 2) print(i) // 输出“42”
    println("\n----------------")
    print("使用 until:")
    // 使用 until 函数排除结束元素
    for (i in 1 until 4) {   // i in [1, 4) 排除了 4
        print(i)
    }
    println("\n----------------")
}

输出结果:

循环输出:1234
----------------
设置步长:13
----------------
使用 downTo:42
----------------
使用 until:123
----------------

13、集合的使用:

遍历一个集合:

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

使用in操作符检查一个集合是否包含一个对象:

 val items = listOf("apple", "banana", "kiwi")
    if(apple in items) {
        println("Yes")
    }

使用Lambda表达式过滤和映射集合:

items 
         .filter { it.startsWith("a") } 
         .sortedBy { it } 
         .map { it.toUpperCase() } 
         .forEach { print(it) }

14、创建类的实例:

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

三、习惯用语

以下只是一部分习惯用语,有些我现在也不是很明白,等学了后面的知识再一点点弄明白吧。

1、创建 DTOs(POJOs/POCOs)

DTO: 数据传输对象.。它的解释是“Data Transfer Object”
POJO: 简单java对象,实际上就是普通的JavaBean。它的解释是“Plain Old Java Object”
POCO:同POJO的涵义是一致的,不同的仅仅是使用的语言不一样。因此,它的解释就是“Plain Old C# Object”

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

提供了一个带有以下功能的Customer类:

  • 所有属性的getter(和var的setter)
  • equals()
  • hashCode()
  • toString()
  • copy()
  • component1(), component2(), …, for all properties

2、函数参数的默认值

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

3、过滤list列表

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

or 相对更简单的写法:

val positives = list.filter { it > 0 }

注意:it 是个啥?后面再补充解释。这里大概知道,就是代表集合例的一个对象。

4、字符串插值

我的理解应该和字符串模板的使用是一个意思。

println("Name $name")

5、实例检查

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

6、遍历成对的映射/列表

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

k, v can be called anything.

7、使用区间

for (i in 1..100) { ... }  // closed range: includes 100
for (i in 1 until 100) { ... } // half-open range: does not include 100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
if (x in 1..10) { ... }

8、只读列表

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

9、只读映射

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

10、访问映射

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

11、Lazy 属性

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

12、扩展函数

fun String.spaceToCamelCase() { ... }

"Convert this to camelcase".spaceToCamelCase()

13、创建单例

object Resource {
    val name = "Name"
}

14、if非空简写

val files = File("Test").listFiles()
// 如果为空则不执行
println(files?.size)

15、if非空和else 简写

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

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

16、if空执行语句

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

17、if非空执行

val value = ...

value?.let {
    ... // execute this block if not null
}

18、if非空,映射空值

val value = ...

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

19、when语句返回值

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

20、try/catch表达式

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

    // Working with result
}

21、if表达式

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

22、 返回Unit类型的方法生成器风格用法

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

23、 单一表达式函数

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")
}

24、在对象实例中(‘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()
}

25、Java7的资源

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

26、需要泛型类型信息的泛型函数的方便形式

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

27、消费可空的Boolean

val b: Boolean? = ...
if (b == true) {
    ...
} else {
    // `b` is false or null
}

四、编码规范

以下这些是Kotlin目前的编码风格。

1、命名规范

如有疑问,默认为Java编码规范,例如:

  • 命名用驼峰式命名(并避免在名称中用下划线)
  • 以大写字母开始
  • 方法和属性以小写字母开始
  • 用4空格缩进
  • public函数应有文档,使其能在Kotlin文档中显示

2、冒号

在用冒号分隔类型和子类型时,冒号前需要添加一个空格;而在分隔实例与类型时,冒号前就不需要空格:

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

3、Lambdas

在Lambda表达式中,空格应该用在花括号两边,以及将参数与函数体分隔的箭头两侧。如果可能,Lambda表达式应该在括号外传递。

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

4、类的头文件格式

带有几个参数的类可以在一行书写

class Person(id: Int, name: String)

带有多个参数的类应该格式化为 每个主构造参数分隔在单独的一行中。并且,右括号应该在新的一行。如果使用继承,父类构造函数的调用或者一实现的接口列表应该同右括号在同一行中书写。

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

多接口中,父类的构造函数调用写在最前面,各个接口写在新的一行中。

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

构造函数参数可以使用常规缩进或延续缩进(常规缩进的两倍)。

5、Unit

如果一个函数返回Unit类型,那么这个返回的类型可以被忽略。

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

}
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐