oing9179 的笔记本儿

Kotlin 入门笔记

Preface

前些天用 Kotlin 写了个 CSGO 辅(wai)助(gua)的网页脚本,发现写 Kotlin 的话就不用跟屎一样的 JS 语法打交道了,于是我花了段时间详细的看了一遍 Kotlin 官方文档。
下文总结一些学习过程中的疑问和解答。

基本语法

1. 函数默认返回 Unit 而不是像 Java 那样的 void

先来看 Unit 的定义:

“Unit” just stands for “something that has only one value”, it’s a traditional name, comes from functional languages. I agree that this name is not very intuitive, but we failed to invent a better name.
Andrey Breslav Mar 27 ‘14 at 6:41

综合 StackOverflow 上 这个问题 的两个答案,返回 Unit 主要有2个原因:

  1. return Unit; 意味着方法一定会返回,而 Nothing 则什么也不会返回,可以看一下 UnitNothing 的源码:
    Unit 是一个单例对象,也就意味着 不管怎样引用它 都会指向同一个 Unit 对象.

    1
    2
    3
    4
    5
    6
    7
    8
    package kotlin

    /**
    * The type with only one value: the Unit object. This type corresponds to the `void` type in Java.
    */
    public object Unit {
    override fun toString() = "kotlin.Unit"
    }

    Nothing 是一个类, 但构造函数是私有的并且没有类似于 getInstance() 之类的函数, 也就意味着 它无法被初始化, 也就意味着无法实例化出一个 Nothing 对象, 也就成为了语义上的 “nothing”.

    1
    2
    3
    4
    5
    6
    7
    package kotlin

    /**
    * Nothing has no instances. You can use Nothing to represent "a value that never exists": for example,
    * if a function has the return type of Nothing, it means that it never returns (always throws an exception).
    */
    public class Nothing private constructor()

    其中一种使用 Nothing 情景就是 某个函数永远不返回任何东西, 退出该函数的方法只有抛出异常。这样, 在语义上, 这个函数就真的是什么也不返回了.

  2. 为了让泛型更好的工作
    先看一个官方文档里的代码:

    1
    2
    3
    4
    5
    6
    class Box<T>(t: T) {
    var value = t
    }

    val theUnit = Box(Unit) // 编译通过, 因为 Unit 是单例 所以不用写成 Unit()
    val theNothing = Box(Nothing()) // 编译失败

    Why Unit has a value (i.e. is not the same as Nothing): because generic code can work smoothly then. If you pass Unit for a generic parameter T, the code written for any T will expect an object, and there must be an object, the sole value of Unit.
    因为 Nothing 无法被实例化, 也就不存在 Nothing 的实例对象, 因此就无法满足 T.

2. Primitive types’ boxing & unboxing

1
2
val unboxedNumber: Int = 0
val boxedNumber: Int? = 0

编译器根据 boxedNumber 是有可能为 null 的, 可能为 null 也就意味着 boxedNumber 肯定指向一个对象, 所以 boxedNumber 是 boxed.

3. Char 在 Kotlin 里与 Java 里的区别

  1. 不能直接和 Int 进行比较, 'a' == 97 会编译错误
  2. 'a'.toInt() == 97 是没问题的并且会返回 true

4. 数组的初始化和使用

1
2
3
4
5
6
7
8
9
10
11
12
// 首先是最简单的数组的初始化方法
// Object[] lArrObjects = new Object[]{new Object(), new Object()};
val lArrObjects: Array<Any> = arrayOf(Any(), Any())

// 针对 primitive types 则使用定制的函数, 这样初始化出来的数组不会被 boxed.
val lArrInts: IntArray = intArrayOf(0, 1, 2)
// 其它的包括但不限于 doubleArrayOf() booleanArrayOf() etc.

// 对于初始化复杂对象的数组, 使用这种语法 会创建出长度为5的字符串数组
val lArrStrings: Array<String> = Array(5, { index ->
"str" + index
})

5. 定义多行字符串

和 Python 一样的语法:

1
2
3
4
val lStrMultiline:String = """
|The quick brown fox jumps over the lazy dog.
|Lorem ipsum dolor. Sit amet integer luctus consectetuer sem. Malesuada id eleifend placerat nam odio. Sociosqu nibh ac quisque dictum.
""".trimMargin()
  1. 使用 """这里写入多行文本""" 来创建多行字符串
  2. 使用 trimMargin() 来去掉 | 以及它前面的空白字符, 如果文本里使用了不是 | 的字符(比如用了 >), 调用函数时加个参数即可 比如 trimMargin(">").

6. 重命名 import 的东西

1
2
import foo.Bar
import bar.Bar as bBar

7. Statement 和 Expression 的区别

Statement: 描述一个事物, 比如 3 = 1 + 2
Expression: 表达(或者说 代表)一个事物,比如 1 + 2 想表达(想代表)的是 3

Expression is a subset of Statement.

用程序员的话来描述 Statement 和 Expression 的区别: Expression 有返回值而 Statement 没有.

8. ifwhen 的用法

Java 里的 if 语法可以照搬过来, 但 Kotlin 提供了一种代替 Ternary Operator 的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val someNum = if (someCondition == 1) {
println("someCondition == 1")
someCondition + 3
} else {
println("someCondition != 1")
someCondition + 2
}

// 等同于
val someNum = 0
if (someCondition == 1) {
println("someCondition == 1")
someNum = someCondition + 3
} else {
println("someCondition != 1")
someNum = someCondition + 2
}

when 是用来代替 Java 里的 switch 的.

1
2
3
4
val someNum = when(someCondition) {
1 -> someCondition + 3
else -> someCondition + 2
}

9. for 循环的变化

Kotlin 没有 for (var i: Int = 0; i < length; i++) 这种语法,只有 for (i in somethingIterable)。替代方法基本上就只能用 while 了。
Kotlin 里的 while 语法和 Java 里是一样的。

10. return break continue 使用 @label 返跳转到特定位置

1
2
3
4
5
6
jumptOut:
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array2.length; j++) {
continue jumpOut;
}
}

对应的 Kotlin:

1
2
3
4
5
jumpOut@ for (i in array.indices) {
for (j in array2.indices) {
continue@jumpOut
}
}

return@label 的语法:

1
2
3
4
5
6
7
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
println("This line will be executed.")
}

更多详细的请参考官方文档: https://kotlinlang.org/docs/reference/returns.html

Classes and Objects

Module 的概念

Module 的概念也很简单,比如:

  • 一个 IntelliJ IDEA module
  • 一个 Maven 项目
  • 一个 Gradle source set
  • a set of files compiled with one invocation of the Ant task.

Visibility Modifiers

Kotlin 没有 Java 的 package-private,但多了个 internalinternal 这个修饰符的作用域就是 “同一 module 内可见”。
举个例子:用 IntelliJ IDEA 新建一个项目,项目里新建2个 Module 一个叫 Lib 一个叫 ClientLib 里有个类是 internal class SomeEntity {},那么 这个类在 Client 里就是不可见的。

PECS?

Producer extends, Consumer super.

What the hack is that? 完全看不懂,那就换个方式思考:

A producer is allowed to produce something more specific, hence extends, a consumer is allowed to accept something more general, hence super.
- Feuermurmel May 7 ‘13 at 13:11

再用 Kotlin 代码来看一下,也许会清晰一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Producer out
abstract class Source<out T> {
abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
// 站在 strs 的角度, strs 在做的事就是提供东西, 也就对应 out
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}


// Consumer in
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
// 站在 x 的角度, x.compareTo() 做的事情是在消耗东西, 也就对应 in
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}

companion object?

先来看一段 Scala 代码:

1
2
3
4
5
6
7
8
9
class X {
import X._

def blah = foo
}

object X {
def foo = 42
}

class X 的同层级下定义一个单例对象 object X。这样,当访问 X.foo 的时候 访问的就不是 class X 里的 foo,而是单例对象 object X 里的 foo,就产生了一种访问 class X 的静态成员的错觉。
Kotlin 里,object 用来定义单例对象,companion object 定义的也是单例对象,但是 companion object 需要定义在类里面,像这样:

1
2
3
4
5
class SomeClass {
companion object {
var pesudoStaticMember: String = "pesudo static"
}
}

Kotlin 的 companion object 拥有如下特性:

  1. 定义在类内部的成员可以访问类的私有成员,而在类外面则不行

    1
    2
    3
    4
    5
    6
    7
    fun tryToAccess() {
    println(SomeClass().privateMember) // compile error
    }

    class SomeClass {
    private var privateMember: Int = 0
    }
  2. companion object 的初始化比它外面的类早

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    fun main(args: Array<String>) {
    println("New instance: SomeClass")
    SomeClass()
    println("Accessing SomeClass.NonCompanion")
    println(SomeClass.NonCompanion)
    }

    class SomeClass {
    init {
    println(javaClass.name + ".init{}")
    }

    companion object {
    init {
    println(javaClass.name + ".init{}")
    }
    }

    object NonCompanion {
    // NonCompanion object get initialized right before accessed.

    init {
    println(javaClass.name + ".init{}")
    }
    }
    }
    /*
    Output:
    New instance: SomeClass
    SomeClass$Companion.init{}
    SomeClass.init{}
    Accessing SomeClass.NonCompanion
    SomeClass$NonCompanion.init{}
    SomeClass$NonCompanion@60e53b93
    */
  3. companion object 内的成员可以直接使用类名来访问,而其它 Singleton 对象则不能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    fun main(args: Array<String>) {
    SomeClass.Companion.directAccess()
    SomeClass.directAccess() // Companion can be omitted.
    SomeClass.NonCompanion.indirectAccess()
    }

    class SomeClass {
    companion object {
    fun directAccess() {
    println(javaClass.name + ".directAccess()")
    }
    }

    object NonCompanion {
    fun indirectAccess() {
    println(javaClass.name + ".indirectAccess()")
    }
    }
    }

至于 为什么设计出 companion object 这种概念,不得而知。

provideDelegate

Providing a Delegate (since 1.1)
这个目前我还想不到实际的应用场景,以后有例子我再在这里补上。

Functions and Lambdas

infix fun

除了 operator overloading 外,Kotlin 还提供了一种近似 自定义运算符 的功能.

1
2
3
4
5
fun main(args: Array<String>) {
println(1 加上 2) // prints "3"
}

infix fun Int.加上(n: Int): Int = this + n

References

  1. What is the purpose of Unit-returning in functions