2-kotlin基本流程和流程控制
本文最后更新于:2 个月前
基本类型
在java中,我们说万物皆是对象,每日有对象就new
一个对象,那么同样的在Kotlin中,万物皆对象。
因此我们可以调用任何对象的成员函数和属性。其中一些类型拥有特殊的内部表示,例如数字,字符和布尔值可以代表运行时的原始值,在用户看起来就像普通的类。这一节中将会介绍 Kotlin 中的基本类型:数字,字符,布尔值,数组,字符串等等。
Numbers
Kotlin 对于数字的处理和 Java 类似,但不完全相同。例如,Kotlin 不会通过隐式转换来拓宽数字的精度,并且在一些情况下字面值会略有不同。
Kotlin提供了如下几种内置类型的数字(和 Java 类似):
Type | Bit width |
---|---|
Byte | 8 |
Short | 16 |
Int | 32 |
Long | 64 |
Float | 32 |
Double | 64 |
注意字符 (character) 在 Kotlin 中不是数字。
字面值常量
整数类型字面值常量有以下几种 :
- 十进制:
123
,Long
型用大写L
标记,123L
- 十六进制:
0x0f
- 二进制:
0b00001011
注意:kotlin不支持八进制
Kotlin 也支持浮点数的常规表示方法 :
- 默认是
Double
类型:123.5
,123.5e10
Float
类型要用f
或F
标记:123.5f
数字字面值的下划线 (since 1.1)
使用下划线使得数字常量更易读 :
加上下划线不会影响数字的大小及储存方式等,只是在编译器中更利于程序员阅读而已。
在其他进制前和其他语言一样,需要做一下标志。
表示
在 Java 平台,数值被作为 JVM 中原始数据类型物理存储,除非我们需要一个可空值的数值引用(如 Int?
)或者泛型。后者数字会自动装箱。
注意 :数字装箱不会保留同一性 :
因为在装箱后是一个引用数据类型,指向不同的地址,所以并不是一个对象。
问题,在实际运行中不会得到相同结果
另一方面,它保持相等性 :
显示转换
由于不同的表示,小类型并不是大类型的子类型,如果是的话,将会出现下列问题:
因此不止同一性,甚至相等性都将丢失, 所以,小类型是不可以隐式转换为大类型的,这就意味着除了显示转换,我们无法将一个Byte
类型的值赋给一个Int
类型 的变量
我们可以通过显式转换来拓宽数字精度
每个数字类型都支持如下转换:
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
隐式转换的缺少一般并不容易被发现,因为可以从上下文推断类型,并且算术运算会做适当的转换,例如:
运算
Kotlin支持标准的数字运算,这些运算符被看做合适的类的成员(但是编译器将函数调用优化为相应的指令)。 对于位运算,Kotlin中没有特殊的字符来表示,而是提供了以中缀形式命名的函数,如下所示:
这一点与Python并不相同,python把and等当作了二元运算符并且等。
下面是全部的位运算操作符(只可用于Int
和Long
):
- shl(bits) – signed shift left (Java’s <<) 有符号左移
- shr(bits) – signed shift right (Java’s >>) 有符号右移
- ushr(bits) – unsigned shift right (Java’s >>>) 无符号右移
- and(bits) – bitwise and 与
- or(bits) – bitwise or 或
- xor(bits) – bitwise xor 异或
- inv() – bitwise inversion 非
字符
字符用Char
类型表示,不可以直接当做数字使用
字2符的字面值通常是单一的引用,如 '1'
,特殊字符可以通过反斜杠转义,如下字符支持转义:\t
, \b
, \n
, \r
, \'
, \"
, \\\\,
$。编译其他字符,使用Unicode转移语法,\uFF00
我们可以把字符显式转换为Int
和数字一样,当需要一个可空引用时,字符会自动装箱,装箱后同一性不会保留
布尔值
布尔值用Boolean
类型表示,只有true
和false
两个值 当需要一个可空引用时,布尔值会被装箱 布尔值的内置运算有:
- || – lazy disjunction
- && – lazy conjunction
- ! - negation
数组
数组用Array
类型表示,拥有set
get
方法(通过运算重载可以转变为[]
),size
属性,还有一些有用的成员函数:
我们使用库函数arrayOf()
来创建数组并传值进去,例如使用arrayOf(1,2,3)
可以创建数组[1,2,3]
。库函数arrayOfNulls()
可以创建一个指定大小的空数组 另一种创建方式是使用工厂函数Array()
,这个函数需要两个参数,数组大小和一个函数来返回每个元素的值,如下:
如上面说过的,[]
运算符代表调用了set
get
方法
Note: 与Java不同,Kotlin中数组是不可变的,这就意味着我们不能将
Array<String>
转化为Array<Any>
,以防止可能的运行时错误(但是可以使用Array<out Any>
)Kotlin有专门的类来表示原始数据类型的数组,避免过度装箱,
ByteArray
,ShortArray
,IntArray
等等。这些类和Array
类没有继承关系,但是和Array
类具有同样的方法和属性,另外都具有相应的工厂方法
字符串
字符串由String
类表示,String
是不可变的,它的每一个字符元素可以通过下标获取:s[i]
,字符串可以通过for循环迭代:
字符串字面值
Kotlin有两种字符串字面值,转义字符串和原始字符串。转义字符串含有转义字符。原始字符串可以包含换行和任意文本
转义以反斜杠形。
原始字符串由"""
界定,不包含转义字符,可以包含换行和其他文本
你可以使用trimMargin()
方法移除字符开头的空格
默认情况下’ | ‘用作边距前缀,但您可以选择另一个字符并将其作为参数传递,如’ trimMargin(“>”)
字符串可以包含模板表达式,即一些求值的代码段并且求值结果会被拼接到字符串中。模板表达式以美元符$
开头,并且包含一个简单的名字:
或者在花括号中包含任意表达式:
在原始字符串和转义字符串内部都支持模板表达式。你可以使用以下表达式在原始字符串中代表美元符(转义字符串中不支持)
包
源文件通常以包声明开头:
源文件所有内容(无论是类还是函数)都包含在声明的包内。 所以上例中 printMessage()
的全名是 org.example.printMessage
, 而 Message
的全名是 org.example.Message
。
如果没有指明包,该文件的内容属于无名字的默认包。
默认导入
有多个包会默认导入到每个 Kotlin 文件中:
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.* (自 1.1 起)
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
根据目标平台还会导入额外的包:
- JVM:
- java.lang.*
- kotlin.jvm.*
- JS:
导入
除了默认导入之外,每个文件可以包含它自己的导入指令。 导入语法在语法中讲述。
可以导入一个单独的名字,如.
也可以导入一个作用域下的所有内容(包、类、对象等):
如果出现名字冲突,可以使用 as 关键字在本地重命名冲突项来消歧义:
关键字 import
并不仅限于导入类;也可用它来导入其他声明:
顶层声明的可见性
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:private
、 protected
、 internal
和 public
。 如果没有显式指定修饰符的话,默认可见性是 public
。
在本页可以学到这些修饰符如何应用到不同类型的声明作用域。
包
函数、属性和类、对象和接口可以在顶层声明,即直接在包内:
- 如果你不指定任何可见性修饰符,默认为
public
,这意味着你的声明将随处可见; - 如果你声明为
private
,它只会在声明它的文件内可见; - 如果你声明为
internal
,它会在相同模块内随处可见; protected
不适用于顶层声明。
注意:要使用另一包中可见的顶层声明,仍需将其导入进来。
例如:
类和接口
对于类内部声明的成员:
private
意味着只在这个类内部(包含其所有成员)可见;protected
—— 和private
一样 + 在子类中可见。internal
—— 能见到类声明的 本模块内 的任何客户端都可见其internal
成员;public
—— 能见到类声明的任何客户端都可见其public
成员。
请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected
成员并且没有显式指定其可见性,该成员还会是 protected
可见性。
例子:
构造函数
要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个显式 constructor 关键字):
这里的构造函数是私有的。默认情况下,所有构造函数都是 public
,这实际上等于类可见的地方它就可见(即 一个 internal
类的构造函数只能在相同模块内可见).
局部声明
局部变量、函数和类不能有可见性修饰符。
模块
可见性修饰符 internal
意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是
test
源集可以访问main
的 internal 声明); - 一次
<kotlinc>
Ant 任务执行所编译的一套文件。
控制流
If 表达式
在 Kotlin 中,if是一个表达式,即它会返回一个值。
因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色。
if 的分支可以是代码块,最后的表达式作为该块的值:
此写法比较像解释语言,例如R语言就是这样去返回值的,只是在这里用在了if上
如果你使用 if 作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else
分支。
When 表达式
when 表达式取代了类 C 语言的 switch 语句。其最简单的形式如下:
when 将它的参数与所有的分支条件顺序比较,直到某个分支满足条件。 when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)
如果其他分支都不满足条件将会求值 else 分支。 如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了[例如,对于 枚举(enum)类条目与密封(sealed)类子类型]。
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
我们可以用任意表达式(而不只是常量)作为分支条件
我们也可以检测一个值在(in)或者不在(*!in*)一个区间或者集合中:
另一种可能性是检测一个值是(is)或者不是(*!is*)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法与属性而无需任何额外的检测。
when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
自 Kotlin 1.3 起,可以使用以下语法将 when 的主语(subject,译注:指 when
所判断的表达式)捕获到变量中:
在 when 主语中引入的变量的作用域仅限于 when 主体。
For 循环
for 循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于像 C# 这样的语言中的 foreach
循环。语法如下:
循环体可以是一个代码块。
如上所述,for 可以循环遍历任何提供了迭代器的对象。即:
有一个成员函数或者扩展函数
,它的返回类型
- 有一个成员函数或者扩展函数
next()
,并且 - 有一个成员函数或者扩展函数
hasNext()
返回Boolean
。
- 有一个成员函数或者扩展函数
这三个函数都需要标记为 operator
。
如需在数字区间上迭代,请使用区间表达式:
对区间或者数组的 for
循环会被编译为并不创建迭代器的基于索引的循环。
如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:
Target platform: JVMRunning on kotlin v. 1.5.31
或者你可以用库函数 withIndex
:
Target platform: JVMRunning on kotlin v. 1.5.31
参见 for 语法。
While 循环
while 与 do..while 照常使用
返回和跳转
Kotlin 有三种结构化跳转表达式:
- return。默认从最直接包围它的函数或者匿名函数返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
所有这些表达式都可以用作更大表达式的一部分:
这些表达式的类型是 Nothing 类型。
Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、fooBar@
都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。
现在,我们可以用标签限制 break 或者continue:
标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。
返回到标签
Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候:
Target platform: JVMRunning on kotlin v. 1.5.31
这个 return 表达式从最直接包围它的函数即 foo
中返回。 (注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。) 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。
Target platform: JVMRunning on kotlin v. 1.5.31
现在,它只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便。 该标签与接受该 lambda 的函数同名。
Target platform: JVMRunning on kotlin v. 1.5.31
或者,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回
Target platform: JVMRunning on kotlin v. 1.5.31
请注意,前文三个示例中使用的局部返回类似于在常规循环中使用 continue。并没有 break 的直接等价形式,不过可以通过增加另一层嵌套 lambda 表达式并从其中非局部返回来模拟:
Target platform: JVMRunning on kotlin v. 1.5.31
当要返一个回值的时候,解析器优先选用标签限制的 return,即
意为“返回 1
到 @a
”,而不是“返回一个标签标注的表达式 (@a 1)
”。