Kotlin 笔记
Kotlin 笔记
原创 已于 2026-09-02 14:39:02 修改 · 粉丝可见 · 62 阅读 · 0 · 0 GEO检测 · 编辑
文章链接:https://blog.csdn.net/hacker_51/article/details/150225457
目录
[TOC]
简介
Kotlin是一种现代化的静态类型编程语言,具有以下优势:
与java互操作性强:Kotin 可以与现有的 Java 代码无缝地互操作,允许开发者在现有的项目中逐步采用 Kotin,而不需要重写整个项目。这使得 Kotlin 成为 Android 应用开发的理想选择。
简洁易读:Kotin 的语法简洁并具有更好的可读性,减少了样板代码的编写。相比 Java、Koti 可以使用更少的代码来实现同样的功能,从而提高开发效率。
空安全性:Kotin 对空值进行了更好的处理。在 Kotin 中,变量默认是非空的,如果需要使用可能为空的值,需要显式声明类型为可空。这有助于减少空指针异常的发生。
函数式编程支持:Kotin 支持函数式编程的特性,如高阶函数、lambda 表达式和函数式编程的集合操作等。这些特性可以让开发者编写更简洁、可维护的代码,并提高代码的表达能力。
扩展函数:Kotin 允许开发者为某个类添加新的方法,而不需要修改该类的源代码。这种扩展函数的特性可以为开发者提供更灵活的方式来扩展现有的类库。
协程支持:Kotin引入了协程(coroutine)机制,使得异步操作更易于管理和编写。通过使用协程,开发者可以使用顺序的方式编写并发代码,并避免了回调地狱的问题。
总的来说,Kotin 是一门功能丰富、可读性高、与 Java 无缝互操作的编程语言,适用于 Android、Web 后端开发等多种场景。
环境配置
简单通过 VMR 去安装也可以的,但是注意,安装路径中的目录不能有空格的目录名,否则会报错。
初级
入门尝试
简单尝试,保存为Main.kt 文件
1 | |
运行编译
1 | |

多出一个 Main.jar,编译为了 jar 包。通过java运行
1 | |

运行效果成功。
变量的声明与使用
变量声明
1 | |
常量声明
1 | |
数据类型
数字类型

对于一些比较长的数字,我们可能需要使用类似于分隔符一类的东西来方便我们计数。
银行往往把1000000000这种长数字记为1,000,000,000,这样看起来会更直观
在Kotlin中也可以像这样去编写:
1 | |
数字类型不仅可以写成十进制,也可以是十六进制或是二进制表示( Kotlin不支持八进制表示 )
十六进制:
1 | |
二进制:
1 | |
除了整数类型外,Kotlin还为无符号整数提供以下类型:
UByte:一个无符号8位整数,范围从0到255
UShort:无符号16位整数,范围从0到65535
UInt:一个无符号32位整数,范围从0到2^32-1
ULong:一个无符号64位整数,范围从0到2^64-1
为了使无符号整数更易于使用,Kotin同样提供了用后缀标记,该后缀表示无符号类型(类似于上面的Long类型添加L字母)
1 | |
uL 和UL 可以将文字直接标记为无符号Long类型
1 | |
浮点数
对于小数来说,Kotin提供符合 IEEE754 标准的浮点类型Float和 Double,Float 为 IEEE754 标准中的单精度数据,而Double位标准中的双精度数据,对于单双精度,本质上就是能够表示的小数位精度,双精度比单精度的小数精度更高。

数字类型的运算
简单的加减乘除就不详细赘述了。
Kotin提供了一组整数的位运算操作,可以直接在二进制层面上与数字表示的位进行操作,不过只适用于Int 和Long类型的数据:
shl(bits) - 有符号左移
shr(bits) - 有符号右移
ushr(bits) - 无符号右移
and(bits) - 按位与
or(bits) - 按位或
xor(bits) - 按位异或
inv() - 取反
布尔类型
布尔值除了可以直接赋值得到,也可以通过一些关系运算得到,常见的关系运算有大于、小于以及等于,所有的关系运算在下方:
判断两个数是否相等:a==b 和 a!=b
判断数之间大小:a<b,a>b,a<=b,a>=b
判断数是否在指定范围中:a..b,x in a..b,x !in a..b
字符类型
字符类型的作用与 C语言的 字符类型一致,也可以表示 ASCII 码。
字符串类型
这里的字符串类型与 python 有点像。
可以使用 三个双引号 打印多行文本
1 | |

模板表达式
通过 `$` 取值嵌入字符串中。示例如下:
1 | |

模板表达式中也可以写入表达式,示例如下:
1 | |

如果用到特殊符号记得转义字符。
if-else判断分支
对于这个与Java基本一致。多了一个特性,可以在变量中进行分支判断。示例如下:
1 | |

when选择分支
它的作用类似Java和C语言中的 switch。

示例代码:
1 | |

for循环
for循环和Java、C语言基本一致。多了以下写法:
1 | |
这里的可遍历目标有很多,比如:
数组
区间(Range,示例:val range : IntRange = 1..10;)
任何实现了运算符重载函数iterator的类
示例代码:
1 | |

默认情况下,每一轮循环会向后 +1 , 我们也可以自由控制每一轮增加多少,也就是步长:
1 | |

也可以倒着遍历,将 `...` 替换成 downTo, 示例:
1 | |

标签语句 `@`
类似于 C语言的 goto。不同的是仅限于循环嵌套进行循环的别名,示例如下:
1 | |
while循环
while循环和C语言基本一致,也有 do while 循环。
中级
函数
kotlin中万物皆函数。
Kotlin 函数使用 fun关键字声明:
1 | |
简单示例:
1 | |
默认值
函数默认值和C语言的差不多,示例如下:
1 | |

函数内部也可以进行定义函数。也支持函数重载:函数名相同,参数不同。
变量
之前说过,万物皆函数,这里的变量就是如此了。我们正常的进行获值和赋值就是调用变量中的 get、set。
1 | |

注意:Kotlin 里
get/set是紧贴在属性声明后面的访问器,它必须和属性声明连成一体。而分号;会强制终结当前声明!
递归函数
有关递归函数,基本是老生常谈了。基本概念那些我就不做过多介绍。
对于 Kotlin ,有一个关键字可以将尾部作为返回值进行递归的操作优化: tailrec
示例:
1 | |
实际上在编译之后,大概会变成:
1 | |
实用库函数
标准输入输出
readln
输入整行
运算函数
pow()
abs()
max()
函数型变量
有点类似 C++ 的回调函数。
也类似 JavaScript 中的 lambda 表达式的箭头函数声明
简单示例代码:
1 | |
搞清楚”函数型变量”到底在说什么
以前学的变量,存的是数据:
1 | |
而函数型变量,存的是行为——也就是”一段还没执行的代码”。
打个比方:
🍳 普通变量 = 一盘做好的菜(结果现成,拿来就能吃)
🍲 函数型变量 = 一张菜谱(存的不是菜,是”怎么做”的步骤。你得照着步骤执行,才会做出菜来)
这是最关键的一点,理解了它,后面全通了。看这个对比:
1 | |
可以这样理解: f 就像存了一个”按钮”,只有按下按钮(调用 f() ),里面的代码才真正执行。
对于调用函数型变量
加
::后:局部函数、顶层函数都能正常传参 ✅(输出30、120)不加
:::哪怕是顶层函数,也报一模一样的错 ❌
所以问题不在你的代码结构,而在于一个 Kotlin 的语法规则。
核心规律:为什么 lambda 能直接传,具名函数要加 ` :: ` ?
| 写法 | 它的本质 | 能不能直接当值传? |
|---|---|---|
{ n -> n + 1 } (lambda) |
本身就是一个函数值 | ✅ 直接传 |
fun(a: Int, b: Int): Int = a * b (匿名函数) |
本身就是一个函数值 | ✅ 直接传 |
addOne (存了 lambda 的变量) |
变量=值 | ✅ 直接传 |
copeNum / topFun (具名函数) |
是一个”名字”,不是值 | ❌ 必须 ::copeNum |
` :: `的作用一句话:把”函数的名字”变成”函数的数值”。
再打个比方——你去饭店:
🍽️ 你喊”宫保鸡丁”,服务员以为你要点这道菜(= 调用这个函数)。 你要是说”给我一份宫保鸡丁的菜谱”,才是在要”做法本身”(= 把这个函数当值)。
::就是那个”菜谱”按钮。直接写函数名,Kotlin 会以为你想调用它,所以报错说function invocation 'copeNum(...)' expected(”你这里是不是想调用 copeNum(...)?”)。
匿名函数(习惯 return 就用这个)
1 | |
起别名关键字typealias
这里的 typealias 和 C语言的 typedef 和 #define 基本类似。
示例代码:
1 | |
lambda表达式与高阶函数
lambda表达式
1 | |
多行lambda函数型变量(多条逻辑,最后一行是返回值)
(多条逻辑,最后一行是返回值)
1 | |
如果函数调用的尾随 lambda表达式 , 默认的标签名字就是函数名字 :
1 | |
内联函数
inline 关键字,与 C++ 的内联函数效果一样。进行性能的优化,将原本包裹的函数,直接提取变成源代码,而不是以函数的形式。所以一般递归函数不建议使用内联关键字 。
这里需要注意的一点,内联函数中参数是函数型变量,则需要noinline关键字。示例:
1 | |
为什么需要 noinline ?
关键在于理解 inline 的本质:
inline= 编译器把你的函数体”抄写”到调用它的地方(展开)。 lambda 参数func也一样——它会被直接展开成一段代码,而不是变成一个”对象”。
可你写了 val a = func ——你想把 func 存起来。这就矛盾了:
inline想让它变成”展开的代码” ✅存变量要求它是”一个能被存放的对象” ❌
一个东西不可能同时既是”展开的代码”又是”被存放的对象”。
所以编译器直接报错,并给你指路:加 noinline 。
noinline 做了什么?
noinline 就是告诉编译器:
“这个
func参数我不要内联展开,你把它当成一个普通函数对象处理吧。”
这样它就可以被存进变量、传出、到处传递了。
打个比方:
📋
inline的 lambda 像纸条上的话——调用时直接”念出来”(展开)。 而val a = func是想把纸条本身收进抽屉。 想收进抽屉,就得先把话从”活的声音”变成”实物纸条”——这就是noinline的切换。
一句话总结:
inline想让 lambda”展开”省开销,代价是不能把它当对象存。 需要存放/传递 lambda 时,就加noinline放弃展开,换来”自由使用”。
内联高阶函数的lambda参数可以直接写 return 不指定标签
示例:
1 | |

