延迟属性

  1. val p: String by lazy {
  2. // 计算该字符串
  3. }

扩展函数

  1. fun String.spaceToCamelCase() { …… }
  2. "Convert this to camelcase".spaceToCamelCase()

返回类型为 Unit 的方法的 Builder 风格用法

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

单表达式函数

  1. fun theAnswer() = 42
  2. 等价于
  3. fun theAnswer(): Int {
  4. return 42
  5. }

单表达式函数与其它惯用法一起使用能简化代码,例如和 when 表达式一起使用:

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

对一个对象实例调用多个方法 (with

  1. class Turtle {
  2. fun penDown()
  3. fun penUp()
  4. fun turn(degrees: Double)
  5. fun forward(pixels: Double)
  6. }
  7. val myTurtle = Turtle()
  8. with(myTurtle) { // 画一个 100 像素的正方形
  9. penDown()
  10. for (i in 1..4) {
  11. forward(100.0)
  12. turn(90.0)
  13. }
  14. penUp()
  15. }

配置对象的属性(apply

  1. val myRectangle = Rectangle().apply {
  2. length = 4
  3. breadth = 5
  4. color = 0xFAFAFA
  5. }

这对于配置未出现在对象构造函数中的属性非常有用。
**

TODO():将代码标记为不完整

Kotlin 的标准库有一个 TODO() 函数,该函数总是抛出一个 NotImplementedError。 其返回类型为 Nothing,因此无论预期类型是什么都可以使用它。 还有一个接受原因参数的重载:

  1. fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")

IntelliJ IDEA 的 kotlin 插件理解 TODO() 的语言,并且会自动在 TODO 工具窗口中添加代码指示。

Kotlin 中的数字没有隐式拓宽转换

与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 FloatInt 或者其他数字值调用。

  1. fun main() {
  2. fun printDouble(d: Double) { print(d) }
  3. val i = 1
  4. val d = 1.1
  5. val f = 1.1f
  6. printDouble(d)
  7. // printDouble(i) // 错误:类型不匹配
  8. // printDouble(f) // 错误:类型不匹配
  9. }

如需将数值转换为不同的类型,请使用显示转换

字符

字符用 Char 类型表示。它们不能直接当作数字

  1. fun check(c: Char) {
  2. if (c == 1) { // 错误:类型不兼容
  3. // ……
  4. }
  5. }

字符字面值用单引号括起来: '1'。 特殊字符可以用反斜杠转义。 支持这几个转义序列:\t\b\n\r\'\"\\\$。 编码其他字符要用 Unicode 转义序列语法:'\uFF00'
我们可以显式把字符转换为 Int 数字:

  1. fun decimalDigitValue(c: Char): Int {
  2. if (c !in '0'..'9')
  3. throw IllegalArgumentException("Out of range")
  4. return c.toInt() - '0'.toInt() // 显式转换为数字
  5. }

当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。

数组

数组在 Kotlin 中使用 Array 类来表示,它定义了 getset 函数(按照运算符重载约定这会转变为 [])以及 size 属性,以及一些其他有用的成员函数:

  1. class Array<T> private constructor() {
  2. val size: Int
  3. operator fun get(index: Int): T
  4. operator fun set(index: Int, value: T): Unit
  5. operator fun iterator(): Iterator<T>
  6. // ……
  7. }

我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。
另一个选项是用接受数组大小以及一个函数参数的 Array 构造函数,用作参数的函数能够返回给定索引的每个元素初始值:

  1. // 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]
  2. val asc = Array(5) { i -> (i * i).toString() }
  3. asc.forEach { println(it) }

如上所述,[] 运算符代表调用成员函数 get()set()
Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array<String> 赋值给 Array<Any>,以防止可能的运行时失败(但是你可以使用 Array<out Any>, 参见类型投影)。

原生类型数组

Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArrayShortArrayIntArray 等等。这些类与 Array 并没有继承关系,但是它们有同样的方法属性集。它们也都有相应的工厂方法:

  1. val x: IntArray = intArrayOf(1, 2, 3)
  2. x[0] = x[1] + x[2]
  3. // 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组
  4. val arr = IntArray(5)
  5. // 例如:用常量初始化数组中的值
  6. // 大小为 5、值为 [42, 42, 42, 42, 42] 的整型数组
  7. val arr = IntArray(5) { 42 }
  8. // 例如:使用 lambda 表达式初始化数组中的值
  9. // 大小为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)
  10. var arr = IntArray(5) { it * 1 }

Nothing 类型

在 Kotlin 中 throw 是表达式,所以你可以使用它(比如)作为 Elvis 表达式的一部分:

  1. val s = person.name ?: throw IllegalArgumentException("Name required")

throw 表达式的类型是特殊类型 Nothing。 该类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing 来标记一个永远不会返回的函数:

  1. fun fail(message: String): Nothing {
  2. throw IllegalArgumentException(message)
  3. }

当你调用该函数时,编译器会知道在该调用后就不再继续执行了:

  1. val s = person.name ?: fail("Name required")
  2. println(s) // 在此已知“s”已初始化

可能会遇到这个类型的另一种情况是类型推断。这个类型的可空变体 Nothing? 有一个可能的值是 null。如果用 null 来初始化一个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing? 类型:

  1. val x = null // “x”具有类型 `Nothing?`
  2. val l = listOf(null) // “l”具有类型 `List<Nothing?>
  1. val s = person.name ?: return

构造函数

  1. kotlin中class可以有多个构造函数,和java是一样的
  2. Kotlin 并没有 new 关键字
  3. 如果类声明了主构造函数,次级构造函数必须委托给主构造函数

Any

在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

  1. class Example // 从 Any 隐式继承

Any 有三个方法:equals()hashCode()toString()。因此,为所有 Kotlin 类都定义了这些方法。

By default, Kotlin classes are final: they can’t be inherited. To make a class inheritable, mark it with the openkeyword.

  1. open class Base //Class is open for inheritance

如需声明一个显式的超类型,请在类头中把超类型放到冒号之后:

  1. open class Base(p: Int)
  2. class Derived(p: Int) : Base(p)

如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

  1. class MyView : View {
  2. constructor(ctx: Context) : super(ctx)
  3. constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
  4. }

覆盖属性

属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖。

  1. open class Shape {
  2. open val vertexCount: Int = 0
  3. }
  4. class Rectangle : Shape() {
  5. override val vertexCount = 4
  6. }

你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 这是允许的,因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。

  1. interface Shape {
  2. val vertexCount: Int
  3. }
  4. class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
  5. class Polygon : Shape {
  6. override var vertexCount: Int = 0 // 以后可以设置为任何数
  7. }

内部类

在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer

  1. class FilledRectangle: Rectangle() {
  2. fun draw() { /* …… */ }
  3. val borderColor: String get() = "black"
  4. inner class Filler {
  5. fun fill() { /* …… */ }
  6. fun drawAndFill() {
  7. super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
  8. fill()
  9. println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
  10. }
  11. }
  12. }

伴生对象

如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。
更具体地讲,如果在你的类内声明了一个伴生对象, 你就可以访问其成员,只是以类名作为限定符。

编译期常量

如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量。 这种属性需要满足以下要求:

这些属性可以用在注解中:

  1. const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
  2. @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }