findViewById()
调用,并将其替换为合成的编译器生成的属性。Android API
的 Kotlin
友好的包装器的库 ,以及一个可以用 Kotlin
代码替换布局 .xml
文件的 DSL
。val
变量值不可变(只读)
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
var
可变变量
var x = 5 // 自动推断出 `Int` 类型
x += 1
is
运算符检测一个表达式是否某类型的一个实例。fun getStringLength(obj: Any): Int? {
// obj 在 && 右边自动转换成 String 类型
if (obj is String && obj.length > 0) {
return obj.length
}
return null
}
使用
in
运算符来检测某个数字是否在指定区间内
如果拿不准的时候,默认使用Java
的编码规范,比如:
Kotlin Doc
中如果函数返回 Unit
类型,该返回类型应该省略:
fun foo() { // 省略了 ": Unit"
}
当某个变量的值可以为 null
的时候,必须在声明处的类型后添加 ?
来标识该引用可为空。
如果 str
的内容不是数字返回 null
:
fun parseInt(str: String): Int? {
// ……
}
例如:
val x = (1 shl 2) and 0x000FF000
完整的位运算列表(只用于 Int
和 Long
):
shl(bits)
– 有符号左移 (Java 的 <<
)shr(bits)
– 有符号右移 (Java 的 >>
)ushr(bits)
– 无符号右移 (Java 的 >>>
) and(bits)
– 位与or(bits)
– 位或xor(bits)
– 位异或inv()
– 位非字符用 Char
类型表示。它们不能直接当作数字
Kotlin 有两种类型的字符串字面值: 转义字符串 可以有转义字符,以及 原生字符串 可以包含换行和任意文本。转义字符串很像 Java
字符串:
val s = "Hello, world!\n"
转义采用传统的反斜杠方式。
原生字符串 使用三个引号("""
)分界符括起来,内部没有转义并且可以包含换行和任何其他字符:
val text = """
for (c in "foo")
print(c)
"""
字符串可以包含 模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:
val i = 10
val s = "i = $i" // 求值结果为 "i = 10"
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $
字符(它不支持反斜杠转义),你可以用下列语法:
val price = """
${'$'}9.99
"""
在Kotlin
中,if
是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if
就能胜任这个角色。
// 传统用法
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式
val max = if (a > b) a else b
if的分支可以是代码块,最后的表达式作为该块的值:
val a=2
val b=5
val max=if(a>b){
print("choose a")
a
}else{
print("Choose b")
b
}
如果你使用 if
作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else
分支。
when
取代了类 C 语言的 switch
操作符。其最简单的形式如下:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
when
将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。 when
既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if
一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)
如果其他分支都不满足条件将会求值 else
分支。 如果when
作为一个表达式使用,则必须有 else
分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
我们可以用任意表达式(而不只是常量)作为分支条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
我们也可以检测一个值在(in
)或者不在(!in
)一个区间或者集合中:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
另一种可能性是检测一个值是(is
)或者不是(!is
)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when
也可以用来取代 if-else if
链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
for
循环可以对任何提供迭代器(iterator
)的对象进行遍历,这相当于像 C#
这样的语言中的 foreach
循环。语法如下:
for (item in collection) print(item)
循环体可以是一个代码块。
for (item: Int in ints) {
// ……
}
while
和 do..while
照常使用
在循环中 Kotlin
支持传统的 break
和 continue
操作符。
表达式,是指由常量、变量或是操作数与运算符所组合而成的语句。
在 Kotlin
中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、fooBar@
都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。
loop@ for (i in 1..100) {
// ……
}
现在,我们可以用标签限制 break
或者continue
:
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
标签限制的 break
跳转到刚好位于该标签指定的循环后面的执行点。 continue
继续标签指定的循环的下一次迭代。
在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。
class Person constructor(firstName: String) {
}
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor
关键字。
class Person(firstName: String) {
}
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks
)中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
!注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
事实上,声明属性以及从主构造函数初始化属性,Kotlin
有简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ……
}
与普通属性一样,主构造函数中声明的属性可以是可变的(var
)或只读的(`val)。
如果构造函数有注解或可见性修饰符,这个constructor
关键字是必需的,并且这些修饰符在它前面:
class Customer public @Inject constructor(name: String) { …… }
类也可以声明前缀有 constructor
的次构造函数:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this
关键字即可:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public
。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:
class DontCreateMe private constructor () {
}
注意:在
JVM
上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得Kotlin
更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。
class Customer(val customerName: String = "")
在 Kotlin
中所有类都有一个共同的超类 Any
,这对于没有超类型声明的类是默认超类:
class Example // 从 Any 隐式继承
Any
不是 java.lang.Object
;尤其是,它除了 equals()
、hashCode()
和toString()
外没有任何成员。
要声明一个显式的超类型,我们把类型放到类头的冒号之后:
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)
}
类上的 open
标注与 Java
中 final
相反,它允许其他类从这个类继承。默认情况下,在 Kotlin
中所有的类都是 final
, 对应于 Effective Java书中的第 17 条:要么为继承而设计,并提供文档说明,要么就禁止继承。
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override
开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter
方法的属性覆盖。
open class Foo {
open val x: Int get() { …… }
}
class Bar1 : Foo() {
override val x: Int = ……
}
派生类中的代码可以使用 super
关键字调用其超类的函数与属性访问器的实现:
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
}
在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super
关键字来实现:super@Outer
:
class Bar : Foo() {
override fun f() { /* …… */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // 调用 Foo 实现的 f()
println(super@Bar.x) // 使用 Foo 实现的 x 的 getter
}
}
}
在 Kotlin
中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super
,如 super
:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // 接口成员默认就是“open”的
fun b() { print("b") }
}
class C() : A(), B {
// 编译器要求覆盖 f():
override fun f() {
super.f() // 调用 A.f()
super.f() // 调用 B.f()
}
}
同时继承 A 和 B 没问题,并且 a() 和 b() 也没问题因为 C 只继承了每个函数的一个实现。 但是 f() 由 C 继承了两个实现,所以我们必须在 C 中覆盖 f() 并且提供我们自己的实现来消除歧义。
Kotlin
的接口与 Java8
类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
使用关键字 interface
来定义接口
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
类委托:类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下实例中派生类 Derived
继承了接口 Base
所有方法,并且委托一个传入的 Base
类的对象来执行这些方法。
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
在 Derived
声明中,by
子句表示,将 b
保存在 Derived
的对象实例内部,而且编译器将会生成继承自 Base
接口的所有方法, 并将调用转发给 b
。
注意,覆盖会以你所期望的方式工作:编译器会使用你的
override
实现取代委托对象中的实现。如果我们为Derived
添加override fun print() { print("abc") }
,该程序会输出“abc”而不是“10”。
高阶函数是将函数用作参数或返回值的函数。
val doubled = ints.map { value -> value * 2 }
注意,如果 lambda 是该调用的唯一参数,则调用中的圆括号可以完全省略。
另一个有用的约定是,如果函数字面值只有一个参数, 那么它的声明可以省略(连同 ->
),其名称是 it
。
ints.map { it * 2 }
一个 lambda
表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达式传递。
例:
max(strings, { a, b -> a.length < b.length })
函数max
是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于:
fun compare(a: String, b: String): Boolean = a.length < b.length
lambda 表达式总是被大括号括着;
如果 lambda
表达式的参数未使用,那么可以用下划线取代其名称:
map.forEach { _, value -> println("$value!") }
Kotlin 区分可变集合和不可变集合
不可变:
listOf()
setOf()
可变
mutableSetOf()
mutableListOf()
区间表达式由具有操作符形式 ..
的 rangeTo
函数辅以in
和 !in
形成。
想要倒序迭代数字可以使用标准库中定义的 downTo()
函数:
for (i in 4 downTo 1) print(i) // 输出“4321”
is
与 !is
操作符
我们可以在运行时通过使用 is
操作符或其否定形式 !is
来检查对象是否符合给定类型:
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // 与 !(obj is String) 相同
print("Not a String")
}
else {
print(obj.length)
}
null
显式检查 b 是否为 null
安全调用操作符,写作 ?.
:
b?.length
如果 b 非空,就返回 b.length
,否则返回 null
,这个表达式的类型是 Int?
。
安全调用在链式调用中很有用。如:
bob?.department?.head?.name
如果任意一个属性(环节)为空,这个链式调用就会返回 null
。
如果要只对非空值执行某个操作,安全调用操作符可以与 let
一起使用:
val listWithNulls: List?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 A 并忽略 null
}
val l = b?.length ?: -1
如果 ?:
左侧表达式非空,elvis
操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。
Kotlin
中所有异常类都是 Throwable
类的子孙类。 每个异常都有消息、堆栈回溯信息和可选的原因
throw
表达式的类型是特殊类型Nothing
.该类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing
来标记一个永远不会返回的函数:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
let
let
可以被任何对象调用。它接收一个函数作为参数,作为参数的函数返回的结果作为整个函数的返回值。它在处理可null
对象的时候是非常有用的,下面是它的定义:
inline fun T.let(f: (T) -> R): R = f(this)
它使用了两个泛型类型: T
和 R
。第一个是被调用者定义的,它的类型被函数
接收到。第二个是函数的返回值类型。
对非空值执行某个操作,安全调用操作符可以与 let
一起使用,如:
//使用前
if (forecast != null) dataMapper.convertDayToDomain(forecast) else null
//使用后
forecast?.let { dataMapper.convertDayToDomain(it) }
with
with
接收一个对象和一个函数,这个函数会作为这个对象的扩展函数执行。这表示我们根据推断可以在函数内使用 this 。定义如下:
inline fun with(receiver: T, f: T.() -> R): R = receiver.f()
T
代表接收类型, R
代表结果。如你所见,函数通过 f: T.() -> R
声明被定义成了扩展函数。这就是为什么我们可以调用 receiver.f()
。
例如:
fun convertFromDomain(forecast: ForecastList) = with(forecast) {
val daily = dailyForecast map { convertDayFromDomain(id, it)}
CityForecast(id, city, country, daily)
}
apply
它看起来与 with
很相似,但是是有点不同之处。 apply
可以避免创建builder的
方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,然后apply
函数会返回它同一个对象:
inline fun T.apply(f: T.() -> Unit): T { f(); return this }
这里我们只需要一个泛型类型,因为调用这个函数的对象也就是这个函数返回的对象。例如:
val textView = TextView(context).apply {
text = "Hello"
hint = "Hint"
textColor = android.R.color.white
}
class Test
val clazz = Test::class.java
val test = Test()
val clazz2 = test.javaClass
lateinit
用于 var
, by lazy
用于 val
lazy()
是一个函数,接受一个Lambda
表达式作为参数,返回一个Lazy
类型的实例,这个实例可以作为一个委托, 实现延迟加载属: 第一次调用 get()
时,保存Lambda
表达式执行的结果, 以后对 get()
的调用都只会简单地返回以前记住的结果,在lazy
中并没有setValue(…)
方法.
lateinit
用于生命周期流程中进行获取或者初始化的变量。