说明
本文知识源:
- kotlin 官方文档
- 中文文档
- 强烈建议直接官方文档,中文文档应该是机器翻译的,很多不好理解的地方。
一、数据结构
数据类型
- 类型和Java基本一致
- 不声明的话,数字量根据数值大小默认为 Int 或者 Long
- 无符号数,数字后面加u表示
- 没有基本类型,包装类型之分
数据转换
- 不能隐式转换,如int -> long,float -> double,都必须显示声明
- Char 不能直接当数字, ‘a’.toInt()
字符串
更加好用的字符串
- 一段字符串
var str = '''
123
456
789
'''
- 换行字符串
|
| - 字符串模板
var str = "123"
println("$str.length:{$str.length}")
二、控制流
三元运算
var a = if (a > 10) 10 else 0;
when 语句
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")
}
for 循环
withIndex() 给出索引值
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
return返回
直接返回到最外层函数
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println("this point is unreachable")
}
内部函数返回return语句
fun foo() {
listOf(1, 2, 3, 4, 5).forEach myfun{
if (it == 3) return@myfun // 非局部直接返回到 foo() 的调用者
print(it)
}
println("this point is reachable")
}
三、类与对象
构造函数
- 主构造函数:类名constructor 次级构造函数:类中方法
class Person constructor(name:String){
constructor(fullName:String):this(fullName.toUpperCase()){
println('次级构造函数')
}
}
- 在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名后。
- 使用init初始化代码块
- init块中的代码实际上会成为主构造函数的一部分
- 主构造函数会作为次构造函数的第一条语句(因为委托的关系)
面向对象
类相关
- 超类 Any
- open 代表可以被覆写,被继承,override 显示代表覆写,继承
- 在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer
- 多继承时,为了表示采用从哪个超类型继承的实现,使用super<超类型名>,如 super
- 伴生对象: 用来声明类似Java的静态成员或方法
成员属性
- 标准声明
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
- get 或 set
var setterVisibility: String = "abc"
private set // 此 setter 是私有的并且有默认实现
var setterWithAnnotation: Any? = null
@Inject set // 用 Inject 注解此 setter
- set方法内,不能直接调用该字段,否则会栈溢出,使用幕后字段field,来代表改成员变量
var counter = 0 // 注意:这个初始器直接为幕后字段赋值
set(value) {
if (value >= 0) field = value
}
- 编译期常量 const
object 或 companion object 类中的属性,基本类型 - 延迟属性 lateinit
- 外部类不能访问内部类的 private 成员。
接口
- 接口无法保存状态,它可以有属性但必须声明为抽象或提供访问器实现
- 接口的方法可以仅仅只声明,也可有实现
扩展函数
- 相当于静态方法
- 可用this指代方法的调用者
- 调用时,根据调用者的声明类型来调用指定的函数
- 如果一个类定义有一个成员函数与一个扩展函数,而这两个函数其他都相同,这种情况总是取成员函数
扩展属性
val <T> List<T>.lastIndex: Int
get() = size - 1
特殊类
// 数据类:用来存储数据,编译器自动编写 toString equals hashCode 方法
data Person(var name:String)
// 密封类:当一个值为有限几种的类型、而不能有任何其他类型。相当于是增强型枚举
sealed class Expr
// 匿名内部类:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
// 枚举类
enum class RGB { RED, GREEN, BLUE }
// 枚举类默认的属性:名称及索引
//val name: String
//val ordinal: Int
泛型
设计原因:Java 泛型的缺陷,Java泛型是不型变,传递过程中会出现问题。
List a --> List b 没有继承关系
声明处型变 out
生成返回的类型,而从不被消费,一般用于函数返回值的类型设定
类型参数逆变 in
只可以被消费而不可以被生产,一般用于函数参数
特别的泛型*
对于声明的类Function <in T, out U>,有如下解释:
- Function<*, String> 表示 Function<in Nothing, String>;
- Function<Int, *> 表示 Function<Int, out Any?>;
- Function<*, *> 表示 Function<in Nothing, out Any?>
泛型声明 reified
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
treeNode.findParentOfType<MyTreeNode>()
对象
typealias 类型别名
内联类
- 主构造器仅有一个基本属性的类
- 内联类不能含有 init 代码块
- 内联类不能含有幕后字段
- 无法继承其他类,能实现接口
委托
类的委托:
- 用来帮助实现继承中一些方便的覆写。但是本身的覆写会优先于委托的覆写
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
委托属性
用处:
- 延迟加载
- 可观察属性
- 属性映射
// 延迟属性
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
// 可观察属性
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
// 属性映射
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
局部变量也可以使用委托:
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
四、函数
相关关键字
- 可变参数:vararg
- 中缀函数: infix
- 尾递归函数: tailrec
匿名函数
fun(x: Int, y: Int): Int = x + y
带有接收者的函数
val sum: Int.(Int) -> Int = { other -> plus(other) }
// 很强大的应用 DSL风格的函数调用
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 创建接收者对象
html.init() // 将该接收者对象传给该 lambda
return html
}
html { // 带接收者的 lambda 由此开始
body() // 调用该接收者对象的一个方法
}
内联函数 inline
出现原因:性能考虑,对于函数对象和类的内存分配和虚拟调用会引入运行时间开销
特性:
- lambda 表达式传给的函数如果是内联的,则可以局部函数内return
禁用内联 noinline
声明内联函数的函数类型参数,禁止传入的函数是内联的
crossinline 函数参数禁止在局部返回
声明内联函数的函数类型参数,禁止传入的函数内部局部返回return