自 2017 年 kotlin 成为 Android 开发一级语言,也没有认真总结,一直对 kotlin 的骚操作没有足够的认知,这篇文从就是提升对 kotlin 骚操作的认知 ~ 。
快速上手
基础语法
1 | var age: Int = 18 |
Java 与 Kotlin 交互
1 | // Utils.kt |
Kotlin 函数直接可以写在文件里,不需要写在类里(但是编译后依旧是在类里),Java 在调用 Kotlin 文件时,只需要在 文件名 后加上 kt 如上 UtilsKt 。Utils 文件中的所有函数和类型变量,最终编译后都会被 public static 修饰。
匿名内部类的调用:
1 | // Utils.kt 匿名内部类 |
Class 的调用:
1 | // Utils.kt |
这是为什么呢?这是因为 Kotlin 与 Java 的 Class 格式是不一致的。Kotlin Class 类型是 KClass 。
当 Java 的变量使用到了 Kotlin 的关键字时, Kotlin 调用这个变量需要加上 ``` `
1 | // JavaMain.java |
一些问题
Kotlin 没有封装类
封装类是什么?就是 Java 中的 Integer、String 等。
Java 中的封装类在 Kotlin 都会转换成基本数据类型调用
比如 Java 中有这么一个接口,然后用 Java 的类去实现接口
1 | public class AInterface { |
Kotlin 在调用调用的时候,提示用只有 Kotlin 的 Int 类型。

最终的打印也只能打印出 int 。而且如果 A 是一个 Kotlin 类的话,重写方法也只能重写一个,否则会报错。
说明 Kotlin 在运行时只会执行它基本数据类型的那个方法,而不会执行封装类型的方法 。Kolin 只有 Int 没有 Integer 类型。
Kotlin 类型空值敏感
通过一段代码说明,首先 Java 有一端这样的代码
1 | public static String format(String string) { |
Kotlin 去调用
1 | var fmt1 = A.format(str) //String! |
分别有三种返回类型接收,
第一种是编译器自动推断的返回 String! ,这个类型只会在 Kotlin 互相调用的时候才会出现,我们是不可以手动声明这个类型的。
第二种是手动声明的 String 没有任何符号,第三种是一种可空的类型 String?
如果我们将 str ="" ,那么 fmt2 将会报错,因为,我们返回的是一个 null 但是 String 却不能为空,所以会报 IllegalStateException:format(str) must not be null
但是这并不表示其他返回类型就是可靠的,如果我们在分别调用一个方法
1 | println(fmt1.length) |
执行发现 fmt1.length 会报空指针,因为 String! 是一个兼容类型,只能临时的使用它,但是如果调用这个值的方法,它会和 Java 语法调用一样去执行,在 Java 中 null 调用任何方法都会报错。
而 fmt3.length 不会报错,会输出一个 null 字符串
说明,如果在赋值的时候我们不确定一个返回值是否为 null ,一定要赋值为可控类型 String? ,这要代码才能可靠。
Kotlin 没有静态变量与静态方法
1 | // Utils.kt 匿名内部类 |
这是上面讲过的一段代码,如果我们想让 Java 调用方式和 Kotlin 一样,或者说 sayMessage 编译后生成的是 public static 的方法。我们可以加入 @JvmStatic 就可以了。
1 | // Utils.kt 匿名内部类 |
函数 与 Lambda 闭包
参数默认值
1 | fun echo(name: String) { |
kotlin 语法可以给参数设置默认值,在 kotlin 调用的时候可以不传参数使用默认值。
1 | fun echo(name: String = "CalmCenter") { |
默认参数通常用于重载函数过多时,默认参数可以大大减小重载函数的数量
当函数体只有一个语句是,可以将语句赋值给这个函数
1 | fun echo3(name: String = "CalmCenter") = print(name) |
内部函数
内部函数可以访问外部函数的局部变量,通常用在某些条件下触发递归的函数,或者不希望被外部函数访问到的函数。会降低代码的可读性
1 | fun function() { |
扩展函数
扩展函数书写方式,既然是函数肯定得 fun ,后面跟随 需要扩展的类名.需要扩展的成员方法
1 | fun File.readText(charset: Charset = Charsets.UTF_8): String = |
这是 FileReadWrite.kt 中的一段代码,用来扩展 File 类。
在 kotlin 中的调用方式和 File 本身的成员函数使用方法相同,但这个函数是使用扩展函数静态添加的一个函数
1 | val file=File("Main.kt") |
Java 在调用时略有不同,Java 在调用扩展函数时,需要用到 Kotlin 生成的那个类 xxxKt ,第一个参数是需要扩展的那个类的对象,第二个参数是函数本身的参数,但是函数即使写了默认参数,Java 调用时也得传,默认参数对 Java 端没有效果。
1 | File file = new File("Main.kt"); |
扩展函数是静态的给一个类添加函数,需要注意,静态的不具备多态效果
通常用于第三方 SDK 中,不能够控制的类中,添加一些需要用到的方法的时候
Lambda 闭包
1 | val thread = Thread(object :Runnable{ |
Lambda闭包可以省略冗余信息,没有参数的方法,还可以省略->val thread = Thread(Runnable { -> Unit }) val thread = Thread(Runnable { }) val thread = Thread({}) <!--code19-->
如果除
lambda(匿名函数)后没有其他参数,则可以省略类名后的 ()val thread = Thread {} <!--code20-->
很显然,kotlin 要方便很多。
声明一个闭包
1 | var print = { name:String -> |
大括号括起来的地方就是闭包,闭包可以有参数,但是参数有上限,最多 22 个。多于 22 会报 NoClassDefFoundError: kotlin/Function23
因为 kotlin 的类在编译后会被编译成 class 文件,kotlin 的 lambda 在编译后会被编译成匿名内部类,有多少个参数就会生成 FunctionX,但是前提得有这个类。
在 Functions.kt 中,声明了 0 ~ 22 个参数的 Function 。所以如果想要使用 23 个参数的闭包,我们需要自己创建这个类。
在报错中我们可以看到 kotlin/Funtion23 ,说明这个类应该在包名为 kotlin 下放着。
但是如果我们使用 kotlin 语言中在 kolint 包下创建一个 Function23 ,编译时会有一个报错 Only the Kotlin standard library is allowed to use the kotlin package,只有 Kotlin 标准库才能使用 kotlin 这样的包名。
遇到这个问题,我们就应该想到 Kotlin 和 Java 是完全兼容的,如果我们不能用 Kotlin 语言声明一个类,我们可以使用 Java 语言实现。
在 kotlin 包下创建 Funtion23.java 文件
1 | package kotlin; |
调用闭包函数时,和函数方法调用一样
1 | print("hello") |
高阶函数
函数 或 lambda(匿名函数) 的参数又是一个 函数 或者 lambda 时,这个函数称为高阶函数
1 | fun onlyif(isDebug: Boolean, block: () -> Unit) { |
- 高阶函数也是函数,肯定也得用
fun修饰 - 第二个匿名函数 返回值为
UnitUnit表示没有返回值的函数,默认返回的一个隐藏类型,在函数作为参数时必须显示表示出返回值
高阶函数的使用,
1 | onlyif(true) { |
函数的声明与传递
获取函数声明,可以使用 (对象名::方法名) ,获取声明后可以赋值给函数对象,并且可以传递到高阶函数中。
高阶函数的参数传递时,只能是匿名函数或者一个函数声明,(对象名.方法名) 表示执行这个函数。
1 | val runnable = Runnable { |
内联优化代码
使用 inline 修饰一个函数
Kotlin 的 Lambda 在编译后会生成一个匿名对象
可以使用 inline 修饰方法,这样当方法在编译时就会拆解方法的调用为语句调用(把函数内的方法直接复制到调用的地方),进而减少创建不必要的对象
1 | inline fun onlyif(isDebug: Boolean, block: () -> Unit) { |
编译后,调用的地方会直接替换成以下代码。
1 | boolean isDebug = true; |
过度使用 inline 会增加编译器的编译负担,并且回加大编译后的代码块,所以 inline 通常只会用于修饰高阶函数,不会随便使用。
类与对象
1 | open class MainActivity : AppCompatActivity(), DialogInterface.OnClickListener |
kotiln声明使用class一个类:后跟随父类或接口,如果没有指定父类,那么他的父类是Any- 实现接口只需要用
,分割,继续添加就可以,不需要使用implements来声明,接口和父类没有先后关系。 kotlin的类默认public final的,如果不需要final,则需要添加open关键字
构造函数
1 | open class MainActivity(var int: Int) : AppCompatActivity() |
声明一个构造函数,可以在类名后直接写构造参数,而不是写一个构造方法
如果想在构造函数中写逻辑,可以写在 init 代码块,init 是类的构造函数被调用的时候去执行的
1 | init { |
如果有多个构造函数,需要显示的声明次级构造函数,主构造函数和次级构造函数可以同时写
1 | class TestView : View { |
主构造函数跟在类名后,这里重写了 三个次级构造函数,次级构造函数必须 直接 或 间接 的 继承 主构造函数 或 父类构造函数,比如上面代码中的这样 : super(context) 或 : this(context, attrs,0)
访问修饰符
在 koltin 中访问修饰符有:
- private:类所有成员都是私有的
- protected:这个类以及子类都可以访问
- public:所有类都可以访问
- internal:同一模块可以访问
(同一 library/module)
可用于项目结构化扩展。
伴生对象
之前说过 Kotlin 中没有静态方法,解决办法之前也说过一种加入 @JvmStatic ,就可以在编译后生成静态方法
还有第二种方法,就是伴生对象 companion object
1 | class TestCompanion { |
伴生对象,在编译器编译后会在这个类里生成一个 companion 静态对象,Java 在调用时是调用静态对象,再调用内部方法。
那 object 和 companion object 有啥区别呢?
object声明(一个类)是延迟加载的,只有当第一次被访问时才会初始化object可以定义在全局也可以在类的内部使用
companion object是当包含它的类被加载时就初始化了的,这一点和Java的static还是一样的companion object只能定义在对应的类中companion object就是Java中的static变量
单例类
之前通过 object 生成过匿名内部类,这也是一种单例的写法
还有一种更好的写法就是通过伴生对象实现
1 | class Single private constructor(){ |
动态代理
Kotlin 默认支持动态代理
1 | interface Animal { |
如果 Zoo 重写了 bark 方法,那么代理将会失效
1 | class Zoo(animal: Animal):Animal by animal{ |
将会返回 Zoo 。
Kotlin 的动态代理在编译后将会转换为静态代理去调用。所以 Kotlin 的动态代理一定比 Java 的动态代理效率高。因为 Java 的动态代理是通过反射实现的。
Kotlin 特有的类
- 数据类
数据类是 Kotlin 中很特殊的一个类
1 | data class User(var id:Int,var name:String) |
它可以自动的将类中的成员变量生成 geter()/setter() ,以及我们经常需要重新的 toString()/hashCode()/equals()/copy() ,这些方法都是由编译器帮我们重写好了。
注意:数据类是 final 类型的,不可以添加 open 去修饰它。所以它不可以被继承。
- 密闭类
Kotlin 中也有枚举类,和 Java 使用方式一样,但是平常 Kotlin 是不适用枚举类的,而是使用 密闭类
1 | sealed class SuperCommand() { |
密闭类通过 sealed 修饰,密闭类可以有子类,但是必须在同一个文件中,所以子类一般写在密闭类内部。如上
密闭类使用方法和枚举基本相同,它的最大的特性就是可以扩展它的子类
比如我们需要对一个 View 做上下左右的移动 或者 放大缩小,可以通过密闭类,轻松的实现各个功能的分工,而且子类也可以传入参数,可以更灵活的控制和操作每一个选项。
1 | sealed class SuperCommand() { |
高级特性
解构
在 Kotlin 中,允许将一个类拆解然后分别赋值
需要用到 operator 重载运算符,将已经有的函数赋予它们新的约定
1 | class User(var age: Int, var name: String) { |
component1 component2 是固定的 componentX,如果需要解构这里不可以改变,也可以有 component3、component4...
类的解构
1 | val user = User(12, "name") |
将 user 才拆解后,分别赋值给 age、name ,component1 代表括号中的第一个元素,component2 代表第二个元素,以此类推
Map 的解构
1 | val map = mapOf("key1" to "value1", "key2" to "value2") |
将原本的 entry 直接解构成 k , v 进行操作。
循环
按照正常 java 思路,循环应该这样写
1 | var count: Int |
但是 kotlin 中没有这个语法,但是它有更多适合的语法
Kotlin 循环符
kotlin 用 .. 运算符,表示一个区间
循环 1 到 10 ,i 表示变量
1 | for (i in 1..10){ |
until
循环 1 到 9
1 | for (i in 1 until 10){ |
until 是一个扩展函数
1 | public infix fun Int.until(to: Int): IntRange { |
这里只拿 Int 举例, 最终也是使用 .. 运算符,执行到 to - 1
downTo
循环 10 到 1
1 | for (i in 10 downTo 1){ |
downTo 源码
1 | public infix fun Int.downTo(to: Int): IntProgression { |
step
循环 1 到 10 每次步长为 2,结果为 1 3 5 7 9
1 | for (i in 1..10 step 2){ |
step 源码
1 | public infix fun IntProgression.step(step: Int): IntProgression { |
in
1 | var list = arrayListOf("a","b","c") |
list.withIndex 源码
1 | public fun <T> Iterable<T>.withIndex(): Iterable<IndexedValue<T>> { |
可以看到这里返回 IndexedValue<T> ,它实际是一个 data class 。
1 | public data class IndexedValue<out T>(public val index: Int, public val value: T) |
repeat
这是一个高阶函数
1 | .internal.InlineOnly |
实际就是封装了一个 for 循环
集合操作符
与 rxjava 的操作符相同,rxjava 允许对数据进行一系列的链式调用,在每一步中对数据进行筛选或处理,最终得到我们想要的数据
kotlin 对集合添加了很多操作符,rxJava 有的 在 Kotlin 中基本上都有
这里简单介绍几个
1 | var list = arrayListOf('a', 'b', 'c', 'e') |
map:将数据进行改变处理,输入值和输出值类型可以不一样
filter:筛选,返回满足条件的集合
find:查找,返回满足条件的第一个值
findLast:返回满足条件的最后一个值
reduce:可以同时拿到两个值进行操作,第一次计算的结果是第二次的第一个值
let:
let扩展函数的实际上是一个作用域函数,当需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。var let:Int = user?.let {//表示 user 不为 null 的条件下,才会去执行 let 函数体 println("my name is ${it.name}, I am ${it.age} years old, my phone number is ${it.phoneNum}") // it 就不需要再写 ? 1000 } <!--code56--> 在使用 `User` 参数时,可以直接只用属性名,可以省去重复的 `user` 对象的书写,`result` 为函数块内最后一行或者 `return` 指定返回的值
run:
let和with两个函数的结合体,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。var run:Int = user?.run { println("my name is $name, I am $age years old, my phone number is $phoneNum") 1000 } <!--code57-->
also:
also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身var also:User = user?.also { println("my name is ${it.name}, I am ${it.age} years old, my phone number is ${it.phoneNum}") } <!--code58-->
MutableList 可变集合,在 Kotlin/JVM 平台中,它的实现就相当于是 ArrayList 。

