Hotaru's Notebook

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 对象.
    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”.
    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. 为了让泛型更好的工作 先看一个官方文档里的代码:
    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

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. 数组的初始化和使用

// 首先是最简单的数组的初始化方法
// 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 一样的语法:

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 的东西

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 的语法:

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 的.

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 返跳转到特定位置

jumptOut:
for (int i = 0; i < array.length; i++) {
    for (int j = 0; j < array2.length; j++) {
        continue jumpOut;
    }
}

对应的 Kotlin:

jumpOut@ for (i in array.indices) {
    for (j in array2.indices) {
        continue@jumpOut
    }
}

return@label 的语法:

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 的概念也很简单,比如:

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 代码来看一下,也许会清晰一些。

// 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 代码:

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 需要定义在类里面,像这样:

class SomeClass {
    companion object {
        var pesudoStaticMember: String = "pesudo static"
    }
}

Kotlin 的 companion object 拥有如下特性:

  1. 定义在类内部的成员可以访问类的私有成员,而在类外面则不行
    fun tryToAccess() {
        println(SomeClass().privateMember) // compile error
    }
    
    class SomeClass {
        private var privateMember: Int = 0
    }
    
  2. companion object 的初始化比它外面的类早
    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 对象则不能
    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 还提供了一种近似 自定义运算符 的功能.

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

#Kotlin #JVM Language