因为内联了,展开代码到main函数中了,这里的return相当于终结main函数。
类的定义
这里的类的定义和Java的类定义类似。
示例代码:
1 | |
但是这里需要注意的是:
如果没有这个构造函数,类定义必须要有个初始值:
1 | |
Kotlin也有一个写法:
1 | |
表示构造这个类,有点类似C++。
注意这里类的定义和Java不太一样,不太需要定义 get、set,因为本身已经做好了封装。如果对某个属性 get 以及set额外操作,自行操作定义的。
为什么不存在?—— Java 惯性思维在这里失灵了
你用 Java 的脑子在想: private 字段 → 手动写 getName() / setName() → 别人通过方法访问。这套在 Java 完全正确。
但 Kotlin 不是这么玩的。打个比方:
| Java 的字段 | Kotlin 的属性(property) | |
|---|---|---|
| 本质 | 字段是”保险柜里的钱”,方法是你手动开的”窗口” | var name 是一个自带窗口的保险柜 |
| 你要取钱 | 必须走你手写的窗口 getName() |
直接说 student.name ,窗口自动开 |
关键区别来了:
var name(不写 private) → Kotlin 自动给它配好”窗口”(getter/setter),外面直接student.name读写。private var name→ Kotlin 认为”这是柜主私藏的钱,连窗口都不装了”,外面既没有student.name,也没有getName()——彻底锁死。
所以你现在是:保险柜加了 private 锁死 → 却又想从”不存在的窗口”取钱(getName) → 当然找不到。
lateinit 关键字
这里是懒加载关键字,暂时先不初始化对象。示例:
1 | |
对象的使用与创建
注意,Kotlin的对象创建与使用与 C++ 有点类似,和 Java的区别就是不需要new。
简单示例:
1 | |
同时注意,这里的对象直接赋值,并不是复制一份对象,而是类似C语言的指针交换。
示例:
1 | |

对象的初始化
init关键字,示例代码:
1 | |

类的成员函数
这里的成员函数和C++的成员函数基本一致。
再谈基本类型
Kotlin中将基本类型也都封装成了对象类。
特别说明:在Kotin中,虽然编码时万物皆对象,但是在最终编译时,会根据上下文进行优化性能
大部分情况下会优先编译为Java原生基本数据类型(不是对象)
而另一部分情况下才会编译为Java中的Integer包装类型。因此很容易出现以下迷惑行为:
1 | |
1 | |
运算符重载
这里的运算符重载和 C++ 基本一致。示例代码:
1 | |
运算:
1 | |
结果:

这里附带一下对应的函数名称:
| 符号 | 对应的函数名称 |
|---|---|
| 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 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) |
| a == b | a.equals(b) |
| a != b | !a.equals(b) |
| a > b | a.compareTo(b) > 0 |
| a < b | a.compareTo(b) < 0 |
| a >= b | a.compareTo(b) >= 0 |
| a <= b | a.compareTo(b) <= 0 |
| 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) |
| a++ | a.inc() |
| a-- | a.dec() |
| +a | a.unaryPlus() |
| -a | a.unaryMinus() |
| !a | a.not() |
| a && b | a.and(b) (或 a && b 编译为逻辑与,但无对应函数,实际为短路运算) |
| a || b | a.or(b) (同理短路运算) |
| a..<b | a.rangeUntil(b) (Kotlin 1.7+) |
| a < b .. c | 无直接函数,由 rangeTo 和 compareTo 组合 |
其中inc()和dec()函数比较特殊,它们必须返回一个值,该值将分配给使用++或--操作的变量,而不是改变执行inc或dec操作的对象,意思就是执行后应该得到一个新生成的对象,然后变量的值直接引用到这个新的对象,因为nt类型就是这样的,比如a++的操作步骤如下:
将a的初始值存储到临时存储a0
将a0.inc()的结果分配给a。
返回a0作为表达式的结果。
同样的,++a的操作步骤如下:
将a.inc()的结果分配给a。
作为表达式的结果返回a的新值。
觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭
wechat pay
ali pay