基于Kotlin的Android应用程序开发
上QQ阅读APP看书,第一时间看更新

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 }