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个原因:
return Unit;
意味着方法一定会返回,而Nothing
则什么也不会返回,可以看一下Unit
和Nothing
的源码: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
情景就是 某个函数永远不返回任何东西, 退出该函数的方法只有抛出异常。这样, 在语义上, 这个函数就真的是什么也不返回了.- 为了让泛型更好的工作
先看一个官方文档里的代码:
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 里的区别
- 不能直接和 Int 进行比较,
'a' == 97
会编译错误 '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()
- 使用
"""这里写入多行文本"""
来创建多行字符串 - 使用
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. if
和 when
的用法
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 的概念也很简单,比如:
- 一个 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
,但多了个 internal
,internal
这个修饰符的作用域就是 “同一 module 内可见”。
举个例子:用 IntelliJ IDEA 新建一个项目,项目里新建2个 Module 一个叫 Lib
一个叫 Client
。Lib
里有个类是 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
拥有如下特性:
- 定义在类内部的成员可以访问类的私有成员,而在类外面则不行
fun tryToAccess() { println(SomeClass().privateMember) // compile error } class SomeClass { private var privateMember: Int = 0 }
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 */
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