基本用法
main()
fun main() {
println("Hello world!")
}
fun main(args: Array<String>) {
println(args.contentToString())
}
print、println
fun main() {
//sampleStart
print("Hello ")
print("world!")
//sampleEnd
}
fun main() {
//sampleStart
println("Hello world!")
println(42)
//sampleEnd
}
函数
//sampleStart
fun sum(a: Int, b: Int): Int {
return a + b
}
//sampleEnd
fun main() {
print("sum of 3 and 5 is ")
println(sum(3, 5))
}
//sampleStart
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
//sampleEnd
fun main() {
printSum(-1, 8)
}
//Unit can be omitted
变量
定义只读局部变量使用关键字
fun main() {
//sampleStart
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
//sampleEnd
println("a = $a, b = $b, c = $c")
}
可重新赋值的变量使用
fun main() {
//sampleStart
var x = 5 // 自动推断出 `Int` 类型
x += 1
//sampleEnd
println("x = $x")
}
可以在顶层声明变量。
//sampleStart
val PI = 3.14
var x = 0
fun incrementX() {
x += 1
}
//sampleEnd
fun main() {
println("x = $x; PI = $PI")
incrementX()
println("incrementX()")
println("x = $x; PI = $PI")
}
类与对象
使用
class Shape
类的属性可以在其声明或主体中列出。
class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
具有类声明中所列参数的默认构造函数会自动可用。
class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
fun main() {
//sampleStart
val rectangle = Rectangle(5.0, 2.0)
println("The perimeter is ${rectangle.perimeter}")
//sampleEnd
}
类之间继承由冒号(
open class Shape
class Rectangle(var height: Double, var length: Double): Shape() {
var perimeter = (height + length) * 2
}
字符串模板
fun main() {
//sampleStart
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"
a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
//sampleEnd
println(s2)
}
if
//sampleStart
fun maxOf(a: Int, b: Int) = if (a > b) a else b
//sampleEnd
fun main() {
println("max of 0 and 42 is ${maxOf(0, 42)}")
}
for
fun main() {
//sampleStart
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
//sampleEnd
}
或者
fun main() {
//sampleStart
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
//sampleEnd
}
while(同java)
when(加强版switch)
//sampleStart
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
//sampleEnd
fun main() {
println(describe(1))
println(describe("Hello"))
println(describe(1000L))
println(describe(2))
println(describe("other"))
}
range
使用
fun main() {
//sampleStart
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
//sampleEnd
}
fun main() {
//sampleStart
for (x in 1..10 step 2) {
print(x)
}
println()
for (x in 9 downTo 0 step 3) {
print(x)
}
//sampleEnd
}
可变参数
fun main() {
val lettersArray = arrayOf("c", "d")
printAllStrings("a", "b", *lettersArray)
// abcd
}
fun printAllStrings(vararg strings: String) {
for (string in strings) {
print(string)
}
}
集合
使用 lambda 表达式来过滤(filter)与映射(map)集合:
fun main() {
//sampleStart
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.uppercase() }
.forEach { println(it) }
//sampleEnd
}
空值
当可能用
如果
fun parseInt(str: String): Int? {
// ……
}
使用返回可空值的函数:
fun parseInt(str: String): Int? {
return str.toIntOrNull()
}
//sampleStart
fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)
// 直接使用 `x * y` 会导致编译错误,因为它们可能为 null
if (x != null && y != null) {
// 在空检测后,x 与 y 会自动转换为非空值(non-nullable)
println(x * y)
}
else {
println("'$arg1' or '$arg2' is not a number")
}
}
//sampleEnd
fun main() {
printProduct("6", "7")
printProduct("a", "7")
printProduct("a", "b")
}
is
is
//sampleStart
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}
//sampleEnd
fun main() {
fun printLength(obj: Any) {
println("Getting the length of '$obj'. Result: ${getStringLength(obj) ?: "Error: The object is not a string"} ")
}
printLength("Incomprehensibilities")
printLength(1000)
printLength(listOf(Any()))
}
安全的调用
访问可空变量的属性的第二种选择是使用安全调用操作符
fun main() {
//sampleStart
val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // 无需安全调用
//sampleEnd
}
如果
安全调用在链式调用中很有用。例如,一个员工 Bob 可能会(或者不会)分配给一个部门。 可能有另外一个员工是该部门的负责人。获取 Bob 所在部门负责人(如果有的话)的名字, 写作:
bob?.department?.head?.name
如果任意一个属性(环节)为
如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:
fun main() {
//sampleStart
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 Kotlin 并忽略 null
}
//sampleEnd
}
安全调用也可以出现在赋值的左侧。这样,如果调用链中的任何一个接收者为
// 如果 `person` 或者 `person.department` 其中之一为空,都不会调用该函数:
person?.department?.head = managersPool.getManager()
Elvis 操作符
当有一个可空的引用
val l: Int = if (b != null) b.length else -1
除了写完整的
val l = b?.length ?: -1
如果
因为
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ……
}
第三种选择是为 NPE 爱好者准备的:非空断言运算符(
val l = b!!.length
因此,如果你想要一个 NPE,你可以得到它,但是你必须显式要求它,否则它不会不期而至。
As
通常,如果转换是不可能的,转换操作符会抛出一个异常。因此,称为
val x: String = y as String
请注意,可空的。 如果
val x: String? = y as String?
“安全的”(可空)转换操作符
为了避免异常,可以使用
val x: String? = y as? String
请注意,尽管事实上
安全的类型转换
如果对象不是目标类型,那么常规类型转换可能会导致
val aInt: Int? = a as? Int
可空类型的集合
如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
Nothing 类型
在 Kotlin 中
val s = person.name ?: throw IllegalArgumentException("Name required")
throw
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
当你调用该函数时,编译器会知道在该调用后就不再继续执行了:
val s = person.name ?: fail("Name required")
println(s) // 在此已知“s”已初始化
当处理类型推断时还可能会遇到这个类型。这个类型的可空变体
val x = null // “x”具有类型 `Nothing?`
val l = listOf(null) // “l”具有类型 `List<Nothing?>
类属性
声明类属性时,可以使用尾部逗号:
class Person(
val firstName: String,
val lastName: String,
var age: Int, // 尾部逗号
) { /*……*/ }
主构造
参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
class Customer(name: String) {
val customerKey = name.uppercase()
}
次构造函数
类也可以声明前缀有
class Person(val pets: MutableList<Pet> = mutableListOf())
class Pet {
constructor(owner: Person) {
owner.pets.add(this) // adds this pet to the list of its owner's pets
}
}
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用
class Person(val name: String) {
val children: MutableList<Person> = mutableListOf()
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
创建类的实例
创建一个类的实例,只需像普通函数一样调用构造函数:
val invoice = Invoice()
val customer = Customer("Joe Smith")
抽象类
类以及其中的某些或全部成员可以声明为
abstract class Polygon {
abstract fun draw()
}
class Rectangle : Polygon() {
override fun draw() {
// draw the rectangle
}
}
可以用一个抽象成员覆盖一个非抽象的开放成员。
open class Polygon {
open fun draw() {
// some default polygon drawing method
}
}
abstract class WildShape : Polygon() {
// Classes that inherit WildShape need to provide their own
// draw method instead of using the default on Polygon
abstract override fun draw()
}
继承
如需声明一个显式的超类型,请在类头中把超类型放到冒号之后:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果派生类有一个主构造函数,其基类可以(并且必须)根据其参数在该主构造函数中初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
覆盖方法
Kotlin 对于可覆盖的成员以及覆盖后的成员需要显式修饰符:
open class Shape {
open fun draw() { /*……*/ }
fun fill() { /*……*/ }
}
class Circle() : Shape() {
override fun draw() { /*……*/ }
}
Circle.draw()
标记为
open class Rectangle() : Shape() {
final override fun draw() { /*……*/ }
}
覆盖属性
属性与方法的覆盖机制相同。在超类中声明然后在派生类中重新声明的属性必须以
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
你也可以用一个
请注意,你可以在主构造函数中使用
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
class Polygon : Shape {
override var vertexCount: Int = 0 // 以后可以设置为任何数
}
接口
Kotlin 的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
使用关键字
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
实现接口
一个类或者对象可以实现一个或多个接口:
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
接口中的属性
可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们:
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
函数式(SAM)接口
只有一个抽象方法的接口称为
可以用
fun interface KRunnable {
fun invoke()
}
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}
扩展属性
与扩展函数类似,Kotlin 支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getter/setter 定义。
例如:
val House.number = 1 // 错误:扩展属性不能有初始化器
Data(POJO)
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
-
.equals()/
-
.toString() 格式是
-
.componentN()
-
.copy() 函数(见下文)
数据类与解构声明
为数据类生成的 解构声明中使用:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age
泛型
Kotlin 为泛型声明用法执行的类型安全检测在编译期进行。 运行时泛型类型的实例不保留关于其类型实参的任何信息。 其类型信息称为被
泛型类型检测与类型转换
由于类型擦除,并没有通用的方法在运行时检测一个泛型类型的实例是否通过指定类型参数所创建 ,并且编译器禁止这种
if (something is List<*>) {
something.forEach { println(it) } // 每一项的类型都是 `Any?`
}
类似地,当已经让一个实例的类型参数(在编译期)静态检测, 就可以对涉及非泛型部分做
fun handleStrings(list: MutableList<String>) {
if (list is ArrayList) {
// `list` 智能转换为 `ArrayList<String>`
}
}
属性委托
-
延迟属性(
-
可观察属性(
-
把多个属性储存在一个映射(
为了涵盖这些(以及其他)情况,Kotlin 支持
class Example {
var p: String by Delegate()
}
语法是:
例如:
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
当从委托到一个
val e = Example()
println(e.p)
输出结果:
Example@33a17727, thank you for delegating 'p' to me!
类似地,当我们给
e.p = "NEW"
输出结果:
NEW has been assigned to 'p' in Example@33a17727.
别名
类型别名为现有类型提供替代名称。 如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。
它有助于缩短较长的泛型类型。 例如,通常缩减集合类型是很有吸引力的:
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
你可以为函数类型提供另外的别名:
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean
Lambda 表达式语法
Lambda 表达式的完整语法形式如下:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
-
lambda 表达式总是括在花括号中。
-
完整语法形式的参数声明放在花括号内,并有可选的类型标注。
-
函数体跟在一个
-
如果推断出的该 lambda 的返回类型不是
如果将所有可选标注都留下,看起来如下:
val sum = { x: Int, y: Int -> x + y }
传递末尾的 lambda 表达式
按照 Kotlin 惯例,如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为
如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }
操作符
算术运算符
表达式 | 翻译为 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a…b | a.rangeTo(b) |
a…<b | a.rangeUntil(b) |
对于此表中的操作,编译器只是解析成
下面是一个从给定值起始的
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
in 操作符
表达式 | 翻译为 |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
对于
索引访问操作符
表达式 | 翻译为 |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
方括号转换为调用带有适当数量参数的
invoke 操作符
表达式 | 翻译为 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ……, i_n) | a.invoke(i_1, ……, i_n) |
圆括号转换为调用带有适当数量参数的
广义赋值
表达式 | 翻译为 |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b) |
对于赋值操作,例如
-
如果右列的函数可用:
-
如果相应的二元函数(即
-
确保其返回类型是
-
生成
-
否则试着生成
赋值在 Kotlin 中
相等与不等操作符
表达式 | 翻译为 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
这些操作符只使用函数 equals(other: Any?): Boolean, 可以覆盖它来提供自定义的相等性检测实现。不会调用任何其他同名函数(如
===
这个
比较操作符
表达式 | 翻译为 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
所有的比较都转换为对
this
限定的 this
要访问来自外部作用域的 类 或者扩展函数, 或者带标签的带有接收者的函数字面值)使用
class A { // 隐式标签 @A
inner class B { // 隐式标签 @B
fun Int.foo() { // 隐式标签 @foo
val a = this@A // A 的 this
val b = this@B // B 的 this
val c = this // foo() 的接收者,一个 Int
val c1 = this@foo // foo() 的接收者,一个 Int
val funLit = lambda@ fun String.() {
val d = this // funLit 的接收者,一个 String
}
val funLit2 = { s: String ->
// foo() 的接收者,因为它包含的 lambda 表达式
// 没有任何接收者
val d1 = this
}
}
}
}
协程
Kotlin 编写异步代码的方式是使用协程,这是一种计算可被挂起的想法。即一种函数可以在某个时刻暂停执行并稍后恢复的想法。
协程的一个好处是,当涉及到开发人员时,编写非阻塞代码与编写阻塞代码基本相同。编程模型本身并没有真正改变。
以下面的代码为例:
fun postItem(item: Item) {
launch {
val token = preparePost()
val post = submitPost(token, item)
processPost(post)
}
}
suspend fun preparePost(): Token {
// 发起请求并挂起该协程
return suspendCoroutine { /* ... */ }
}
此代码将启动长时间运行的操作,而不会阻塞主线程。
注解
注解是将元数据附加到代码的方法。要声明注解,请将
annotation class Fancy
注解的附加属性可以通过用元注解标注注解类来指定:
-
@Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性与表达式);
-
@Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true);
-
@Repeatable 允许在单个元素上多次使用相同的该注解;
-
@MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
用法
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
如果需要对类的主构造函数进行标注,则需要在构造函数声明中添加
class Foo @Inject constructor(dependency: MyDependency) { …… }
你也可以标注属性访问器:
class Foo {
var x: MyDependency? = null
@Inject set
}
构造函数
注解可以有接受参数的构造函数。
annotation class Special(val why: String)
@Special("example") class Foo {}