├── README.md └── concepts ├── classes_objects.md ├── control_flow.md ├── functions.md ├── null.md ├── packages_imports.md └── types.md /README.md: -------------------------------------------------------------------------------- 1 | # Java 转 Kotlin 指南 | Java To Kotlin Guide 2 | 3 | > Java 转 Kotlin 快速入门指南和速查手册 4 | 5 | ## 简介 6 | 7 | Kotlin 已经是一门成熟的编程语言,它简洁、高效、安全,可与 Java 互操作,出于 Java 而胜于 Java。本文用于向有 Java 基础的人员提供快速的 Kotlin 入门。Kotlin 的优势和详细介绍可参考[官方网站](https://kotlinlang.org/)。 8 | 9 | ## 快速开始 10 | 11 | 由于 Kotlin 就是 JetBrains 公司研发的,所以使用他们的 IDE(IntelliJ IDEA) 开发非常简单。 12 | 13 | 对于新项目直接使用 Kotlin 开发的,在创建项目时选择 Kotlin,选择对应的 JDK 版本即可。如果要在原 Java 项目中添加 Kotlin 进行混合开发,只要在项目中创建一个 `Kotlin 类/文件`,在项目中添加一个 `.kt` 文件,IDE 会自动提示 Kotlin 未配置,点击配置即可。IDE 会在项目配置文件(POM/Gradle)中添加 Kotlin 的依赖和插件,并提供 `kotlin.version` 的变量,用于指定 Kotlin 的版本。 14 | 15 | 下面是两者语法和特性的快速指南。 16 | 17 | [Github 版](https://github.com/wwtg99/java-to-kotlin-guide) / [Gitee 版](https://gitee.com/wwtg99/java-to-kotlin-guide) 18 | 19 | ## 基础 20 | 21 | ### 变量赋值 22 | 23 | Java 24 | 25 | ```java 26 | String name = "wwtg99"; 27 | final String name = "wwtg99"; 28 | ``` 29 | 30 | Kotlin 31 | 32 | ```kotlin 33 | var name = "wwtg99" // 可变 34 | val name = "wwtg99" // 不可变 35 | ``` 36 | 37 | Kotlin 的类型可自动推断,也可显式指定。 38 | 39 | ``` 40 | var name: String = "wwtg99" // 指定类型为 String 41 | ``` 42 | 43 | ### 空值 44 | 45 | Kotlin 的类型不能为空,如果可能为空,需要加 `?`。 46 | 47 | ```kotlin 48 | var otherName: String? = null 49 | ``` 50 | 51 | Kotlin 提供了很多空值判断的简化写法: 52 | 53 | Java 54 | 55 | ```java 56 | if (text != null) { 57 | int length = text.length(); 58 | } 59 | ``` 60 | 61 | Kotlin 62 | 63 | ```kotlin 64 | // 当 text 不为 null 则执行 65 | text?.let { 66 | val length = text.length 67 | } 68 | // 当 text 不为 null 则获取,不然为 null 69 | val length = text?.length 70 | ``` 71 | 72 | 更多用法请参考[空安全](./concepts/null.md)。 73 | 74 | ### 字符串判断空 75 | 76 | 字符串判断 null 或者空字符串不再需要其他的工具类了。 77 | 78 | Kotlin 为字符串提供了很多判断方法。 79 | 80 | - isEmpty(): 字符串长度是否为 0 81 | - isNotEmpty():字符串长度是否不为 0 82 | - isNullOrEmpty():字符串为 null 或者长度是否为 0 83 | - isBlank():字符串为空(仅包含空字符) 84 | - isNotBlank():字符串不为空 85 | - isNullOrBlank():字符串为 null 或者为空 86 | 87 | 同时,可通过扩展方法扩展更多的功能,参考[扩展](./concepts/classes_objects.md#扩展)。 88 | 89 | ### 字符串拼接、格式化 90 | 91 | Kotlin 提供了模版字符串,简化了字符串拼接和格式化的操作。 92 | 93 | ```kotlin 94 | val str1 = "abc" 95 | val str2 = "123" 96 | val str3 = "${str1}/${str2}" // abc/123 97 | ``` 98 | 99 | 具体详见[字符串](./concepts/types.md#字符串)。 100 | 101 | ### 字符串设置默认值 102 | 103 | Kotlin 中可以使用 if 表达式或者 `ifBlank` 方法来设置默认值。 104 | 105 | ```kotlin 106 | fun main() { 107 | val name = getName().ifBlank { "wwtg99" } 108 | println(name) 109 | } 110 | 111 | fun getName(): String = if (Random.nextBoolean()) "" else "wwtg99" 112 | ``` 113 | 114 | ### 字符串操作(取字符、子串) 115 | 116 | Kotlin 的字符串操作也非常简单。 117 | 118 | ```kotlin 119 | val str = "Java to Kotlin" 120 | printlin(str[0]) // J 121 | println(str.substring(8..13)) // Kotlin, 前面包含,后面不包含 122 | ``` 123 | 124 | ### 正则表达式 125 | 126 | 在 Kotlin 中使用 `Regex` 类来简化正则表达式的操作。 127 | 128 | ```kotlin 129 | val regex = Regex("""\w*\d+\w*""") 130 | val input = "login: Pokemon5, password: 1q2w3e4r5t" 131 | val replacementResult = regex.replace(input, replacement = "xxx") 132 | println("Initial input: '$input'") 133 | println("Anonymized input: '$replacementResult'") 134 | ``` 135 | 136 | ### 类型转换 137 | 138 | Kotlin 中使用 `is` 进行类型判断。 139 | 140 | Java 141 | 142 | ```java 143 | if (object instanceof Car) { 144 | Car car = (Car) object; 145 | } 146 | ``` 147 | 148 | Kotlin 149 | 150 | ```kotlin 151 | if (object is Car) { 152 | var car = object as Car // kotlin 支持自动类型转换,不需要显式转换,详见类型转换 153 | } 154 | 155 | // 安全的类型转换,不成功则返回 null 156 | var car = object as? Car 157 | ``` 158 | 159 | 可参考[安全的类型转换](./concepts/null.md#安全的类型转换)。 160 | 161 | ### 打印 162 | 163 | Java 164 | 165 | ```java 166 | System.out.print("wwtg99"); 167 | System.out.println("wwtg99"); 168 | ``` 169 | 170 | Kotlin 171 | 172 | ```kotlin 173 | print("wwtg99") // 不换行 174 | println("wwtg99") // 换行 175 | ``` 176 | 177 | ## 条件判断 178 | 179 | if 在 Kotlin 中可以是表达式: 180 | 181 | ```kotlin 182 | val max = if (a > b) a else b 183 | ``` 184 | 185 | 这就替代了三元操作符,所以 Kotlin 中没有三元操作符 `条件 ? 然后 : 否则`。 186 | 187 | if 表达式也可以是代码块,最后的表达式就是返回值。 188 | 189 | ```kotlin 190 | val max = if (a > b) { 191 | print("Choose a") 192 | a 193 | } else { 194 | print("Choose b") 195 | b 196 | } 197 | ``` 198 | 199 | 同时 Kotlin 还提供了类似 `switch` 的多分支判断表达式 `when`,详见[控制流程](./concepts/control_flow.md#if-条件判断)。 200 | 201 | ## 循环 202 | 203 | ```kotlin 204 | for (i in 1..10) { } 205 | 206 | for (i in 1 until 10) { } 207 | 208 | for (i in 10 downTo 0) { } 209 | 210 | for (i in 1..10 step 2) { } 211 | 212 | for (i in 10 downTo 0 step 2) { } 213 | 214 | for (item in collection) { } 215 | 216 | for ((key, value) in map) { } 217 | ``` 218 | 219 | 详见[控制流程](./concepts/control_flow.md#for-循环)。 220 | 221 | ## 集合 222 | 223 | Kotlin 中的集合分为了可变集合和不可变集合。 224 | 225 | List, Set, Map 都是不可变的,而 MutableList, MutableSet, MutableMap 是对应的可变集合。 226 | 227 | 可以通过 `listOf(1, 2, 3)`,`mutableListOf(1, 2, 3)` 等方法创建。 228 | 229 | ## 方法 230 | 231 | Kotlin 使用 `fun` 定义方法。 232 | 233 | ```kotlin 234 | fun doSomething() { 235 | // logic here 236 | } 237 | ``` 238 | 239 | 带返回类型的方法 240 | 241 | ```kotlin 242 | fun doSomething(): Int { 243 | // logic here 244 | return 1 245 | } 246 | ``` 247 | 248 | 单表达式函数 249 | 250 | ```kotlin 251 | fun double(x: Int): Int = x * 2 252 | ``` 253 | 254 | 详细可参考[函数](./concepts/functions.md)。 255 | 256 | ## 方法参数默认值 257 | 258 | Kotlin 支持方法参数的默认值,不需要额外重载方法。 259 | 260 | ```kotlin 261 | fun calculateCost(quantity: Int, pricePerItem: Double = 20.5) = quantity * pricePerItem 262 | 263 | calculateCost(10, 25.0) // 250 264 | calculateCost(10) // 205 265 | ``` 266 | 267 | ## 可变数量的参数 268 | 269 | Kotlin 使用 `varargs` 定义可变数量的参数。 270 | 271 | ```kotlin 272 | fun doSomething(vararg numbers: Int) { 273 | // logic here 274 | } 275 | ``` 276 | 277 | 详细可参考[可变参数](./concepts/functions.md#可变参数)。 278 | 279 | ## 构造函数 280 | 281 | Kotlin 中类有一个主构造函数和若干次构造函数。主构造函数在类头中声明,它跟在类名与可选的类型参数后。 282 | 283 | ```kotlin 284 | class Person constructor(firstName: String) { /*……*/ } 285 | ``` 286 | 287 | 如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。 288 | 289 | ```kotlin 290 | class Person(firstName: String) { /*……*/ } 291 | ``` 292 | 293 | 类也可以声明前缀有 constructor 的次构造函数,使用 `this` 委托到另一个构造函数。 294 | 295 | ```kotlin 296 | class Person(val name: String) { 297 | val children: MutableList = mutableListOf() 298 | constructor(name: String, parent: Person) : this(name) { 299 | parent.children.add(this) 300 | } 301 | } 302 | ``` 303 | 304 | > 注:Kotlin 中创建对象不需要 new。 305 | 306 | ## Getters 和 Setters 307 | 308 | 在类中使用 `var` 或 `val` 声明属性。会自动添加 getter 和 setter(val 无 setter),直接用属性名使用。 309 | 310 | ```kotlin 311 | class Address { 312 | var name: String = "wwtg99" 313 | var street: String = "street" 314 | } 315 | 316 | fun copyAddress(address: Address): Address { 317 | val result = Address() 318 | result.name = address.name // 将调用访问器 319 | result.street = address.street // 设置属性 320 | return result 321 | } 322 | ``` 323 | 324 | 详细可参考[Getter 与 Setter](./concepts/classes_objects.md#getter-与-setter)。 325 | 326 | ## 可见性修饰符 327 | 328 | Kotlin 提供了四个可见性修饰符:private、 protected、 internal 和 public,默认是 public。 329 | 330 | 简单来说,public 就是都可以访问,internal 是同模块都可以访问,protected 是内部及子类可访问,private 仅内部可访问。具体可参见[可见性修饰符](./concepts/classes_objects.md#可见性修饰符)。 331 | 332 | ## POJO(数据类) 333 | 334 | Kotlin 提供了数据类,用于 POJO(简单 Java 对象)或仅保存数据的类,类似于 Lombok 的 `@Data` 注解。 335 | 336 | ``` 337 | data class User(val name: String, val age: Int) 338 | ``` 339 | 340 | 数据类自动生成以下几个方法: 341 | 342 | - .equals()/.hashCode() 343 | - .toString() 格式是 "User(name=John, age=42)" 344 | - .componentN() 函数,按声明顺序对应于所有属性 345 | - .copy() 函数 346 | 347 | 数据类还有一些能力和约束条件,详见[数据类](./concepts/classes_objects.md#数据类)。 348 | 349 | ## 初始化代码块 350 | 351 | 在类中可以提供初始化代码块,初始化块按照它们出现在类体中的顺序执行。 352 | 353 | ```kotlin 354 | class InitOrderDemo(name: String) { 355 | val firstProperty = "第一个属性: $name".also(::println) 356 | 357 | init { 358 | println("第一个初始化代码块打印 $name") 359 | } 360 | 361 | val secondProperty = "第二个属性: ${name.length}".also(::println) 362 | 363 | init { 364 | println("第二个初始化代码块打印 ${name.length}") 365 | } 366 | } 367 | ``` 368 | 369 | ## 类继承 370 | 371 | Kotlin 中默认的类都是 final 的,不允许继承,要继承需要添加 `open` 修饰符。 372 | 373 | ```kotlin 374 | open class Shape { 375 | open fun draw() { /*……*/ } 376 | } 377 | ``` 378 | 379 | 子类使用冒号 `:` 继承,覆盖的方法前加 `override`。属性覆盖也类似。 380 | 381 | ```kotlin 382 | class Circle() : Shape() { 383 | override fun draw() { /*……*/ } 384 | } 385 | ``` 386 | 387 | 详细可参考[继承](./concepts/classes_objects.md#继承)。 388 | 389 | ## 单例对象 390 | 391 | Kotlin 使用 `object` 可以声明一个单例对象。 392 | 393 | ```kotlin 394 | object DataProviderManager { 395 | fun registerDataProvider(provider: DataProvider) { 396 | // …… 397 | } 398 | 399 | val allDataProviders: Collection 400 | get() = // …… 401 | } 402 | ``` 403 | 404 | ## 静态方法/变量 405 | 406 | Kotlin 使用伴生对象来提供静态方法和变量,使用 `companion object` 声明。 407 | 408 | ```kotlin 409 | class MyClass { 410 | companion object Factory { 411 | fun create(): MyClass = MyClass() 412 | } 413 | } 414 | 415 | val instance = MyClass.create() 416 | ``` 417 | 418 | > 注:伴生对象在运行时仍然是真实的实例成员,在 JVM 平台,如果使用 @JvmStatic 注解的方法或使用 @JvmField 注解或 const 关键词的属性,可以将伴生对象的成员生成为真正的静态方法和属性。 419 | 420 | ## 获取 Class 421 | 422 | Kotlin 需要获取 Java 的类(Class)对象可以通过以下方式: 423 | 424 | ```kotlin 425 | // 1. 在类上调用 ::class.java, 其中 ::class 是 Kotlin 的类 426 | val myClass1 = MyClass::class.java 427 | // 2. 在实例上调用 javaClass 428 | val myClass2 = myInstance.javaClass 429 | ``` 430 | 431 | ## Java 中调用 Kotlin 432 | 433 | ### 属性 434 | 435 | Kotlin 的 var 属性会生成 Java 的 Getter/Setter,val 只有 Getter。 436 | 437 | ### 包级函数 438 | 439 | Kotlin 的包级函数、扩展函数等,会生成一个特殊的 Java 类中的静态方法。例如,在 `org.example` 包内的 `app.kt` 文件中声明的所有的函数和属性,包括扩展函数,都编译成一个名为 `org.example.AppKt` 的 Java 类的静态方法。 440 | 441 | 可以使用 `@JvmName` 注解自定义生成的 Java 类的类名。 442 | 443 | ```kotlin 444 | @file:JvmName("DemoUtils") 445 | 446 | package org.example 447 | 448 | class Util 449 | 450 | fun getTime() { /*……*/ } 451 | ``` 452 | 453 | 在 Java 中 454 | 455 | ```java 456 | new org.example.Util(); 457 | org.example.DemoUtils.getTime(); 458 | ``` 459 | 460 | ### 生成重载 461 | 462 | 一个有默认实参值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法。如果希望向 Java 调用者暴露多个重载,可以使用 `@JvmOverloads` 注解。该注解也适用于构造函数、静态方法等。但它不能用于抽象方法,包括在接口中定义的方法。对于每一个有默认值的参数,都会生成一个额外的重载,这个重载会把这个参数和它右边的所有参数都移除掉。 463 | 464 | ```kotlin 465 | class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) { 466 | @JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*……*/ } 467 | } 468 | ``` 469 | 470 | 会生成如下方法 471 | 472 | ```java 473 | // 构造函数: 474 | Circle(int centerX, int centerY, double radius) 475 | Circle(int centerX, int centerY) 476 | 477 | // 方法 478 | void draw(String label, int lineWidth, String color) { } 479 | void draw(String label, int lineWidth) { } 480 | void draw(String label) { } 481 | ``` 482 | 483 | ### 受检异常 484 | 485 | Kotlin 没有受检异常,Kotlin 函数的 Java 签名不会声明抛出异常,要声明这个异常,需要添加 `@Throws` 注解。 486 | 487 | ```kotlin 488 | @Throws(IOException::class) 489 | fun writeToFile() { 490 | /*……*/ 491 | throw IOException() 492 | } 493 | ``` 494 | 495 | ## 指南文档 496 | 497 | - [类型](./concepts/types.md) 498 | - [控制流程](./concepts/control_flow.md) 499 | - [包和导入](./concepts/packages_imports.md) 500 | - [类和对象](./concepts/classes_objects.md) 501 | - [函数](./concepts/functions.md) 502 | - [空安全](./concepts/null.md) 503 | 504 | ## 参考 505 | 506 | - [Kotlin 官方文档](https://kotlinlang.org/docs/getting-started.html) 507 | -------------------------------------------------------------------------------- /concepts/classes_objects.md: -------------------------------------------------------------------------------- 1 | # 类和对象 2 | 3 | ## 类 4 | 5 | Kotlin 中使用 `class` 关键词声明类。 6 | 7 | ```kotlin 8 | class Person { /*...*/ } 9 | ``` 10 | 11 | 如果没有类体,可以省略花括号。 12 | 13 | ### 构造函数 14 | 15 | 在 Kotlin 中类有一个主构造函数和若干个次构造函数,主构造函数在类头中声明。 16 | 17 | ```kotlin 18 | class Person constructor(firstName: String) { /*...*/ } 19 | ``` 20 | 21 | 如果主构造函数没有任何注解或可见性修饰符,可以省略 `constructor` 关键词。 22 | 23 | 主构造函数初始化类实例和属性,但不能在类头中包含任何可运行的代码。如果需要在初始化时执行代码,可在类体中声明初始化块。初始化块使用 `init` 关键词声明。 24 | 25 | ```kotlin 26 | class InitOrderDemo(name: String) { 27 | val firstProperty = "第一个属性: $name".also(::println) 28 | 29 | init { 30 | println("第一个初始化块,打印 $name") 31 | } 32 | 33 | val secondProperty = "第二个属性: ${name.length}".also(::println) 34 | 35 | init { 36 | println("第二个初始化块,打印 ${name.length}") 37 | } 38 | } 39 | ``` 40 | 41 | 主构造函数的参数能在初始化块和属性初始化时使用。 42 | 43 | ```kotlin 44 | class Customer(name: String) { 45 | val customerKey = name.uppercase() 46 | } 47 | ``` 48 | 49 | Kotlin 还有一种简便的方式直接在主构造函数中声明属性,并初始化。 50 | 51 | ```kotlin 52 | class Person(val firstName: String, val lastName: String, var age: Int) 53 | ``` 54 | 55 | 并可以给属性添加默认值。 56 | 57 | ```kotlin 58 | class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true) 59 | ``` 60 | 61 | 其中,可以添加最后尾部的逗号,便于统一格式化。 62 | 63 | ```kotlin 64 | class Person( 65 | val firstName: String, 66 | val lastName: String, 67 | var age: Int, // 尾部逗号 68 | ) { /*...*/ } 69 | ``` 70 | 71 | 与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。 72 | 73 | ### 次构造函数 74 | 75 | 可以在类体中使用 `constructor` 关键词声明次构造函数。 76 | 77 | ```kotlin 78 | class Person(val pets: MutableList = mutableListOf()) 79 | 80 | class Pet { 81 | constructor(owner: Person) { 82 | owner.pets.add(this) 83 | } 84 | } 85 | ``` 86 | 87 | 如果类有一个主构造函数,每个次构造函数需要直接或通过其他次构造函数间接委托给主构造函数,使用 `this` 关键词。 88 | 89 | ```kotlin 90 | class Person(val name: String) { 91 | val children: MutableList = mutableListOf() 92 | constructor(name: String, parent: Person) : this(name) { 93 | parent.children.add(this) 94 | } 95 | } 96 | ``` 97 | 98 | > 注:初始化块中的代码实际上会成为主构造函数的一部分。对主构造函数的委托发生在访问次构造函数的第一条语句前,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使类没有主构造函数,仍然会隐式执行初始化块。 99 | 100 | 如果一个非抽象类没有声明任何主或次构造函数,它也会有一个生成的不带参数的 public 的主构造函数。如果你不希望类有一个公有构造函数,那么声明一个带有非默认可见性的空的主构造函数即可。 101 | 102 | ```kotlin 103 | class DontCreateMe private constructor() { /*……*/ } 104 | ``` 105 | 106 | > 注:在 JVM 中,如果所有主构造函数的参数都有默认值,则会生成一个用默认值初始化的无参构造函数。这样便于 Jackson 或 JPA 使用。 107 | 108 | ### 创建类的实例 109 | 110 | 创建类的实例只需要调用它的构造函数即可,也就是不需要 `new` 关键词了。 111 | 112 | ```kotlin 113 | val invoice = Invoice() 114 | 115 | val customer = Customer("wwtg99") 116 | ``` 117 | 118 | ## 继承 119 | 120 | 在 Kotlin 中所有类都有一个共同的超类:Any,类似 Java 的 Object。Any 有三个方法,`equals()`, `hashCode()` 和 `toString()`,所以所有类都有这三个方法。 121 | 122 | 默认 Kotlin 的类都是 final (不可继承)的,若要类可继承,要使用 `open` 关键词声明。 123 | 124 | ```kotlin 125 | open class Base 126 | ``` 127 | 128 | 然后使用分号(:)声明继承的父类。 129 | 130 | ```kotlin 131 | open class Base(p: Int) 132 | 133 | class Derived(p: Int) : Base(p) 134 | ``` 135 | 136 | 如果子类有主构造函数,父类必须在主构造函数中初始化参数。如果子类没有主构造函数,那么次构造函数必须通过 `super` 关键词委托给父类或其他构造函数,这类似于 Java 的子类构造函数中必须使用 super 调用(或隐式调用)父类构造函数。 137 | 138 | ```kotlin 139 | class MyView : View { 140 | constructor(ctx: Context) : super(ctx) 141 | 142 | constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) 143 | } 144 | ``` 145 | 146 | ### 覆盖方法 147 | 148 | Kotlin 要求使用 `override` 关键词声明覆盖的方法,类似 Java 的 `@override` 注解,但不能省略。 149 | 150 | ```kotlin 151 | open class Shape { 152 | open fun draw() { /*...*/ } 153 | fun fill() { /*...*/ } 154 | } 155 | 156 | class Circle() : Shape() { 157 | override fun draw() { /*...*/ } 158 | } 159 | ``` 160 | 161 | 标记为 `override` 的成员本身就是开放的,如果不想被再次覆盖,请使用 `final` 关键词。 162 | 163 | ```kotlin 164 | open class Rectangle() : Shape() { 165 | final override fun draw() { /*……*/ } 166 | } 167 | ``` 168 | 169 | ### 覆盖属性 170 | 171 | 覆盖属性和覆盖方法类似,必须使用 `override` 关键词且必须使用兼容的类型。 172 | 173 | ```kotlin 174 | open class Shape { 175 | open val vertexCount: Int = 0 176 | } 177 | 178 | class Rectangle : Shape() { 179 | override val vertexCount = 4 180 | } 181 | ``` 182 | 183 | 可以使用 `var` 属性覆盖一个 `val` 声明的属性,但反过来就不行。因为 `var` 声明的属性有 get 和 set 方法,而 `val` 声明的属性只有 get 方法。 184 | 185 | ### 继承的注意事项 186 | 187 | 在派生类实例化的过程中,会首先完成父类的初始化,因此在父类初始化块的执行中请不要使用 `open` 的方法或属性,因为它们在子类中还没有初始化。 188 | 189 | 子类中可以使用 `super` 关键词调用父类中的方法或属性。如果父类中有同名的成员,可以使用尖括号区分。 190 | 191 | ```kotlin 192 | open class Rectangle { 193 | open fun draw() { /* ... */ } 194 | } 195 | 196 | interface Polygon { 197 | fun draw() { /* ... */ } // 接口的成员默认都是 open 的 198 | } 199 | 200 | class Square() : Rectangle(), Polygon { 201 | // 必须实现方法 202 | override fun draw() { 203 | super.draw() // 调用 Rectangle.draw() 204 | super.draw() // 调用 Polygon.draw() 205 | } 206 | } 207 | ``` 208 | 209 | ## 属性 210 | 211 | ### 声明属性 212 | 213 | Kotlin 类中使用 `var` 声明可变属性,`val` 声明不可变属性。使用属性,直接用名称即可。 214 | 215 | ```kotlin 216 | class Address { 217 | var name: String = "wwtg99" 218 | var street: String = "street" 219 | } 220 | 221 | fun copyAddress(address: Address): Address { 222 | val result = Address() 223 | result.name = address.name // 将调用访问器 224 | result.street = address.street 225 | // …… 226 | return result 227 | } 228 | ``` 229 | 230 | ### Getter 与 Setter 231 | 232 | 声明属性的完整语法如下所示: 233 | 234 | ```kotlin 235 | var [: ] [= ] 236 | [] 237 | [] 238 | ``` 239 | 240 | 其中,初始化器、getter、setter 都是可选的,如果类型可以推断出来的话也是可选的。 241 | 242 | 不可变属性没有 setter。 243 | 244 | ```kotlin 245 | val simple: Int? // 类型 Int, 默认的 getter, 必须在构造函数中初始化 246 | val inferredType = 1 // 推断出类型 Int,默认的 getter 247 | ``` 248 | 249 | 可以自定义属性获取方法,这样每次获取属性时都会调用。 250 | 251 | ```kotlin 252 | class Rectangle(val width: Int, val height: Int) { 253 | val area: Int // 类型可省略,可推断出来 254 | get() = this.width * this.height 255 | } 256 | ``` 257 | 258 | 自定义的属性设置方法,这样每次设置属性都会调用。 259 | 260 | ```kotlin 261 | var stringRepresentation: String 262 | get() = this.toString() 263 | set(value) { 264 | setDataFromString(value) // 解析字符串,或设置其他属性 265 | } 266 | ``` 267 | 268 | ### 延迟初始化 269 | 270 | 可以使用 `lateinit` 关键词来声明一个延迟初始化的属性或变量。 271 | 272 | ```kotlin 273 | public class MyTest { 274 | lateinit var subject: TestSubject 275 | 276 | @SetUp fun setup() { 277 | subject = TestSubject() 278 | } 279 | 280 | @Test fun test() { 281 | subject.method() 282 | } 283 | } 284 | ``` 285 | 286 | 这个关键词只能用于类体中 `var` 定义的属性(不在主构造函数中声明,且没有自定义的 getter 或 setter)、顶层属性或局部变量,该属性或变量必须是非空类型,且不能是原生类型。 287 | 288 | 可以使用 `.isInitialized` 来检查一个延迟属性是否被初始化了。 289 | 290 | ```kotlin 291 | if (foo::bar.isInitialized) { 292 | println(foo.bar) 293 | } 294 | ``` 295 | 296 | ## 接口 297 | 298 | Kotlin 中的接口和 Java 类似,可以有抽象或者实现方法,但不能保存状态。它可以有属性但必须声明为抽象或提供访问器实现。 299 | 300 | ```kotlin 301 | interface MyInterface { 302 | fun bar() 303 | fun foo() { 304 | // 可选的方法体 305 | } 306 | } 307 | ``` 308 | 309 | 实现接口 310 | 311 | ```kotlin 312 | class Child : MyInterface { 313 | override fun bar() { 314 | // body 315 | } 316 | } 317 | ``` 318 | 319 | ### 接口中的属性 320 | 321 | 在接口中声明的属性要么是抽象的,要么提供访问器的实现。 322 | 323 | ```kotlin 324 | interface MyInterface { 325 | val prop: Int // 抽象的 326 | 327 | val propertyWithImplementation: String 328 | get() = "foo" 329 | 330 | fun foo() { 331 | print(prop) 332 | } 333 | } 334 | 335 | class Child : MyInterface { 336 | override val prop: Int = 29 337 | } 338 | ``` 339 | 340 | ### 接口继承 341 | 342 | 接口也可以继承其他接口,这和 Java 类似。当实现多个接口时,可能遇到同名冲突,使用 `super<>` 来指定具体哪个方法。 343 | 344 | ```kotlin 345 | interface A { 346 | fun foo() { print("A") } 347 | fun bar() 348 | } 349 | 350 | interface B { 351 | fun foo() { print("B") } 352 | fun bar() { print("bar") } 353 | } 354 | 355 | class C : A { 356 | override fun bar() { print("bar") } 357 | } 358 | 359 | class D : A, B { 360 | override fun foo() { 361 | super.foo() 362 | super.foo() 363 | } 364 | 365 | override fun bar() { 366 | super.bar() 367 | } 368 | } 369 | ``` 370 | 371 | ## 函数式(SAM)接口 372 | 373 | 只有一个抽象方法的接口叫函数式接口或单一抽象方法(single abstract method, SAM)接口,类似于 Java 的 `@FunctionalInterface` 注解标记的接口。在 Kotlin 中使用 `fun` 关键词来定义。 374 | 375 | ```kotlin 376 | fun interface KRunnable { 377 | fun invoke() 378 | } 379 | ``` 380 | 381 | 函数式接口可以方便地用于 Lambda 表达式中。 382 | 383 | ```kotlin 384 | // 现定义如下函数式接口 385 | fun interface IntPredicate { 386 | fun accept(i: Int): Boolean 387 | } 388 | 389 | // 使用 Lambda 创建一个实例 390 | val isEven = IntPredicate { it % 2 == 0 } 391 | ``` 392 | 393 | ## 可见性修饰符 394 | 395 | 在 Kotlin 中有这四个可见性修饰符:private、protected、internal 和 public。默认可见性是 public。 396 | 397 | ### 包 398 | 399 | 函数、属性、类、对象和接口可以直接在包内的顶层声明。 400 | 401 | ```kotlin 402 | // 文件名: example.kt 403 | package foo 404 | 405 | fun baz() { ... } 406 | class Bar { ... } 407 | ``` 408 | 409 | - 如果不使用任何可见性修饰符,默认为 public,这意味着你的声明将随处可见。 410 | - 如果声明为 private,它只会在声明它的文件内可见。 411 | - 如果声明为 internal,它会在相同模块内随处可见。 412 | - protected 修饰符不适用于顶层声明。 413 | 414 | ### 类成员 415 | 416 | 对于类内部的成员: 417 | 418 | - private 意味着该成员只在这个类内部(包含其所有成员)可见。 419 | - protected 意味着该成员具有与 private 一样的可见性,但也在子类中可见。 420 | - internal 意味着能见到类声明的本模块内都可见。 421 | - public 意味着能见到类声明的都可见。 422 | 423 | 如果覆盖一个 protected 或 internal 成员并且没有显式指定其可见性,该成员还会具有与原始成员相同的可见性。 424 | 425 | > 注:局部变量、函数和类不能有可见性修饰符。 426 | 427 | ### 模块 428 | 429 | 在 Kotlin 中模块指的是编译在一起的一套 Kotlin 文件,如: 430 | 431 | - 一个 IntelliJ IDEA 模块 432 | - 一个 Maven 项目 433 | - 一个 Gradle 源代码集(例外是 test 源代码集可以访问 main 的 internal 声明) 434 | - 一次 Ant 任务执行所编译的一套文件 435 | 436 | ## 扩展 437 | 438 | Kotlin 能够对一个类或接口扩展新功能而无需继承该类或者使用像装饰者这样的设计模式。 439 | 440 | ### 扩展函数 441 | 442 | 声明一个扩展函数需用一个被扩展的类型来作为它的前缀。例如下面代码为 MutableList 添加一个 swap 函数: 443 | 444 | ```kotlin 445 | fun MutableList.swap(index1: Int, index2: Int) { 446 | val tmp = this[index1] // 'this' 对应 list 实例 447 | this[index1] = this[index2] 448 | this[index2] = tmp 449 | } 450 | ``` 451 | 452 | 然后就可以像下面这样使用 453 | 454 | ```kotlin 455 | val list = mutableListOf(1, 2, 3) 456 | list.swap(0, 2) // 'swap()' 方法中的 this 就是这个 list 457 | ``` 458 | 459 | > 注:扩展是静态解析的,并不是真正在类中添加成员,不过是用一个函数来调用。 460 | 461 | ### 可空接收者 462 | 463 | 如果需要为可以为 null 的对象调用扩展,获取的 this 就为 null,注意做好 null 的判断。 464 | 465 | ```kotlin 466 | fun Any?.toString(): String { 467 | if (this == null) return "null" 468 | // 在 null 检查之后,this 就是非空对象了 469 | // 可解析为 Any 的任何成员函数 470 | return toString() 471 | } 472 | ``` 473 | 474 | ### 扩展属性 475 | 476 | 与扩展函数类似,Kotlin 支持扩展属性。 477 | 478 | ```kotlin 479 | val List.lastIndex: Int 480 | get() = size - 1 481 | ``` 482 | 483 | > 注:扩展属性并不是添加一个属性到对象中,因此不能有初始化器,只能通过 getter/setter 来处理。 484 | 485 | ### 伴生对象扩展 486 | 487 | 同样可以扩展伴生对象的方法或属性。 488 | 489 | ```kotlin 490 | class MyClass { 491 | companion object { } // 通过 "Companion" 引用 492 | } 493 | 494 | fun MyClass.Companion.printCompanion() { println("companion") } 495 | 496 | fun main() { 497 | MyClass.printCompanion() 498 | } 499 | ``` 500 | 501 | ## 数据类 502 | 503 | Kotlin 中有一个专门用于保存数据的类,类似于 Java 的 POJO (Plain Ordinary Java Object,简单 Java 对象),使用数据类可以替代 Lombok 的 `@Data`、`@ToString`、`@EqualsAndHashCode` 等注解。 504 | 505 | ```kotlin 506 | data class User(val name: String, val age: Int) 507 | ``` 508 | 509 | 数据类会自动生成几个方法: 510 | 511 | - .equals()/.hashCode() 512 | - .toString() 形式为 "User(name=John, age=42)" 513 | - .componentN() 函数按声明的顺序对应所有的属性 514 | - .copy() 函数 515 | 516 | 同时对数据类有一些要求: 517 | 518 | - 主构造函数必须至少有一个参数(可以有默认值)。 519 | - 主构造函数的所有参数必须标记为 val 或 var。 520 | - 数据类不能是抽象、开放、密封或者内部的。 521 | 522 | 其中,.equals()、.hashCode() 或者 .toString() 可以显式覆盖,而 .componentN() 和 .copy() 不允许。 523 | 524 | ### 类体中定义的属性 525 | 526 | 自动生成仅针对主构造函数的属性,如果要排除某个属性,就在类体中定义。 527 | 528 | ```kotlin 529 | data class Person(val name: String) { 530 | var age: Int = 0 531 | } 532 | ``` 533 | 534 | 上例中,只有 name 属性会生成到 .toString()、.equals()、.hashCode()、 .copy() 的实现中,也只会生成一个 .component1()。 535 | 536 | ### 解构声明 537 | 538 | 数据类生成的 .componentN() 函数可用于对象解构。 539 | 540 | ```kotlin 541 | val jane = User("Jane", 35) 542 | val (name, age) = jane 543 | println("$name, $age years of age") 544 | // Jane, 35 years of age 545 | ``` 546 | 547 | ## 密封类和密封接口 548 | 549 | 密封类和密封接口为类层次结构提供受控继承。密封类的所有直接子类在编译时都是已知的。在定义密封类的模块和包之外,不得出现其他子类。一旦编译了带有密封接口的模块,就不能再创建新的子类。 550 | 551 | 当密封类和 `when` 语句结合使用时,就可以判断所有已知的子类。 552 | 553 | 密封类主要用于以下场景: 554 | 555 | - 需要受控继承,在编译期间有哪些子类是确定的。 556 | - 类型安全的设计 557 | - 对关闭的 API,不希望第三方客户端来扩展 558 | 559 | ### 声明密封类和密封接口 560 | 561 | 使用 `sealed` 关键词来声明。 562 | 563 | ```kotlin 564 | // 创建一个密封接口 565 | sealed interface Error 566 | 567 | // 创建一个密封类实现密封接口 568 | sealed class IOError(): Error 569 | 570 | // 定义密封类的子类 571 | class FileReadError(val file: File): IOError() 572 | class DatabaseError(val source: DataSource): IOError() 573 | 574 | // 创建一个单例实例实现密封接口 575 | object RuntimeError : Error 576 | ``` 577 | 578 | > 注:密封类总是抽象类 579 | 580 | ### 密封类结合 when 表达式 581 | 582 | ```kotlin 583 | // 记录错误的函数 584 | fun log(e: Error) = when(e) { 585 | is Error.FileReadError -> println("Error while reading file ${e.file}") 586 | is Error.DatabaseError -> println("Error while reading from database ${e.source}") 587 | Error.RuntimeError -> println("Runtime error") 588 | // 不需要 else,因为所有类型已经遍历了 589 | } 590 | ``` 591 | 592 | ## 范型 593 | 594 | Kotlin 与 Java 一样可以有范型参数。 595 | 596 | ```kotlin 597 | class Box(t: T) { 598 | var value = t 599 | } 600 | ``` 601 | 602 | 创建类实例只需提供类型参数,如果可以推断则可省略。 603 | 604 | ```kotlin 605 | val box: Box = Box(1) 606 | 607 | // 编译器可根据 1 的类型推断出 Box 608 | val box = Box(1) 609 | ``` 610 | 611 | ### 型变 612 | 613 | Java 中的通配符范型参数是一个很棘手的问题,但 Kotlin 中没有,取而代之的是有声明处型变(declaration-site variance)与类型投影(type projections)。 614 | 615 | Java 中的范型有类型不兼容的问题,例如 `Collection` 可以读取 `E` 类型及其子类型,但是不能添加,要使用 `extends` 和 `super` 来确定范型的上界和下界。 616 | 617 | ```java 618 | // Java 619 | interface Collection ... { 620 | void addAll(Collection items); 621 | } 622 | ``` 623 | 624 | Kotlin 中如果范型 T 仅出现在方法返回,不在方法参数中,可以使用 `out` 修饰符。 625 | 626 | ```kotlin 627 | interface Source { 628 | fun nextT(): T 629 | } 630 | 631 | fun demo(strs: Source) { 632 | val objects: Source = strs // 这个没问题,因为 T 是一个 out 参数 633 | // …… 634 | } 635 | ``` 636 | 637 | 反过来,如果范型参数只在函数方法中,不在返回值中,则可以使用 `in` 修饰符。 638 | 639 | ```kotlin 640 | interface Comparable { 641 | operator fun compareTo(other: T): Int 642 | } 643 | 644 | fun demo(x: Comparable) { 645 | x.compareTo(1.0) // 1.0 是 Double 类型,是 Number 的子类型 646 | // 因此 x 可以兼容类型 Comparable 647 | val y: Comparable = x // 没问题 648 | } 649 | ``` 650 | 651 | ## 嵌套类 652 | 653 | 类可以嵌套在其他类中。 654 | 655 | ```kotlin 656 | class Outer { 657 | private val bar: Int = 1 658 | class Nested { 659 | fun foo() = 2 660 | } 661 | } 662 | 663 | val demo = Outer.Nested().foo() // == 2 664 | ``` 665 | 666 | ## 内部类 667 | 668 | 使用 inner 关键词的嵌套类能够访问其外部类的成员,内部类会带有一个对外部类的对象的引用。 669 | 670 | ```kotlin 671 | class Outer { 672 | private val bar: Int = 1 673 | inner class Inner { 674 | fun foo() = bar 675 | } 676 | } 677 | 678 | val demo = Outer().Inner().foo() // == 1 679 | ``` 680 | 681 | 要访问来自外部作用域的 `this` 使用 `this@label`,其中 `@label` 是一个代指 `this` 来源的标签。 682 | 683 | ## 匿名内部类 684 | 685 | 使用对象表达式可创建匿名内部类。 686 | 687 | ```kotlin 688 | window.addMouseListener(object : MouseAdapter() { 689 | override fun mouseClicked(e: MouseEvent) { ... } 690 | 691 | override fun mouseEntered(e: MouseEvent) { ... } 692 | }) 693 | ``` 694 | 695 | ## 枚举类 696 | 697 | 枚举类与 Java 类似。 698 | 699 | ```kotlin 700 | enum class Direction { 701 | NORTH, SOUTH, WEST, EAST 702 | } 703 | ``` 704 | 705 | 枚举类可以有参数,实现接口(但不能继承类)。 706 | 707 | ## 内联类 708 | 709 | 内联类是基于值的类的子集,它们没有标识,只能容纳值。使用 `value` 关键词声明内联类。 710 | 711 | ```kotlin 712 | value class Password(private val s: String) 713 | ``` 714 | 715 | 对于 JVM 需要加上 `@JvmInline` 注解。 716 | 717 | ```kotlin 718 | @JvmInline 719 | value class Password(private val s: String) 720 | ``` 721 | 722 | 内联类必须在主构造函数中含有唯一的一个属性并初始化。在运行时,将使用这个唯一属性来表示内联类的实例。 723 | 724 | 内联类支持定义属性和方法,包含初始化块等。 725 | 726 | ```kotlin 727 | @JvmInline 728 | value class Person(private val fullName: String) { 729 | init { 730 | require(fullName.isNotEmpty()) { 731 | "Full name shouldn't be empty" 732 | } 733 | } 734 | 735 | constructor(firstName: String, lastName: String) : this("$firstName $lastName") { 736 | require(lastName.isNotBlank()) { 737 | "Last name shouldn't be empty" 738 | } 739 | } 740 | 741 | val length: Int 742 | get() = fullName.length 743 | 744 | fun greet() { 745 | println("Hello, $fullName") 746 | } 747 | } 748 | 749 | fun main() { 750 | val name1 = Person("Kotlin", "Mascot") 751 | val name2 = Person("Kodee") 752 | name1.greet() // greet() 作为静态方法调用 753 | println(name2.length) // 属性获取作为静态方法调用 754 | } 755 | ``` 756 | 757 | 内联类可以实现接口,但不能继承类或被继承。 758 | 759 | ## 对象表达式 760 | 761 | 有时候需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 762 | 763 | 对象表达式为一个匿名类创建实例,适合于一次性使用。 764 | 765 | ```kotlin 766 | val helloWorld = object { 767 | val hello = "Hello" 768 | val world = "World" 769 | // 对象表达式继承 Any 类, 因此 toString() 方法的 override 是必须的 770 | override fun toString() = "$hello $world" 771 | } 772 | 773 | print(helloWorld) 774 | ``` 775 | 776 | 对象表达式可以使用继承。 777 | 778 | ```kotlin 779 | window.addMouseListener(object : MouseAdapter() { 780 | override fun mouseClicked(e: MouseEvent) { /*...*/ } 781 | 782 | override fun mouseEntered(e: MouseEvent) { /*...*/ } 783 | }) 784 | ``` 785 | 786 | ## 对象声明 787 | 788 | Kotlin 让单例模式很容易使用。 789 | 790 | ```kotlin 791 | object DataProviderManager { 792 | fun registerDataProvider(provider: DataProvider) { 793 | // ... 794 | } 795 | 796 | val allDataProviders: Collection 797 | get() = // ... 798 | } 799 | ``` 800 | 801 | 这称为对象声明,它不是一个表达式,不能用在赋值语句的右边。对象声明的初始化过程是线程安全的并且在首次访问时进行。如需引用该对象,直接使用其名称即可。 802 | 803 | ```kotlin 804 | DataProviderManager.registerDataProvider(...) 805 | ``` 806 | 807 | 对象可以继承其他类。 808 | 809 | 可以用 `data` 关键词修饰,将对象声明为数据类,自动生成相关的方法(但是没有 copy() 和 componentN() 方法)。 810 | 811 | ```kotlin 812 | data object MyDataObject { 813 | val x: Int = 3 814 | } 815 | 816 | fun main() { 817 | println(MyDataObject) // MyDataObject 818 | } 819 | ``` 820 | 821 | ## 伴生对象 822 | 823 | 类内部的对象可以用 `companion` 关键词声明为伴生对象。 824 | 825 | ```kotlin 826 | class MyClass { 827 | companion object Factory { 828 | fun create(): MyClass = MyClass() 829 | } 830 | } 831 | ``` 832 | 833 | 该伴生对象的成员可通过只使用类名来调用。 834 | 835 | ```kotlin 836 | val instance = MyClass.create() 837 | ``` 838 | 839 | 可以省略伴生对象的名称,在这种情况下将使用名称 Companion 。 840 | 841 | ```kotlin 842 | class MyClass { 843 | companion object { } 844 | } 845 | 846 | val x = MyClass.Companion 847 | ``` 848 | 849 | > 注:即使伴生对象的成员看起来像 Java 的静态成员,在运行时他们仍然是真实对象的实例成员 850 | 851 | 在 JVM 平台中,如果使用 `@JvmStatic` 注解,则可以生成为真正的静态成员。 852 | 853 | ## 对象表达式和对象声明的差异 854 | 855 | - 对象表达式是在使用它们的地方立即执行以及初始化的。 856 | - 对象声明是在第一次被访问到时延迟初始化的。 857 | - 伴生对象的初始化是在相应的类被加载时,与 Java 静态初始化器的语义相匹配。 858 | 859 | ## 委托 860 | 861 | 委托是一种很好的实现继承的方式。 862 | 863 | 如下所示,`Derived` 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 `Base`。 864 | 865 | ```kotlin 866 | interface Base { 867 | fun print() 868 | } 869 | 870 | class BaseImpl(val x: Int) : Base { 871 | override fun print() { print(x) } 872 | } 873 | 874 | class Derived(b: Base) : Base by b 875 | 876 | fun main() { 877 | val base = BaseImpl(10) 878 | Derived(base).print() 879 | } 880 | ``` 881 | 882 | ## 属性委托 883 | 884 | Kotlin 支持属性委托,例如在如下的场景中: 885 | 886 | - 延迟属性,只在首次访问时计算 887 | - 可观察属性,监听器会收到属性变更的通知 888 | - 把多个属性存储在一个 map 中,而不是单独的属性中 889 | 890 | 基本的语法 891 | 892 | ```kotlin 893 | class Example { 894 | var p: String by Delegate() 895 | } 896 | ``` 897 | 898 | 无需实现接口,但需要提供 `getValue` 方法和 `setValue` 方法(用于 var)。 899 | 900 | ```kotlin 901 | import kotlin.reflect.KProperty 902 | 903 | class Delegate { 904 | operator fun getValue(thisRef: Any?, property: KProperty<*>): String { 905 | return "$thisRef, thank you for delegating '${property.name}' to me!" 906 | } 907 | 908 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { 909 | println("$value has been assigned to '${property.name}' in $thisRef.") 910 | } 911 | } 912 | ``` 913 | 914 | `val` 必须提供 `getValue`,参数为: 915 | 916 | - thisRef 必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是其父类型。 917 | - property 必须是类型 KProperty<\*> 或其父类型。 918 | 919 | 返回与属性相同的类型或其子类型。 920 | 921 | `var` 还必须提供 `setValue`,参数为: 922 | 923 | - thisRef 必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是其父类型。 924 | - property 必须是类型 KProperty<\*> 或其父类型。 925 | - value 必须与属性类型相同(或者是其父类型)。 926 | 927 | 两函数都需要用 `operator` 关键词来进行标记。 928 | 929 | 可以创建匿名对象实现接口 `ReadOnlyProperty` 和 `ReadWriteProperty` 来实现属性委托。 930 | 931 | ```kotlin 932 | fun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty = 933 | object : ReadWriteProperty { 934 | var curValue = resource 935 | override fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValue 936 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) { 937 | curValue = value 938 | } 939 | } 940 | 941 | val readOnlyResource: Resource by resourceDelegate() 942 | var readWriteResource: Resource by resourceDelegate() 943 | ``` 944 | 945 | ### 延迟属性 946 | 947 | `lazy()` 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托。第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果。后续调用 get() 只是返回记录的结果。 948 | 949 | ```kotlin 950 | val lazyValue: String by lazy { 951 | println("computed!") 952 | "Hello" 953 | } 954 | 955 | fun main() { 956 | println(lazyValue) 957 | println(lazyValue) 958 | } 959 | ``` 960 | 961 | ### 可观察属性 962 | 963 | `Delegates.observable()` 接受两个参数:初始值与修改时的处理程序。每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值。 964 | 965 | ```kotlin 966 | import kotlin.properties.Delegates 967 | 968 | class User { 969 | var name: String by Delegates.observable("") { 970 | prop, old, new -> 971 | println("$old -> $new") 972 | } 973 | } 974 | 975 | fun main() { 976 | val user = User() 977 | user.name = "first" 978 | user.name = "second" 979 | } 980 | ``` 981 | 982 | 如果想截获赋值并否决它们,那么使用 `vetoable()` 取代 `observable()`。在属性被赋新值之前会调用传递给 `vetoable` 的处理程序。 983 | 984 | ### 委托给另一个属性 985 | 986 | 一个属性可以把它的 getter 与 setter 委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。该委托属性可以为: 987 | 988 | - 顶层属性 989 | - 同一个类的成员或扩展属性 990 | - 另一个类的成员或扩展属性 991 | 992 | 使用 `::` 限定符将一个属性委托给另一个属性。 993 | 994 | ```kotlin 995 | var topLevelInt: Int = 0 996 | class ClassWithDelegate(val anotherClassInt: Int) 997 | 998 | class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) { 999 | var delegatedToMember: Int by this::memberInt 1000 | var delegatedToTopLevel: Int by ::topLevelInt 1001 | 1002 | val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt 1003 | } 1004 | var MyClass.extDelegated: Int by ::topLevelInt 1005 | ``` 1006 | 1007 | 当要向后兼容的时候很有用,可以引入一个新的属性,使用 `@Deprecated` 注解标记旧的属性,并委托其实现。 1008 | 1009 | ### 将属性存储在映射中 1010 | 1011 | ```kotlin 1012 | class User(val map: Map) { 1013 | val name: String by map 1014 | val age: Int by map 1015 | } 1016 | ``` 1017 | 1018 | 代理属性使用字符串键从 map 中获取值。如果对 MutableMap 使用 var 也是可以的。 1019 | 1020 | ```kotlin 1021 | class User(val map: Map) { 1022 | val name: String by map 1023 | val age: Int by map 1024 | } 1025 | 1026 | fun main() { 1027 | val user = User(mapOf( 1028 | "name" to "wwtg99", 1029 | "age" to 25 1030 | )) 1031 | 1032 | println(user.name) // Prints "wwtg99" 1033 | println(user.age) // Prints 25 1034 | 1035 | } 1036 | ``` 1037 | 1038 | ### 局部委托属性 1039 | 1040 | 可以将局部变量声明为委托属性,可用于惰性初始化。 1041 | 1042 | ``` 1043 | fun example(computeFoo: () -> Foo) { 1044 | val memoizedFoo by lazy(computeFoo) 1045 | 1046 | if (someCondition && memoizedFoo.isValid()) { 1047 | memoizedFoo.doSomething() 1048 | } 1049 | } 1050 | ``` 1051 | 1052 | ## 类型别名 1053 | 1054 | 类型别名为现有类型提供替代名称。如果类型名称太长,可以另外引入较短的名称,非常适合用于范型类型。 1055 | 1056 | ```kotlin 1057 | typealias NodeSet = Set 1058 | 1059 | typealias FileTable = MutableMap> 1060 | ``` 1061 | 1062 | 也可以为函数类型提供别名。 1063 | 1064 | ```kotlin 1065 | typealias MyHandler = (Int, String, Any) -> Unit 1066 | 1067 | typealias Predicate = (T) -> Boolean 1068 | ``` 1069 | 1070 | 还可以为内部类或嵌套类提供别名。 1071 | 1072 | ```kotlin 1073 | class A { 1074 | inner class Inner 1075 | } 1076 | class B { 1077 | inner class Inner 1078 | } 1079 | 1080 | typealias AInner = A.Inner 1081 | typealias BInner = B.Inner 1082 | ``` 1083 | -------------------------------------------------------------------------------- /concepts/control_flow.md: -------------------------------------------------------------------------------- 1 | # 控制流程 2 | 3 | ## if 条件判断 4 | 5 | 在 Kotlin 中,if 可以是表达式,因此替代了三元操作符 `条件 ? 然后 : 否则`。 6 | 7 | ```kotlin 8 | var max = a 9 | if (a < b) max = b 10 | 11 | // 使用 else 12 | if (a > b) { 13 | max = a 14 | } else { 15 | max = b 16 | } 17 | 18 | // 作为表达式 19 | max = if (a > b) a else b 20 | 21 | // 在表达式中使用 else if 22 | val maxLimit = 1 23 | val maxOrLimit = if (maxLimit > a) maxLimit else if (a > b) a else b 24 | ``` 25 | 26 | if 表达式也可以是代码块,最后的表达式就是返回值。 27 | 28 | ```kotlin 29 | val max = if (a > b) { 30 | print("Choose a") 31 | a 32 | } else { 33 | print("Choose b") 34 | b 35 | } 36 | ``` 37 | 38 | > 注:在赋值表达式中 else 是必须的。 39 | 40 | ## 多分支判断 41 | 42 | 类似于 Java 的 `switch`, Kotlin 提供了更灵活的 `when`。 43 | 44 | ```kotlin 45 | when (x) { 46 | 1 -> print("x == 1") 47 | 2 -> print("x == 2") 48 | else -> { 49 | print("x is neither 1 nor 2") 50 | } 51 | } 52 | ``` 53 | 54 | `when` 将它的参数与所有的分支条件顺序比较,直到某个分支满足条件。 55 | 56 | `when` 也可以作为表达式使用,配合枚举类也非常简便 57 | 58 | ```kotlin 59 | enum class Bit { 60 | ZERO, ONE 61 | } 62 | 63 | val numericValue = when (getRandomBit()) { 64 | Bit.ZERO -> 0 65 | Bit.ONE -> 1 66 | // 'else' 这里 else 不是必须的,如果前面没有全部覆盖就必须提供 67 | } 68 | ``` 69 | 70 | 合并部分条件 71 | 72 | ```kotlin 73 | when (x) { 74 | 0, 1 -> print("x == 0 or x == 1") 75 | else -> print("otherwise") 76 | } 77 | ``` 78 | 79 | 可以使用任意表达式作为条件判断 80 | 81 | ```kotlin 82 | when (x) { 83 | s.toInt() -> print("s encodes x") 84 | else -> print("s does not encode x") 85 | } 86 | ``` 87 | 88 | 使用 `in` 或 `!in` 检测区间或者集合 89 | 90 | ```kotlin 91 | when (x) { 92 | in 1..10 -> print("x is in the range") 93 | in validNumbers -> print("x is valid") 94 | !in 10..20 -> print("x is outside the range") 95 | else -> print("none of the above") 96 | } 97 | ``` 98 | 99 | `when` 也可以用来取代 if-else 链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支 100 | 101 | ```kotlin 102 | when { 103 | x.isOdd() -> print("x is odd") 104 | y.isEven() -> print("y is even") 105 | else -> print("x+y is odd") 106 | } 107 | ``` 108 | 109 | 在条件块中绑定条件变量 110 | 111 | ```kotlin 112 | fun Request.getBody() = 113 | when (val response = executeRequest()) { 114 | is Success -> response.body 115 | is HttpError -> throw HttpException(response.status) 116 | } 117 | ``` 118 | 119 | ## For 循环 120 | 121 | For 循环类似于 foreach。 122 | 123 | ```kotlin 124 | for (item in collection) print(item) 125 | 126 | for (item: Int in ints) { 127 | // ... 128 | } 129 | ``` 130 | 131 | 支持区间表达式 132 | 133 | ```kotlin 134 | for (i in 1..3) { 135 | println(i) 136 | } 137 | 138 | for (i in 6 downTo 0 step 2) { 139 | println(i) 140 | } 141 | ``` 142 | 143 | 如果需要使用索引值,可以如下获取 144 | 145 | ```kotlin 146 | for (i in array.indices) { 147 | println(array[i]) 148 | } 149 | 150 | for ((index, value) in array.withIndex()) { 151 | println("the element at $index is $value") 152 | } 153 | ``` 154 | 155 | ## while 循环 156 | 157 | while 和 do while 执行循环直到不满足条件,while 先检查再执行,do while 先执行一次再检查。 158 | 159 | ```kotlin 160 | while (x > 0) { 161 | x-- 162 | } 163 | 164 | do { 165 | val y = retrieveData() 166 | } while (y != null) 167 | ``` 168 | 169 | ## 跳转 170 | 171 | Kotlin 也支持 break 和 continue 来跳出循环。同时还支持使用 `@` 来跳到指定的标签。 172 | 173 | ```kotlin 174 | loop@ for (i in 1..100) { 175 | for (j in 1..100) { 176 | if (...) break@loop 177 | } 178 | } 179 | ``` 180 | 181 | ## 异常 182 | 183 | Kotlin 的异常与 Java 类似,使用 `throw` 抛出异常,使用 `try catch finally` 来处理。 184 | 185 | ```kotlin 186 | try { 187 | // some code 188 | } catch (e: SomeException) { 189 | // handler 190 | } finally { 191 | // optional finally block 192 | } 193 | ``` 194 | 195 | 同时,`try` 可以是表达式。 196 | 197 | ```kotlin 198 | val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null } 199 | ``` 200 | -------------------------------------------------------------------------------- /concepts/functions.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | ## 函数声明 4 | 5 | Kotlin 中使用 `fun` 关键词声明函数。 6 | 7 | ```kotlin 8 | fun double(x: Int): Int { 9 | return 2 * x 10 | } 11 | ``` 12 | 13 | 如下方式调用,有多个参数时也可以指定参数名。 14 | 15 | ```kotlin 16 | val result = double(2) 17 | ``` 18 | 19 | ### 参数 20 | 21 | 函数参数使用 Pascal 表示法定义:`name: type`,参数用逗号隔开,每个参数必须有显式类型。 22 | 23 | ```kotlin 24 | fun powerOf(number: Int, exponent: Int): Int { /*...*/ } 25 | ``` 26 | 27 | 可以保留最后一个参数的尾部逗号。 28 | 29 | ### 默认参数 30 | 31 | 函数参数可以有默认值,这样可以减少重载方法。当覆盖一个有默认参数值的方法时,必须从签名中省略默认值。 32 | 33 | ```kotlin 34 | fun read( 35 | b: ByteArray, 36 | off: Int = 0, 37 | len: Int = b.size, 38 | ) { /*...*/ } 39 | ``` 40 | 41 | 如果一个有默认值参数在一个无默认值的参数之前,那么该默认值只能通过使用具名实参调用该函数来使用。 42 | 43 | ```kotlin 44 | fun foo( 45 | bar: Int = 0, 46 | baz: Int, 47 | ) { /*...*/ } 48 | 49 | foo(baz = 1) // 默认值 bar = 0 被使用 50 | ``` 51 | 52 | 如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为具名实参在括号内传入,也可以在括号外传入。 53 | 54 | ```kotlin 55 | fun foo( 56 | bar: Int = 0, 57 | baz: Int = 1, 58 | qux: () -> Unit, 59 | ) { /*...*/ } 60 | 61 | foo(1) { println("hello") } // 使用默认值 baz = 1 62 | foo(qux = { println("hello") }) // 使用默认值 bar = 0 和 baz = 1 63 | foo { println("hello") } // 使用默认值 bar = 0 和 baz = 1 64 | ``` 65 | 66 | ### 可变参数 67 | 68 | 使用 `vararg` 关键词指定可变参数。可以使用具名参数和 `*` 来展开参数。 69 | 70 | ```kotlin 71 | fun foo(vararg strings: String) { /*...*/ } 72 | 73 | foo(strings = *arrayOf("a", "b", "c")) 74 | ``` 75 | 76 | > 注:在 JVM 平台中调用 Java 函数时不能使用具名实参语法。 77 | 78 | ### 返回 Unit 79 | 80 | 如果一个函数并不返回有用的值,其返回类型是 Unit,类似于 Java 的 void。Unit 是一种只有一个值的类型。 81 | 82 | ```kotlin 83 | fun printHello(name: String?): Unit { 84 | if (name != null) 85 | println("Hello $name") 86 | else 87 | println("Hi there!") 88 | // `return Unit` 或 `return` 都是可选的 89 | } 90 | ``` 91 | 92 | Unit 的返回类型声明也是可选的,可以省略。 93 | 94 | ### 单表达式函数 95 | 96 | 当函数体由单个表达式构成时,可以省略花括号。 97 | 98 | ```kotlin 99 | fun double(x: Int): Int = x * 2 100 | ``` 101 | 102 | 返回值也可以通过类型推断而省略。 103 | 104 | > 注:对于有代码块的函数,返回类型显示声明是必须的,除非返回类型是 Unit。 105 | 106 | ### Infix 表示法 107 | 108 | 带有 `infix` 关键词的函数也可以使用 Infix 表示法(忽略该调用的点与圆括号)调用。但有如下要求: 109 | 110 | - 它们必须是成员函数或扩展函数。 111 | - 它们必须只有一个参数。 112 | - 其参数不得接受可变数量的参数且不能有默认值。 113 | 114 | ```kotlin 115 | infix fun Int.shl(x: Int): Int { ... } 116 | 117 | // infix 表示法 118 | 1 shl 2 119 | 120 | // 和下面的写法一样 121 | 1.shl(2) 122 | ``` 123 | 124 | > Infix 函数调用的优先级低于算术操作符、类型转换以及 rangeTo 操作符,但高于布尔运算符 && 和 ||、is- 与 in- 检测。 125 | 126 | ### 函数作用域 127 | 128 | Kotlin 的函数可以在文件顶层声明,也可以声明在局部作用域,作为成员函数以及扩展函数。 129 | 130 | #### 局部函数 131 | 132 | Kotlin 支持局部函数,即一个函数在另一个函数内部。 133 | 134 | ```kotlin 135 | fun dfs(graph: Graph) { 136 | fun dfs(current: Vertex, visited: MutableSet) { 137 | if (!visited.add(current)) return 138 | for (v in current.neighbors) 139 | dfs(v, visited) 140 | } 141 | 142 | dfs(graph.vertices[0], HashSet()) 143 | } 144 | ``` 145 | 146 | 局部函数可以访问外部函数(闭包)的局部变量。 147 | 148 | #### 成员函数 149 | 150 | 成员函数是在类或对象内部定义的函数。 151 | 152 | ```kotlin 153 | class Sample { 154 | fun foo() { print("Foo") } 155 | } 156 | ``` 157 | 158 | ### 范型函数 159 | 160 | 函数可以有范型参数,与 Java 类似。 161 | 162 | ```kotlin 163 | fun singletonList(item: T): List { /*……*/ } 164 | ``` 165 | 166 | ### 尾递归函数 167 | 168 | Kotlin 支持一种称为尾递归的函数式编程风格。对于某些使用循环的逻辑,可以使用尾递归替代而不会有堆栈溢出的风险。当一个函数用 `tailrec` 关键词标记并满足所需的条件时,编译器会优化该递归,生成一个快速而高效的基于循环的版本。 169 | 170 | ```kotlin 171 | val eps = 1E-10 172 | 173 | tailrec fun findFixPoint(x: Double = 1.0): Double = 174 | if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x)) 175 | ``` 176 | 177 | 这段代码计算余弦的不动点(fixpoint of cosine),这是一个数学常数。最终的代码会生成以下这种传统的循环。 178 | 179 | ```kotlin 180 | val eps = 1E-10 181 | 182 | private fun findFixPoint(): Double { 183 | var x = 1.0 184 | while (true) { 185 | val y = Math.cos(x) 186 | if (Math.abs(x - y) < eps) return x 187 | x = Math.cos(x) 188 | } 189 | } 190 | ``` 191 | 192 | 函数需要满足如下条件: 193 | 194 | - 递归调用(调用函数自身)必须是它执行的最后一个操作。 195 | - 不能用在 try/catch/finally 块中,也不能用于 open 的函数。 196 | 197 | ## 高阶函数和 Lambda 表达式 198 | 199 | Kotlin 中的函数是“一等公民”,也就是它们可以存储在变量或数据结构中,且可以用于函数参数以及返回。 200 | 201 | ### 高阶函数 202 | 203 | 高阶函数是用函数作为参数或返回值的函数。 204 | 205 | ```kotlin 206 | fun Collection.fold( 207 | initial: R, 208 | combine: (acc: R, nextElement: T) -> R 209 | ): R { 210 | var accumulator: R = initial 211 | for (element: T in this) { 212 | accumulator = combine(accumulator, element) 213 | } 214 | return accumulator 215 | } 216 | ``` 217 | 218 | 上述代码中,函数的参数 combine 具有函数类型 `(R, T) -> R`,而这个参数可以使用 lambda 表达式传参。 219 | 220 | ```kotlin 221 | fun main() { 222 | //sampleStart 223 | val items = listOf(1, 2, 3, 4, 5) 224 | 225 | // lambda 表达式是花括号括起来的代码块 226 | items.fold(0, { 227 | // 如果一个 lambda 表达式有参数,-> 前面是参数 228 | acc: Int, i: Int -> 229 | print("acc = $acc, i = $i, ") 230 | val result = acc + i 231 | println("result = $result") 232 | // lambda 表达式中的最后一个表达式的值是返回值 233 | result 234 | }) 235 | 236 | // lambda 表达式的参数类型如果可以推断出来的话是可选的 237 | val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i }) 238 | 239 | // 函数引用也可以用于高阶函数调用 240 | val product = items.fold(1, Int::times) 241 | //sampleEnd 242 | println("joinedToString = $joinedToString") 243 | println("product = $product") 244 | } 245 | ``` 246 | 247 | ### 函数类型 248 | 249 | Kotlin 使用类似 `(Int) -> String` 的表示法来声明函数类型。参数类型列表可以为空,如 `() -> A`,Unit 返回类型不可省略。 250 | 251 | 函数类型可以有一个额外的接收者类型(也就是[扩展函数](./classes_objects.md#扩展函数)),在表示法的点之前指定,如类型 `A.(B) -> C` 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 252 | 253 | 如果要指定一个可为 null 的函数类型,可以这样表示 `((Int, Int) -> Int)?`。 254 | 255 | ### Lambda 表达式 256 | 257 | Lambda 表达式的完整语法如下所示: 258 | 259 | ```kotlin 260 | val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y } 261 | ``` 262 | 263 | - lambda 表达式总是括在花括号中。 264 | - 完整语法形式的参数声明放在花括号内,并有可选的类型标注。 265 | - 函数体跟在 `->` 后。 266 | - 如果该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个表达式的值会视为返回值。 267 | 268 | 如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外,这种语法也称为拖尾 lambda 表达式。。 269 | 270 | ```kotlin 271 | val product = items.fold(1) { acc, e -> acc * e } 272 | ``` 273 | 274 | #### it:单参数的隐式名称 275 | 276 | 如果该 lambda 表达式是唯一的参数,那么圆括号也可以省略。 277 | 278 | ```kotlin 279 | run { println("...") } 280 | ``` 281 | 282 | 如果 lambda 表达式只有一个参数,该参数和 `->` 也可以省略,会隐式使用 `it` 作为参数。 283 | 284 | ```kotlin 285 | ints.filter { it > 0 } // 这个函数是 (it: Int) -> Boolean 类型 286 | ``` 287 | 288 | 如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称。 289 | 290 | ```kotlin 291 | map.forEach { (_, value) -> println("$value!") } 292 | ``` 293 | 294 | ### 匿名函数 295 | 296 | lambda 表达式中没有指定返回值的类型,这可以推断出来。但如果需要显示指定,可以使用匿名函数。 297 | 298 | ```kotlin 299 | fun(x: Int, y: Int): Int = x + y 300 | ``` 301 | 302 | 匿名函数类似于普通函数声明,只不过没有函数名。函数体可以是表达式也可以是代码块(花括号包裹)。 303 | 304 | ### 闭包 305 | 306 | Lambda 表达式或者匿名函数(包括局部函数和对象表达式)可以访问其闭包,其中包含在外部作用域中声明的变量。 307 | 308 | ```kotlin 309 | var sum = 0 310 | ints.filter { it > 0 }.forEach { 311 | sum += it 312 | } 313 | print(sum) 314 | ``` 315 | 316 | ## 内联函数 317 | 318 | 使用高阶函数会带来一些运行时的效率损失,每一个函数都是一个对象,并且会捕获一个闭包。在许多情况下通过内联化 lambda 表达式可以消除这类的开销。 319 | 320 | 例如 `lock()` 函数: 321 | 322 | ```kotlin 323 | lock(l) { foo() } 324 | ``` 325 | 326 | 编译器会生成如下代码: 327 | 328 | ```kotlin 329 | l.lock() 330 | try { 331 | foo() 332 | } finally { 333 | l.unlock() 334 | } 335 | ``` 336 | 337 | 如果要这么做,使用 `inline` 关键词修饰函数。 338 | 339 | ```kotlin 340 | inline fun lock(lock: Lock, body: () -> T): T { …… } 341 | ``` 342 | 343 | `inline` 关键词影响函数本身和传递给它的 lambda 表达式,它们将被内联到调用处。内联可能导致生成的代码量增加。不过如果使用得当(避免内联过大函数),性能上会有所提升。 344 | 345 | ### 非内联 346 | 347 | 如果不希望内联所有传递给内联函数的 lambda 表达式,那么可以用 `noinline` 关键词修饰不希望内联的函数参数。 348 | 349 | ```kotlin 350 | inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... } 351 | ``` 352 | 353 | ## 操作符重载 354 | 355 | Kotlin 中可以为类型提供预定义的一组操作符的自定义实现,如 +,\*,使用 `operator` 关键词修饰对应的方法,提供对应的参数声明,当覆盖方法时可省略 `operator`。 356 | 357 | ```kotlin 358 | interface IndexedContainer { 359 | operator fun get(index: Int) 360 | } 361 | ``` 362 | 363 | ### 一元操作 364 | 365 | | 表达式 | 对应函数 | 366 | | ------ | -------------- | 367 | | +a | a.unaryPlus() | 368 | | -a | a.unaryMinus() | 369 | | !a | a.not() | 370 | | a++ | a.inc() | 371 | | a-- | a.dec() | 372 | 373 | `inc()` 和 `dec()` 函数必须返回一个值,它用于赋值给使用 `++` 或 `--` 操作的变量。它们不应该改变在其上调用 `inc()` 或 `dec()` 的对象。 374 | 375 | 例: 376 | 377 | ```kotlin 378 | data class Point(val x: Int, val y: Int) 379 | 380 | operator fun Point.unaryMinus() = Point(-x, -y) 381 | 382 | val point = Point(10, 20) 383 | 384 | fun main() { 385 | println(-point) // 输出 Point(x=-10, y=-20) 386 | } 387 | ``` 388 | 389 | ### 二元操作 390 | 391 | #### 算术运算符 392 | 393 | | 表达式 | 对应函数 | 394 | | ------ | --------------- | 395 | | a + b | a.plus(b) | 396 | | a - b | a.minus(b) | 397 | | a \* b | a.times(b) | 398 | | a / b | a.div(b) | 399 | | a % b | a.rem(b) | 400 | | a..b | a.rangeTo(b) | 401 | | a.. 注:=== 和 !== 不可重载,判断两个引用是否指向同一对象 448 | 449 | #### 比较操作符 450 | 451 | | 表达式 | 对应函数 | 452 | | ------ | ------------------- | 453 | | a > b | a.compareTo(b) > 0 | 454 | | a < b | a.compareTo(b) < 0 | 455 | | a >= b | a.compareTo(b) >= 0 | 456 | | a <= b | a.compareTo(b) <= 0 | 457 | 458 | 所有的比较都转换为对 `compareTo` 函数的调用,这个函数需要返回 Int 类型。 459 | -------------------------------------------------------------------------------- /concepts/null.md: -------------------------------------------------------------------------------- 1 | # 空安全 2 | 3 | 在 Kotlin 中,类型系统区分一个引用可以容纳 null (可空引用)还是不能容纳(非空引用)。 例如,String 类型的常规变量不能容纳 null。如果要允许为空,可以声明一个变量为可空字符串(写作 String?)。 4 | 5 | 例: 6 | 7 | ```kotlin 8 | var a: String = "abc" // 不可以为 null 9 | var b: String? = "abc" // 可以为 null 10 | ``` 11 | 12 | ## 判断 null 13 | 14 | 使用 `if` 判断检测 15 | 16 | ```kotlin 17 | // 获取 b 的长度,b 为 null 则取 -1 18 | val l = if (b != null) b.length else -1 19 | ``` 20 | 21 | 简化的安全调用 22 | 23 | ```kotlin 24 | val b: String? = null 25 | println(b?.length) 26 | ``` 27 | 28 | 如果 b 非空,就返回 b.length,否则返回 null,这个表达式的结果类型是 Int?。 29 | 30 | 更长的链式调用 31 | 32 | ```kotlin 33 | staff?.department?.head?.name 34 | ``` 35 | 36 | 如果任意一个属性(环节)为 null,这个链式调用就会返回 null。 37 | 38 | ## 对非空操作 39 | 40 | 如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用。 41 | 42 | ```kotlin 43 | val listWithNulls: List = listOf("Kotlin", null) 44 | for (item in listWithNulls) { 45 | item?.let { println(it) } // 输出 Kotlin 并忽略 null 46 | } 47 | ``` 48 | 49 | 安全调用也可以出现在赋值的左侧。这样,如果调用链中的任何一个接收者为 null 都会跳过赋值,而右侧的表达式根本不会求值。 50 | 51 | ```kotlin 52 | person?.department?.head = managersPool.getManager() 53 | ``` 54 | 55 | 如果 `person` 或者 `person.department` 其中之一为空,都不会调用该函数。 56 | 57 | ## Elvis 操作符 58 | 59 | 类似于 Java 的三元操作符。 60 | 61 | Java 中: 62 | 63 | ```java 64 | String a = null; 65 | String str = a != null ? a : ""; 66 | ``` 67 | 68 | Kotlin 中这样写: 69 | 70 | ```kotlin 71 | val a: String? = null 72 | val str = if (a != null) a else "" 73 | ``` 74 | 75 | 也可以使用 `?:` 操作符: 76 | 77 | ```kotlin 78 | val str = a ?: "" 79 | ``` 80 | 81 | 如果 `?:` 左侧表达式不是 null,Elvis 操作符就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为 null 时,才会对右侧表达式求值。 82 | 83 | 和 return 或 throw 组合很方便: 84 | 85 | ```kotlin 86 | fun foo(node: Node): String? { 87 | val parent = node.getParent() ?: return null 88 | val name = node.getName() ?: throw IllegalArgumentException("name expected") 89 | } 90 | ``` 91 | 92 | ## !! 操作符 93 | 94 | 非空断言运算符`(!!)`将任何值转换为非空类型,若该值为 null 则抛出异常。 95 | 96 | ```kotlin 97 | val l = b!!.length 98 | ``` 99 | 100 | ## 安全的类型转换 101 | 102 | 如果类型尝试转换不成功则返回 null,而不是抛出异常。 103 | 104 | ```kotlin 105 | val aInt: Int? = a as? Int 106 | ``` 107 | 108 | ## 可空类型的集合 109 | 110 | 对于可空类型的集合,如果要获取所有非空的元素,可以使用 `filterNotNull` 方法。 111 | 112 | ```kotlin 113 | val nullableList: List = listOf(1, 2, null, 4) 114 | val intList: List = nullableList.filterNotNull() 115 | ``` 116 | -------------------------------------------------------------------------------- /concepts/packages_imports.md: -------------------------------------------------------------------------------- 1 | # 包和导入 2 | 3 | Kotlin 包和导入与 Java 一样,源文件头部声明包。 4 | 5 | ```kotlin 6 | package org.example 7 | 8 | fun printMessage() { /*...*/ } 9 | class Message { /*...*/ } 10 | ``` 11 | 12 | 其中,函数和属性可以直接在包中声明,如果没有指定包,则默认从属于默认没有名字的包。 13 | 14 | ## 默认导入 15 | 16 | 任何 Kotlin 文件都默认导入了以下一些包: 17 | 18 | - [kotlin.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html) 19 | - [kotlin.annotation.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.annotation/index.html) 20 | - [kotlin.collections.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html) 21 | - [kotlin.comparisons.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.comparisons/index.html) 22 | - [kotlin.io.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/index.html) 23 | - [kotlin.ranges.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/index.html) 24 | - [kotlin.sequences.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html) 25 | - [kotlin.text.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/index.html) 26 | 27 | 另外一些包根据平台导入: 28 | 29 | JVM: 30 | 31 | - java.lang.\* 32 | - [kotlin.jvm.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/index.html) 33 | 34 | JS: 35 | 36 | - [kotlin.js.\*](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/index.html) 37 | 38 | ## 导入 39 | 40 | 导入和 Java 一样 41 | 42 | ```kotlin 43 | import org.example.Message 44 | import org.example.* 45 | import org.test.Message as TestMessage 46 | ``` 47 | 48 | import 不仅可以导入类,还可以导入: 49 | 50 | - 顶层的函数和属性 51 | - 在对象声明中的函数和属性 52 | - 枚举常量 53 | -------------------------------------------------------------------------------- /concepts/types.md: -------------------------------------------------------------------------------- 1 | # 类型 2 | 3 | > Kotlin 中所有的类型都是对象。 4 | 5 | ## 基本类型 6 | 7 | ### 数值类型 8 | 9 | #### 整型 10 | 11 | | 类型 | 大小 | 范围 | 12 | | ----- | ----- | ------------------------------------------------------- | 13 | | Byte | 8 位 | -128 ~ 127 | 14 | | Short | 16 位 | -32768 ~ 32767 | 15 | | Int | 32 位 | -2,147,483,648 ~ 2,147,483,647 | 16 | | Long | 64 位 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 17 | 18 | 例: 19 | 20 | - 十进制(自动推断,在 Int 范围内则为 Int,否则为 Long):123 21 | - 强制指定长整型用 `L` 标记:123L 22 | - 十六进制:0x0F 23 | - 二进制:0b00001011 24 | 25 | #### 无符号整型 26 | 27 | | 类型 | 大小 | 范围 | 28 | | ------ | ----- | ------------------------------ | 29 | | UByte | 8 位 | 0 ~ 255 | 30 | | UShort | 16 位 | 0 ~ 65,535 | 31 | | UInt | 32 位 | 0 ~ 4,294,967,295 | 32 | | ULong | 64 位 | 0 ~ 18,446,744,073,709,551,615 | 33 | 34 | 例: 35 | 使用 `u` 或 `U` 标记 36 | 37 | ``` 38 | val a1 = 42u // UInt:未提供预期类型,常量适于 UInt 39 | val a2 = 1UL // ULong,即使未提供预期类型并且常量适于 UInt 40 | ``` 41 | 42 | #### 浮点型 43 | 44 | - Float: 32 位 45 | - Double: 64 位 46 | 47 | 例: 48 | 默认推断为 Double,显式指定为 Flout 使用 f 或 F 结尾。 49 | 50 | ```kotlin 51 | val pi = 3.14 // Double 52 | val d1 = 1.0 // Double 53 | val f1 = 1.1f // Float 54 | ``` 55 | 56 | > 注:具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用,需要显式类型转换。 57 | 58 | #### 字面常量 59 | 60 | - 十进制:123 61 | - 长整型用 `L` 标记:123L 62 | - 十六进制:0x0F 63 | - 二进制:0b00001011 64 | - 浮点数(默认 Double):123.5 65 | - 单精度(Float)用 `f` 或 `F` 标记:123.5f 66 | 67 | Kotlin 不支持八进制。 68 | 69 | 过长可用下划线分隔,如: 70 | 71 | ```kotlin 72 | val oneMillion = 1_000_000 73 | val creditCardNumber = 1234_5678_9012_3456L 74 | val socialSecurityNumber = 999_99_9999L 75 | val hexBytes = 0xFF_EC_DE_5E 76 | val bytes = 0b11010010_01101001_10010100_10010010 77 | ``` 78 | 79 | #### 显式数值类型转换 80 | 81 | 所有数字类型都支持转换为其他类型: 82 | 83 | - toByte(): Byte 84 | - toShort(): Short 85 | - toInt(): Int 86 | - toLong(): Long 87 | - toFloat(): Float 88 | - toDouble(): Double 89 | 90 | #### 数学运算 91 | 92 | Kotlin 支持标准数学运算符:`+`, `-`, `*`, `/`, `%`。 93 | 94 | 可以通过操作符重载自定义覆盖默认行为。 95 | 96 | > 注:整数除法总是返回整数,若要返回浮点数,请将其中一个数转换为浮点数。 97 | 98 | #### 位运算 99 | 100 | Kotlin 对整数提供位运算。 101 | 102 | 这是完整的位运算列表: 103 | 104 | - shl(bits) – 有符号左移 105 | - shr(bits) – 有符号右移 106 | - ushr(bits) – 无符号右移 107 | - and(bits) – 位与 108 | - or(bits) – 位或 109 | - xor(bits) – 位异或 110 | - inv() – 位非 111 | 112 | ### 布尔型 113 | 114 | Boolean: true 或 false 115 | 116 | #### 逻辑运算 117 | 118 | 布尔值的内置运算有: 119 | 120 | - ||:逻辑或 121 | - &&:逻辑与 122 | - !:逻辑非 123 | 124 | ### 字符 125 | 126 | Char 单引号包裹,如 `'a'` 127 | 128 | 支持 `\` 开头的转义字符: 129 | 130 | - \t:制表符 131 | - \b:退格符 132 | - \n:换行(LF) 133 | - \r:回车(CR) 134 | - \':单引号 135 | - \":双引号 136 | - \\:反斜杠 137 | - \$:美元符 138 | 139 | 支持 Unicode 转义序列语法,如:'\uFF00'。 140 | 141 | ### 字符串 142 | 143 | String 双引号包裹,如 `"abc"` 144 | 145 | > Kotlin 中的字符串也是不可变的。 146 | 147 | #### 双引号包裹的字符序列 148 | 149 | - 可以使用索引运算访问字符:`s[i]` 150 | - 支持转义字符,如 \n 151 | - 支持字符串模版(使用 `$` 或 `${}`) 152 | 153 | ```kotlin 154 | val s = "abc" 155 | println("$s.length is ${s.length}") 156 | ``` 157 | 158 | #### 使用三个双引号包裹多行字符串 159 | 160 | ```kotlin 161 | val text = """ 162 | for (c in "foo") 163 | print(c) 164 | """ 165 | ``` 166 | 167 | - 不支持转义字符 168 | - 支持模版,模版中输出 `$` 用 `${'$'}_9.99` 169 | - 使用 `trimMargin()` 可删除多余的前导空格(默认 | 作为边界前缀) 170 | 171 | ```kotlin 172 | val text = """ 173 | |Tell me and I forget. 174 | |Teach me and I remember. 175 | |Involve me and I learn. 176 | |(Benjamin Franklin) 177 | """.trimMargin() 178 | ``` 179 | 180 | ### 数组 181 | 182 | 保存固定数量相同类型的数据结构。 183 | 184 | #### 创建数组 185 | 186 | 可以使用函数 `arrayOf()`, `arrayOfNulls()` 或者 `emptyArray()` 创建数组,也使用使用 Array 构造函数创建。 187 | 188 | 例: 189 | 190 | ```kotlin 191 | val simpleArray = arrayOf(1, 2, 3) 192 | val nullArray: Array = arrayOfNulls(3) // 创建长度为 3 都是 null 的数组 193 | var exampleArray = emptyArray() // 创建空数组 194 | val asc = Array(5) { i -> (i * i).toString() } // 创建长度为 5,且元素根据索引为参数的函数创建 195 | ``` 196 | 197 | #### 嵌套数组(多维数组) 198 | 199 | 嵌套数组的元素同时也是数组。 200 | 201 | ```kotlin 202 | val twoDArray = Array(2) { Array(2) { 0 } } 203 | ``` 204 | 205 | #### 访问与修改元素 206 | 207 | 使用 `[]` 索引操作符访问与修改元素。 208 | 209 | ```kotlin 210 | val simpleArray = arrayOf(1, 2, 3) 211 | val twoDArray = Array(2) { Array(2) { 0 } } 212 | 213 | // 访问并修改元素 214 | simpleArray[0] = 10 215 | twoDArray[0][0] = 2 216 | 217 | // 打印元素 218 | println(simpleArray[0].toString()) // 10 219 | println(twoDArray[0][0].toString()) // 2 220 | ``` 221 | --------------------------------------------------------------------------------