1.1 基本语法
Kotlin语言支持较为自由的程序编写风格,程序可采用面向对象或面向过程的方式进行编写。在程序编写过程中,程序文件的名称可根据实际情况任意指定,同时,程序文件的扩展名为kt。Kotlin程序运行的起点为main方法(或称为main函数)。
以下示例是一个简单的Kotlin程序。该程序运行时可通过打印语句在输出窗口中显示一个“Hello World!”字符串。
1 package niltok.demos
2
3 fun main(args: Array<String>){
4 println("Hello World!")
5 }
上述程序包含两个部分:包(程序第1行)及main方法声明(程序第3行至第5行)。其中,包声明使用package命令实现,而main方法声明实质是main方法的一个定义。在main方法声明中,fun为方法(或函数)声明关键字,main为方法的名称,args为main方法的输入参数。在输入参数方面,“args: Array<String>”语句说明args是一个字符串数组,数据类型为Array<String>。示例程序main方法中的println语句是一个打印语句,该语句可在输出窗口中打印一个指定的字符串。
有的情况下,main方法的输入参数args可被忽略,但该参数可用于传输与程序运行有关的多个数值。在程序运行前,参数args中数值的输入需借助命令行工具,以手工输入方式来指定。而在程序运行时,main方法中的程序可访问并使用args所包含的数值。以下为一个访问args参数的简单示例:
1 package niltok.demos
2
3 fun main(args: Array<String>){
4 println("Inputs:") //显示提示信息
5 println(args.size) //打印显示args的长度
6 for (i in args){ //访问args
7 println(i)
8 }
9 }
上述示例中,程序第5行是打印显示args参数的长度,第6行至第8行则使用for语句遍历args参数中的元素,并将每个元素进行打印显示。
在Intelli J IDEA中,若要指定args中的数值,可在系统菜单中单击“Run”项,并在显示的菜单中选择“Run”(也可在集成开发环境中直接使用快捷键“Alt+Shift+F10”)项;之后,开发环境会显示一个对话框,在对话框中选择“Edit Configurations…”项,系统显示一个标题为“Run”的配置向导(对话框)。向导的“Configuration”(配置)标签页中,可在“Program arguments:”项中指定程序所需参数(即设置args所包含的多个数值)。输入参数设置时,参数间使用空格作为分割,例如,若想在args中填写两个参数“123”和“456”,所填写的内容为123 345。参数填写完毕,单击向导中的“Run”按钮,程序开始运行。以输入参数为“123”和“456”为例,示例程序运行的结果如下。
1 inputs:
2 2
3 123
4 345
在语法方面,Kotlin程序的每个语句不使用结尾符(这与传统语言不同)。Kotlin程序中,只读变量的使用场景相对广泛。只读变量在声明时使用val关键字进行说明,一旦某个变量被指定为只读变量,则在程序运行过程中,该变量的值是不允许被修改的。与只读变量不同,可变更变量(即普通变量)的值可以在程序运行过程中根据需要而改变。可变更变量在声明时使用var关键字进行说明。
Kotlin程序中,变量声明使用的格式为变量名:变量类型。例如,声明一个整型只读变量i时,声明语句为val i: Int;当声明一个整型变量(普通变量)j时,声明语句为var j: Int。
Kotlin程序中的程序注释基本格式与C语言或Java语言相同,即使用符号//和/ *…* /。其中,//符号用于实现单行注释,而/ *…* /则可实现多行注释。
1.1.1 基本数据类型
Kotlin语言支持的基本数据类型包含[2]数字型、布尔型、字符和数组。
(1)数字
Kotlin内置的数字类型如表1.1所示,具体包含双精小数(Double)、单精小数(Float)、长整型(Long)、整型(Int)、短整型(Short)和字节(Byte)。
表1.1 Kotlin内置数字类型及物理存储长度
在未特别说明的情况下,Kotlin程序中的小数数值默认为Double类型。若需要指定Float类型的小数数值时,可使用格式“小数数值f”或“小数数值F”。例如,当需要指定123.4为单精小数时,程序中可使用123.4F。对于整数,Kotlin暂不支持八进制整数,其他的整型数字按以下规则表示。
● 对于普通十进制整数,使用普遍格式,例如:321;
● 对于长整型(十进制),使用格式为数字L,例如:321L;
● 对于十六进制整数,格式为0x数字,例如:0x FF;
● 对于二进制整数,格式为0b数字,例如:0b001。
另外,Kotlin支持以下画线来分割数字,如:1_2345_6789。
(2)类型转换
Kotlin程序中,位数短的数据类型数据不能直接转换成为位数长的数据类型数据(这个技术特点与Java语言的特点不同),例如,下列程序将无法运行。
1 val i: Int = 100 //只读变量i,类型为整型
2 val l: Long = i //本句无法运行,因为系统中Int类型数据比Long类型数据所占的位数短
在程序编写过程中,对于不同类型的数字,它们之间的转换可以显式方式实现。具体的转换可借助以下函数[2]:
● 转换为字节型(Byte),使用to Byte(),例如:10.to Byte();
● 转换为短整型(Short),使用to Short(),例如:(12.34).to Short();
● 转换为整型(Int),使用to Int(),例如:(12.23).to Int();
● 转换为长整型(Long),使用to Long(),例如:(1234.56).to Long();
● 转换为单精小数(Float),使用to Float(),例如:123.to Float();
● 转换为双精小数(Double),使用to Double(),例如:123.to Double();
● 转换为字符(Char),使用to Char(),例如:123.to Char()。
(3)数学运算
Kotlin语言能实现多种数学运算,其中,基本运算包含:+(加)、−(减)、*(乘)、/(除)、%(求模)。Kotlin语言中的*运算符还可作为传值符号,支持将一个数组赋值给一个包含可变长输入参数的方法。
Kotlin语言还为常见数学运算提供了实现方法,这些方法可被调用,并能完成相应计算任务。例如,位运算可通过以下方式实现[2]。
● shl(bits),类似Java的<<运算,是带符号位左移运算;
● shr(bits),类似Java的>>运算,是带符号位右移运算;
● ushr(bits),类似Java的>>>运算,是无符号位右移运算;
● ushr(bits),类似Java的<<<运算,是无符号位左移运算;
● and(bits),位上的and(和)运算;
● or(bits),位上的or(或)运算;
● xor(bits),位上的xor(异或)运算;
● inv(),位上取反。
(4)字符
Kotlin语言中,字符使用类型声明符Char进行说明,字符数据必须使用单引号来表示;例如,将字符'a'赋值给一个变量c,实现语句为:var c: Char = 'a'。区别于Java语言,Kotlin语言中的一个字符不能被当作数字来直接使用,但可使用to Int()方法实现从字符到整型的转换。对于特殊字符,可使用转义符:\;例如:\t(制表)、\b(退格)、\n(换行)、\r(回车)、\'(单引号)、\"(双引号)、\\(斜杠) 和\$(美元符号) 等;此外,还可使用Unicode转义语法,例如:’\u FFFF’。
(5)布尔型数据
Kotlin语言中的布尔型数据类型为Boolean,基本的取值为:true(真)和false(假)。对于布尔型数据的运算,Kotlin语言包含:||(或运算)、&&(与运算)、!(否运算)等。
(6)数组
Kotlin语言中的数组基于Array类实现,Array类中常用的操作包含[2]:size(数组元素个数)、set(设值)、get(取值)等。创建数组使用array Of或array Of Nulls方法。例如,创建一个字符串数组,且数组包含的元素有:{"this", "is", "an", "array", "of", "Strings"},则使用array Of("this", "is","an", "array", "of", "Strings")语句创建字符串数组;另外,当需要定义一个空的字符串数组时,可使用array Of Nulls<String>语句。在实际程序中,这些方法的使用如下列示例所示。
1 package niltok.demos
2
3 fun main(args: Array<String>){
4 var strs1: Array<String> = array Of("this", "is", "an", "array", "of", "Strings")
5 var strs2: Array<String?> = array Of Nulls<String>(2)
6 println(strs1[0]) //显示第一个字符串的第一个元素
7 println(strs2.get(0)) //显示第二个字符串的第一个元素
8 }
运行时,上述程序初始化了两个字符串数组strs1和strs2(程序中的var为变量定义说明符)。其中,strs2是一个长度为2的空字符串数组。程序第6行将打印显示strs1中的第0位元素(即“this”),而程序第7行将打印显示strs2中的第0位元素,实际的结果为空(“null”)。程序中的数组可使用“[]”操作符来实现基于位置的元素访问,例如,strs1[0]表示获取strs1数组中的第0号元素。
需要特别注意的是,Kotlin程序在声明变量时,如果变量类型后使用了符号“?”,则表示该变量可为空;但若变量类型后未使用“?”符号,则该变量不能被赋空值(null)。
Kotlin语言的类库中还为基础数据类型定义了特定的数组类,如Byte Array(字节型数组)、Short Array(短整型数组)、Int Array(整型数组)等。这些类与Array类的使用方法类似。
数组初始化可基于“工厂函数”实现。例如,下列示例程序中的“{i->i+1}”为一个工厂函数。在程序第1行中,Array(5, {i->i+1})语句第1个参数用于指定数组的长度,程序中使用了数值5(数组中,实际元素的位置索引则为0,1,2,3,4);而Array(5, {i->i+1})语句中的第2个参数{i->i+1}可实现这样的功能:将元素索引值加1,并将值设置为数组中对应元素的值,即i的取值范围为0至4,而数组中对应元素的数值为i+1。
1 var ins = Array(5, {i->i+1})
2 for (i in ins){
3 println(i)
4 }
(7)字符串
Kotlin程序中的字符串为String类型,字符串为不可变更的数据类型。字符串中的字符可通过字符元素的位置进行访问;字符串中可使用转义字符;另外,可使用3个双引号(“““…”””)来表示一个自由格式的字符串,如多行字符串。
Kotlin程序中的字符串可使用模板表达式,基本格式为$标识名。下列程序展示了模板表达式的使用:
1 fun main(args: Array<String>){
2 val i = 9
3 val s = "$i is in the string" //$i为一个模板表达式
4 val s1 = "\'$s\'.length is ${s.length}" //$s和${s.length}为模板表达式
5 println(s)
6 println(s1)
7 }
上述程序中除了$i和$s为基本的模板元素,${s.length}是基于模板来显示一个操作结果。${s.length}中,s.length是一个运算操作,含义是获得字符串s的长度,而${s.length}则将实际的结果组织到字符串s1中。上述程序运行结束,$i位置显示9,$s位置显示字符串“9 is in the string”,${s.length}为s字符串的长度18。
(8)空值
程序中可使用空值null。当变量、常量、参数或返回值中可包含空值时,在声明时必须使用符号“?”。例如,var a: Int? 语句说明变量a是可为空的整型变量。需要特别说明的是,在程序中,当变量、参数或返回值声明中未使用?号,则表示该变量、参数或返回值不能为空,否则相关程序语句为非法语句。空值的检查可使用比较符==,该比较符所计算的结果为布尔值,如 if (a ==null){…}。
(9)数据类型的检查与转换
程序中,数据类型检查使用操作符is(是)或 !is(不是),其中,!is是is的否操作。例如,当检查变量a是否为一个整型时,使用if (a is Int){…}。针对空值null,is或!is操作无效,可使用==完成相关的比较操作。
类型转换可使用操作符as。若类型转换过程中可能会发生违例的情况,则这样的类型转换被称为不安全转换;例如,当变量a为null(空值)时,var b: Int = a as Int为不安全转换,可使用var b: Int? = a as Int? 进行控制。另外,可使用as? 操作符号进行安全转换。
Kotlin程序中的数据类型会根据具体情况进行类型的智能转换。智能转换主要指无须直接使用类型转换操作符的情况,例如,println(1)语句中,整型数据被智能转换为字符串。
1.1.2 包
Kotlin中关于“包”的概念与Java中的“包”相似。在程序中,package命令是用来声明程序包的信息,而import则是用来加载程序包的命令。
在应用程序中,“包”技术主要用于建立程序的名称空间,并避免在不同范围内的同名概念之间可能会产生的冲突。例如,A组织在开发程序时定义了Person类,B组织开发程序时也使用Person作为类名;若不使用名称空间,当A组织的Person类和B组织的Person类在同一个程序中工作时,这两个同名类会发生概念冲突;因为,程序执行工具无法正确区分两者之间的区别。面对这样的问题,可使用名称空间技术来解决问题。例如,A组织的程序定义包a,B组织定义包b,当两个类在同一个程序中相遇时,实际上它们被解释为a.Person和b.Person;这样,两个类在同一个程序中可正常工作,也有效地解决了同名概念所引起的冲突问题。
包在声明时,建议基于程序编制单位的网址来进行命名,例如,假设程序员隶属于A组织,网址为a.test,而当前正在开发的项目名称为DEMO,则包的命名可以为test.a.demo。
Kotlin平台本身存在大量预定义的程序包;另外,由于Kotlin可与Java程序相互协作,编写程序时,可加载技术环境中可用的Java程序包。Kotlin应用程序在编写和运行时,系统预加载包包含java.lang.*、kotlin.jvm.*、kotlin.js.*等(其中,符号*表示“所有类库包”),而预加载的开发类库包含[2]:
● kotlin.*
● kotlin.annotation.*
● kotlin.collections.*
● kotlin.comparisons.*
● kotlin.io.*
● kotlin.ranges.*
● kotlin.sequences.*
● kotlin.text.*
1.1.3 程序的控制结构
Kotlin程序中常用的控制结构包含if结构、when结构、for循环、while循环。其中,if和when可作为表达式直接使用。
(1)if结构
if结构的基本格式如下:
if (条件){
程序语句1
…
}else{
程序语句2
…
}
if结构执行时,首先对条件部分进行判断;若条件判断为真,则从“程序语句1”开始执行,当程序执行至第1个“}”时结束;若条件判断为假,则从“程序语句2”开始执行,当程序执行至第2个“}”时结束。Kotlin程序可将if结构作为表达式,放在赋值语句的右侧,例如,下列语句将结构运算结果直接赋值给变量value:
1 var value = if (a>b) {a} else {b}
(2)when结构
when结构类似Java中的switch语句,基本格式为:
when (变量){
值1 -> 语句1
值2 -> 语句2
…
else -> 语句n
}
上述结构在运行时,基本的工作过程为:程序首先对“when (变量)”部分中的“变量”进行计算判别,并将结果与结构中的分支条件(即结构中箭头的左侧部分)进行匹配,若某分支条件匹配成功,则该分支所对应的程序语句执行(箭头右侧代码)。when结构在执行过程中,只要一个分支条件被执行,则其他分支条件将不再参与匹配运算;另外,when结构可指定默认执行程序,默认执行程序的分支判断条件为else,也就是说,若其他任何一个分支条件都不满足时,else条件满足,所对应的程序开始工作。
when结构中的分支条件可以使用逗号进行组合,当使用逗号时,两个分支条件之间的关系类似于条件间的“或”关系;另外,分支条件中可以使用表达式,或者in、!in、is、!is等操作符(其中,!is是is的否操作,!in是in的否操作)。例如,分支条件可以是“条件1,条件2”“in 范围”“!in 范围”“is 类型”“!is 类型”等。
(3)for循环
for循环用于控制程序代码段的多重循环,最常见的应用场景为数据或对象集合的遍历。Kotlin中,for循环的常见结构为:
for (变量 in 集合){
关于变量的执行语句
}
注意,传统for(…;…;…)结构在Kotlin中已不被支持。
上述结构中,程序段循环的次数基于“集合”中的元素个数确定。需要注意,上述结构可以运行的基本条件为,集合对象必须提供内置的迭代器(iterator)。对于一个数组,若想通过数组索引(元素位置)来访问数组,可使用数组实例的indices属性来获得索引集合,例如:
1 for (idx in numbers.indices){
2 //执行程序
3 }
另外,可使用数组实例的 with Index 方法获取数组中的键值对,例如:for ((k, v) in strs.with Index()){…}。
(4)while循环
Kotlin中可使用两种while循环,基本结构为:
while (判断条件){ //while循环结构1
执行语句
…
}
do{ //while循环结构2
执行语句
…
} while (判断条件)
上述结构中,第1个结构的工作原理为:程序首先判断while的判断条件;当条件满足时,运行结构中的程序语句;当语句执行结束,程序再次进行条件判断,若条件满足,则继续执行结构中的程序语句,直到判断条件不满足为止;最后,while循环结束。第2个结构的工作原理为:程序首先执行结构中的程序语句(即do{…}中的所有语句),语句执行完毕,对while条件进行判断,若条件满足则再次执行结构中的程序语句;这样的过程一直到while判断条件不被满足为止。
1.1.4 返回值与循环结构的跳转
当方法或函数需要返回值时,程序语句中需要使用return命令,例如:return 123。
循环结构的跳转主要包含两个命令,即break和continue。其中,break命令是终止当前循环;continue是跳出当前循环,继续后续循环。下列示例程序展示了break和continue的工作原理:
1 fun main(args: Array<String>){
2 for (i in 1..10){
3 println("index: " + i.to String())
4 if(i == 2)
5 continue
6 println("after continue")
7 if(i == 4)
8 break
9 println("after break")
10 }
11 }
上述程序使用for结构遍历1至10之间的数字。程序在变量i为2时,由于使用了continue命令,该语句之后的语句都不会执行;随后i为3,程序继续执行其他语句;当i为4时,程序第8行使用了break语句,则该语句的后续语句不会被执行,且循环被终止。程序运行的结果为:
1 index:1
2 after continue
3 after break
4 index: 2
5 index: 3
6 after continue
7 after break
8 index: 4
9 after continue
1.1.5 集合类型
除了数组结构外,Kotlin中的集合类型包含列表(List)、集合(Set)、字典(Map)等;Kotlin中,集合类型分为可修改和只读两种[2]。
列表结构类似于数组,但与数组相比较,列表的长度大小可在程序运行时被动态调整。列表中的元素必须为相同类型,而且,在一个列表中可以存在多个值相同的元素。与列表相比,集合(Set)类型是多个相同类型元素的一个集合,但集合中的元素不允许重复。字典类型的结构相对复杂,该类型中的元素按“键-值”对方式进行组织;每个元素具有“键”值和“值”项两个部分,其中,该键值用于标识一个元素,而“值”项则用于存储该元素的具体数值。
Kotlin中,只读列表基于List<T>接口定义,可修改列表基于Mutable List<T>定义;类似,Set<T>为只读集合,Mutable Set<T>为可修改集合;Map<K, V>为只读字典,Mutable Map<K, V>为可修改字典。
初始化集合类型时,推荐直接调用系统提供的标准方法:list Of、mutable List Of、set Of、mutable Set Of、map Of、mutable Map Of等。复制一个集合类型的数据,可使用的方法为to Map、to List、to Set等。
以字典为例,若想创建、访问并扩展一个具有3个元素的字典,相关程序如下:
1 fun main(args: Array<String>){
2 val m = mutable Map Of("k1" to "v1", "k2" to "v2", "k3" to "v3")
3 println(m.get("k2"))
4 m.put("k4", "new value")
5 println(m.get("k4"))
6 }
上述程序中,第2行使用mutable Map Of创建一个字典,该数据结构初始化时具有数据{“k1:v1”, “k2: v2”, “k3: v3”};mutable Map Of中,一个键值对按“键 to 值”方式进行声明;第3行,程序访问字典(结构)中键为“k2”的值,并进行打印显示;第4行,程序在结构中增加一个数据项“k4: new value”;第5行,程序访问字典(结构)中键为“k4”的值,并进行打印显示。程序运行的结果为:
1 v2
2 new value
1.1.6 数值范围
Kotlin可以直接使用数值范围表达式:..(两个点)。例如,1..10表示范围1至10(整数)。在for循环中使用范围时需要注意,for (i in 1..10)是可工作的,但for (i in 10..1)是不可工作的。当范围起始值大于终止值时,可使用类似于for (i in 10 downto 1)的语句来进行程序控制。在循环语句中使用范围表达式还可控制变量访问的步长,例如for (i in 1..10 step 2),表示从1开始每次前进2步,至10终止。另外,当不需要使用某个范围的终止值时,可使用关键字until,例如for (i in 1 until 10),表示数值范围是从1开始,并至9终止。
1.1.7 等式
Kotlin可使用两种等式运算符:===和==;其中,==用于值或结构相等关系的判断(!=为对应的不相等关系的判断);===用于应用对象相等关系的判断(!==为对应的不相等关系的判断),例如,在下列语句中,===被用于对象直接的比较判定:
1 var o = My Class()
2 var oo = o
3 oo === o //本语句为真
1.1.8 操作符
Kotlin基本的操作符号包含以下几种。
● 一元前缀操作符:+(正)、−(负)、!(非);
● 递增、递减:++和--,例如:a++或a--;
● 数学操作符:+(加号)、−(减号)、*(乘号)、/(除号)、%(取模)、..(范围)等;
● 在范围中进行查询或遍历的in操作符:in和!in;
● 数组基于位置索引的访问符:[],例如:a[i]、b[i, j]、c[i_2]等;
● 扩展赋值符:+=(累加)、−=(累减)、*=(累乘)、/=(累除)、%=(累积取模),例如:a += b与a = a+b相同;
● 比较操作符:==(等)、!=(不等)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于)。
1.1.9 其他操作符
Elvis操作符格式为:被判断对象 ?:返回值。例如:n ?:“nothing”语句与if(n!=null) n else“nothing”等价;再例如:o?.length ?: 0语句与if(o!=null) o.length else 0的含义相同。
另外,!!操作符会对被操作对象进行检查,如果该对象为空值时,操作符会掷出违例,例如:s!!.length语句中,如果s为空,则程序会产生违例。
1.1.10 违例处理
在应用程序开发、运行过程中,违例是在所难免的。所谓违例是指程序运行过程中可能会发生的错误。违例产生原因有:①程序语句使用错误;②运行过程中,程序运行的外部条件不能满足程序运行的需求而引发的执行错误等。
Kotlin程序中的所有违例类从Throwable类继承。程序运行时,一个违例对象包含了关于违例的描述信息,具体包含:错误、程序堆栈信息和错误原因。在编写程序时,违例的掷出需要使用throw命令,例如:throw My Exception(“messages”)语句在执行时会产生一个My Exception类型的违例。程序中,throw命令为特殊类型Nothing;如果某方法在定义时,只实现了throw语句,则该方法的返回值可使用类型Nothing。
Kotlin中,违例处理的结构与Java语言中的违例处理类似,基本结构为:
try{
操作语句
…
}catch(e: 违例类型){
违例处理
…
} finally{
收尾操作语句
…
}
上述结构运行时的基本流程如下。
● 程序首先执行try代码块,若没有发生错误,程序执行finally块中的程序语句;
● 若try代码块发生错误,则catch语句捕获违例,并执行catch代码块;最后,程序执行finally代码块。
在违例结构中,finally代码块一般用于实现与程序或程序违例相关的补偿、维护等性质的工作,在不必要的情况下,finally代码块也可省略。下列程序展示了违例处理的工作过程;其中, try块中的程序发生访问错误;catch块的语句被执行;最后,finally块的程序被执行。运行结果为“error”和“finally”。
1 fun main(args: Array<String>){
2 var n = array Of(1, 2, 3)
3 try{
4 n[4] //本语句会产生违例
5 }catch(e: Exception){ //捕获违例
6 println("error")
7 }finally { //后续工作
8 println("finally")
9 }
10 }