kotlin关键字
- abstract 抽象声明,被标注对象默认是open
- annotation 注解声明
- by 类委托、属性委托
- class 声明类
- companion 伴生对象声明
- const 声明编译期常量
- constructor 声明构造函数
- data 数据类,声明的类默认实现equals()/hashCode()/toString/copy()/componentN()
- enum 声明枚举类
- field 属性的幕后字段
- fun 声明函数
- import 导入
- in 修饰类型参数,使其逆变:只可以被消费而不可以被生产
- init 初始化块;相当于主构造函数的方法体
- inner 标记嵌套类,使其成为内部类:可访问外部类的成员
- interface 声明接口
- internal 可见性修饰符,相同模块内可见
- lateinit 延迟初始化,避免空检查
- object 对象表达式、对象声明
- open 允许其它类继承;kotlin类默认都是final,禁止继承
- out 修饰类型参数,使其协变:只可以被生产而不可以被消费
- override 标注复写的方法、属性
- package 包声明
- private 可见性修饰符,文件内可见
- protected 可见性声明,只修饰类成员,子类中可见
- public kotlin默认的可见性修饰符,随处可见
- super 访问超类的方法、属性
- throw 抛异常
- val 声明只读属性
- var 声明可变属性
- vararg 修饰函数参数:声明为可变数量参数
- inline 声明内联函数
- noinline 禁用内联,标记内联函数不需要内联的参数
- crossinline 标记内联函数的lambda表达式参数,标识该lambda函数返回为非局部返回,不允许非局部控制流
- operator 标记重载操作符的函数
- suspend 声明挂起函数,该函数只能从协程和其他挂起函数中调用
- reified 限定类型参数,需要配合inline关键字使用
- sealed 声明密封类,功能类似枚举
- typealias 声明类型别名
inline noinline crossinline
inline
- inline 的工作原理就是将内联函数的函数体复制到调用处实现内联。
1 2 3 4 5 6 7
inline fun inlined(getString: () -> String?) = println(getString()) fun notInlined(getString: () -> String?) = println(getString()) fun test() { var testVar = "Test" notInlined { testVar } inlined { testVar } }
反编译成Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public static final void test() { final ObjectRef testVar = new ObjectRef(); testVar.element = "Test Variable"; // notInlined: notInlined((Function0)(new Function0(0) { public Object invoke() { return this.invoke(); } @NotNull public final String invoke() { return (String)testVar.element; } })); // inlined: String var3 = (String)testVar.element; System.out.println(var3); }
-
不应该内联所有功能。而且官方也不建议这样做。Kotlin 贡献者的建议,原文是:「Functions should only be made inline when they use inline-only features like inlined lambda parameters or reified types.」意思就是说:inline 关键字应该只用在需要内联特性的函数中,比如高阶函数作为参数和具体化的类型参数时。
- 为什么高阶函数为参数时推荐使用 inline 关键字,因为用 inline修饰的方法,不止本方法会内联,如果参数为函数也会内联。就相当于至少减少了两次方法数调用 ?
noinline
我们的内联函数中有多个lambda参数时,如果我们想要其中某一个lambda参数不进行内联,则可以使用noinline关键字。
1 2 3 4 5 6 7 8 9 10 11 12
fun main() { myMethod(former = { println("former") }, latter = { println("latter") }) } private inline fun myMethod(former: () -> Unit, noinline latter: () -> Unit) { former() latter() }
在上述情况下 latter 会被编译成一个Function 而不是直接将实现代码copy到调用处
crossinline
在使用inline函数时,有一个注意点:小心地使用流程跳转。示例代码如下: ```kotlin fun main() { println(“start execution:”) sayHello { println(“in lambda”) return } println(“end execution”) }
private inline fun sayHello(block: () -> Unit) { println(“in sayHello”) block() } // 输出结果为: // start execution: // in sayHello // in lambda
1
2
3
4
5
6
可以看出,最后的"end execution"并没有输出在控制台,那是因为程序在执行sayHello时,由于inline函数,导致return语句直接结束了main方法。我们如果想在sayHello定义时,就不想以后出现这样的使用隐患,就可以使用crossinline关键字:
```kotlin
private inline fun sayHello(crossinline block: () -> Unit) {
println("in sayHello")
block()
}
在加上了 crossinline 关键字之后,return 语句那块就已经显示语法错误了。
operator
- 运算符重载就是对已有的运算符赋予他们新的含义。重载的修饰符是operator
- 运算符重载实际上是函数重载,本质上是对运算符函数的调用,从运算符到对应函数的映射过程由编译器完成。
- 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
data class Person(var name: String, var age: Int){ operator fun plus(other:Person):Person{ return Person(this.name+"+"+other.name,this.age+other.age) } } fun main() { var person1=Person("A",3) var person2=Person("B",4) var person3=person1+person2 println("person3=$person3") } //输出结果 person3=Person(name=A+B, age=7)
- 我的理解:对 kotlin 具有特殊意义的一些函数进行函数重载需要添加 operator 比如 invoke 、 plus 等
reified
Kotlin-reified 官方文档
这个是为了满足inline特性而设计的语法糖,因为给函数使用内联之后,编译器会用其函数体来替换掉函数调用,而如果该函数里面有泛型就可能会出现编译器不懂该泛型的问题,所以引入reified,使该泛型被智能替换成对应的类型
sealed
声明密封类,功能类似枚举,是对 enum 类的一种扩展
实战举例
举个例子,假如在 Android 中我们有一个 view,我们现在想通过 when 语句设置针对 view 进行两种操作:显示和隐藏,那么就可以这样做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
}
以上功能其实完全可以用枚举实现,但是如果我们现在想加两个操作:水平平移和纵向平移,并且还要携带一些数据,比如平移了多少距离,平移过程的动画类型等数据,用枚举显然就不太好办了,这时密封类的优势就可以发挥了,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px // 这个 when 语句分支不仅告诉 view 要水平移动,还告诉 view 需要移动多少距离,这是枚举等 Java 传统思想不容易实现的
is UiOp.TranslateY -> view.translationY = op.px
}
typealias
- typealias关键字的作用就是将一个类映射到另一个类上面,或者可以说是给一个类起个别名。
- typealias关键字不会创建一个新的类型,只是给该类取了一个别名,编译运行的时候还会还原成原来的类型。
- typealias的映射类只能定义在Kotlin文件内,而不能定义在类中或者方法中。
- 对于Kotlin中映射类型,在Java中是无法调用的,我们只能调用它的原始类型来和映射类型进行交互。
实战举例
1
2
3
4
5
6
7
8
9
public typealias StringTypealias = String
val nameT: StringTypealias = "ZhangSan"
val nameS: String = "ZhangSan"
fun main() {
println("nameT==nameS : ${nameT == nameS}")
}
// 输出结果:
// nameT==nameS : true
应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Classes and Interfaces (类和接口)
typealias RegularExpression = String
typealias IntentData = Parcelable
// Nullable types (可空类型)
typealias MaybeString = String?
// Generics with Type Parameters (类型参数泛型)
typealias MultivaluedMap<K, V> = HashMap<K, List<V>>
typealias Lookup<T> = HashMap<T, T>
// Generics with Concrete Type Arguments (混合类型参数泛型)
typealias Users = ArrayList<User>
// Type Projections (类型投影)
typealias Strings = Array<out String>
typealias OutArray<T> = Array<out T>
typealias AnyIterable = Iterable<*>
// Objects (including Companion Objects) (对象,包括伴生对象)
typealias RegexUtil = Regex.Companion
// Function Types (函数类型)
typealias ClickHandler = (View) -> Unit
// Lambda with Receiver (带接收者的Lambda)
typealias IntentInitializer = Intent.() -> Unit
// Nested Classes and Interfaces (嵌套类和接口)
typealias NotificationBuilder = NotificationCompat.Builder
typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
// Enums (枚举类)
typealias Direction = kotlin.io.FileWalkDirection
// (but you cannot alias a single enum *entry*)
// Annotation (注解)
typealias Multifile = JvmMultifileClass
一些概念
闭包
- 参考文章
- 如何在外部调取局部的变量呢?答案就是——闭包。闭包就是能够读取其他函数内部变量的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//这是一个返回值为一个函数的高阶函数 fun makeFun():()->Unit{ var conut = 0 return fun(){ //返回一个匿名函数,这个函数持有count的状态 println(++conut) } } fun main() { val returnFun = makeFun() //函数调用,返回一个函数 returnFun() //调用这个返回的函数,此时makeFun持有makeFun()内部变量的状态 returnFun() returnFun() }
运行结果:
1
2
3 - 广义上来说,在Kotlin语言之中,函数、条件语句、控制流语句、花括号逻辑块、Lambda表达式都可以称之为闭包,但通常情况下,我们所指的闭包都是在说Lambda表达式。
- 函数可以作为变量的经典示例
- args.forEach(::println)
- 双冒号获取函数对象
1 2 3 4 5 6 7 8 9
class MyLogger(val tag: String) { fun print(i: Int) { println("$tag $i") } } fun main(args: Array<String>) { val arr = intArrayOf(1, 2, 4, 6) arr.forEach(MyLogger("TAG")::print) }
- 声明一个函数并赋值
1 2 3
var oneFun: () -> Unit = fun() { }
- 闭包
1 2 3 4 5 6
fun aaa(): () -> (Int) { var current = 10 return fun(): Int { return current++ } }
高阶函数
- 定义:将函数用作一个函数的参数或者返回值的函数。
- 示例
1 2 3
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit { for (element in this) action(element) }
分析:forEach的参数为一个函数,所以forEach为一个高阶函数
常用高阶函数
- run
1 2 3 4 5 6 7 8 9 10
T.run { // 入参为this // 任何类型可以作为返回值 R } kotlin.run { // 没有入参 // 任何类型可以作为返回值 R }
- with
1 2 3 4 5
with(Any()) { // 入参为this // 任何类型可以作为返回值 R }
- apply
1 2 3 4
Any.apply { // 入参为this // 返回值和入参一致 }
- also
1 2 3 4
Any.also { // 入参为 it // 返回值和入参一致 }
- let
1 2 3 4 5
Any.let { // 入参为 it // 任何类型可以作为返回值 R }
- takeIf
1
"abc".takeIf { it == "sss" }?.length
只有为 true 才会返回调用对象,否则返回 null
- takeUnless
1
"abc".takeIf { it == "sss" }?.length
判断条件与 takeIf 相反,只有为 false 才会返回调用对象,否则返回 null
- repeat
1 2 3
repeat(5){ println("repeat") }
重复执行 闭包 指定次数
DSL
带接收者的lambda
声明一个 lambda 表达式时,参数后面添加 .() 例如 String.() -> Unit ,那么函数实例作用域内的 this 为 String 的实例对象
- 经典示例apply
public inline fun
T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
kotlin跳出循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//跳出本次循环,功能类似于continue
(0..10).forEachIndexed { index, it ->
println("-- forEach -- ${index} --")
if (it > 5) return@forEachIndexed
println(it)
}
//跳出整个循环,功能类似于break
run outside@{
(0..10).forEachIndexed { index, it ->
println("-- forEach -- ${index} --")
if (it > 5) return@outside
println(it)
}
}
be careful
- 在kotlin项目中 注解处理器要使用kapt代替annotationProcessor,annotationProcessor(可能会失效?)
添加 apply plugin: ‘kotlin-kapt’ - databinding 在布局中条用 kotlin 伴生类的方法,调用不到(使用@JvmStatic)
- kotlin ARouter在autowired 的变量要添加@JvmFiled
创建数组迭代器
(0..10) 表示[0,10]
(0 until 10)表示[0,10)
init{} 执行时机
是在kotlin constructor构造方法之前调用,翻译成Java代码就是插入到构造方法的第一行然后才执行kotlin构造方法
1
2
3
4
5
6
7
8
9
10
11
12
class Person() {
private var gender: Boolean = true
constructor(name: String,gender: Boolean):this() {
println("constructor")
}
init {
println("Person init 2,gender:${gender}")
}
init {
println("Person init 1")
}
}
执行结果:
Person init 2,gender:true
Person init 1
constructor