延迟属性
val p: String by lazy {// 计算该字符串}
扩展函数
fun String.spaceToCamelCase() { …… }"Convert this to camelcase".spaceToCamelCase()
返回类型为 Unit 的方法的 Builder 风格用法
fun arrayOfMinusOnes(size: Int): IntArray {return IntArray(size).apply { fill(-1) }}
单表达式函数
fun theAnswer() = 42等价于fun theAnswer(): Int {return 42}
单表达式函数与其它惯用法一起使用能简化代码,例如和 when 表达式一起使用:
fun transform(color: String): Int = when (color) {"Red" -> 0"Green" -> 1"Blue" -> 2else -> throw IllegalArgumentException("Invalid color param value")}
对一个对象实例调用多个方法 (with)
class Turtle {fun penDown()fun penUp()fun turn(degrees: Double)fun forward(pixels: Double)}val myTurtle = Turtle()with(myTurtle) { // 画一个 100 像素的正方形penDown()for (i in 1..4) {forward(100.0)turn(90.0)}penUp()}
配置对象的属性(apply)
val myRectangle = Rectangle().apply {length = 4breadth = 5color = 0xFAFAFA}
TODO():将代码标记为不完整
Kotlin 的标准库有一个 TODO() 函数,该函数总是抛出一个 NotImplementedError。 其返回类型为 Nothing,因此无论预期类型是什么都可以使用它。 还有一个接受原因参数的重载:
fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")
IntelliJ IDEA 的 kotlin 插件理解 TODO() 的语言,并且会自动在 TODO 工具窗口中添加代码指示。
Kotlin 中的数字没有隐式拓宽转换
与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用。
fun main() {fun printDouble(d: Double) { print(d) }val i = 1val d = 1.1val f = 1.1fprintDouble(d)// printDouble(i) // 错误:类型不匹配// printDouble(f) // 错误:类型不匹配}
如需将数值转换为不同的类型,请使用显示转换。
字符
字符用 Char 类型表示。它们不能直接当作数字
fun check(c: Char) {if (c == 1) { // 错误:类型不兼容// ……}}
字符字面值用单引号括起来: '1'。 特殊字符可以用反斜杠转义。 支持这几个转义序列:\t、 \b、\n、\r、\'、\"、\\ 与 \$。 编码其他字符要用 Unicode 转义序列语法:'\uFF00'。
我们可以显式把字符转换为 Int 数字:
fun decimalDigitValue(c: Char): Int {if (c !in '0'..'9')throw IllegalArgumentException("Out of range")return c.toInt() - '0'.toInt() // 显式转换为数字}
当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。
数组
数组在 Kotlin 中使用 Array 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及 size 属性,以及一些其他有用的成员函数:
class Array<T> private constructor() {val size: Intoperator fun get(index: Int): Toperator fun set(index: Int, value: T): Unitoperator fun iterator(): Iterator<T>// ……}
我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。
另一个选项是用接受数组大小以及一个函数参数的 Array 构造函数,用作参数的函数能够返回给定索引的每个元素初始值:
// 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]val asc = Array(5) { i -> (i * i).toString() }asc.forEach { println(it) }
如上所述,[] 运算符代表调用成员函数 get() 与 set()。
Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array<String> 赋值给 Array<Any>,以防止可能的运行时失败(但是你可以使用 Array<out Any>, 参见类型投影)。
原生类型数组
Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray、 ShortArray、IntArray 等等。这些类与 Array 并没有继承关系,但是它们有同样的方法属性集。它们也都有相应的工厂方法:
val x: IntArray = intArrayOf(1, 2, 3)x[0] = x[1] + x[2]// 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组val arr = IntArray(5)// 例如:用常量初始化数组中的值// 大小为 5、值为 [42, 42, 42, 42, 42] 的整型数组val arr = IntArray(5) { 42 }// 例如:使用 lambda 表达式初始化数组中的值// 大小为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)var arr = IntArray(5) { it * 1 }
Nothing 类型
在 Kotlin 中 throw 是表达式,所以你可以使用它(比如)作为 Elvis 表达式的一部分:
val s = person.name ?: throw IllegalArgumentException("Name required")
throw 表达式的类型是特殊类型 Nothing。 该类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing 来标记一个永远不会返回的函数:
fun fail(message: String): Nothing {throw IllegalArgumentException(message)}
当你调用该函数时,编译器会知道在该调用后就不再继续执行了:
val s = person.name ?: fail("Name required")println(s) // 在此已知“s”已初始化
可能会遇到这个类型的另一种情况是类型推断。这个类型的可空变体 Nothing? 有一个可能的值是 null。如果用 null 来初始化一个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing? 类型:
val x = null // “x”具有类型 `Nothing?`val l = listOf(null) // “l”具有类型 `List<Nothing?>
val s = person.name ?: return
构造函数
- kotlin中class可以有多个构造函数,和java是一样的
- Kotlin 并没有 new 关键字
- 如果类声明了主构造函数,次级构造函数必须委托给主构造函数
Any
在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:
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.
open class Base //Class is open for inheritance
如需声明一个显式的超类型,请在类头中把超类型放到冒号之后:
open class Base(p: Int)class Derived(p: Int) : Base(p)
如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
class MyView : View {constructor(ctx: Context) : super(ctx)constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)}
覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖。
open class Shape {open val vertexCount: Int = 0}class Rectangle : Shape() {override val vertexCount = 4}
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 这是允许的,因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。
interface Shape {val vertexCount: Int}class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点class Polygon : Shape {override var vertexCount: Int = 0 // 以后可以设置为任何数}
内部类
在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer:
class FilledRectangle: Rectangle() {fun draw() { /* …… */ }val borderColor: String get() = "black"inner class Filler {fun fill() { /* …… */ }fun drawAndFill() {super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现fill()println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()}}}
伴生对象
如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。
更具体地讲,如果在你的类内声明了一个伴生对象, 你就可以访问其成员,只是以类名作为限定符。
编译期常量
如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量。 这种属性需要满足以下要求:
- 位于顶层或者是 object 声明 或 companion object 的一个成员
- 以
String或原生类型值初始化 - 没有自定义 getter
这些属性可以用在注解中:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }
