无论使用什么语言编程,总要处理数据,处理数据就需要使用变量来保存数据,形象地看,变量就像一个个小容器,用于“盛装”程序中的数据。常量同样也用于“盛装”程序中的数据,常量与变量的区别是:常量一旦保存某个数据之后,该数据就不能发生改变;但变量保存的数据则可以多次发生改变,只要程序对变量重新赋值即可。
1.1 分隔符
Kotlin语言里的分号(;)、花括号({})、方括号([])、圆括号(())、空格、圆点(.)都具有特殊的分隔作用,因此被统称为分隔符。
1. 分号
与Java不同,Kotlin并不强制要求每条语句必须以分号结尾,当然也可以使用分号表示语句结束。正如前面程序所看到的,如果单独一行内只有一条语句,Kotlin允许这条语句不以分号结尾。
但如果打算在同一行内书写多条独立的语句,则前面语句需要使用分号表示结束。例如如下语句:
var name = "fkit"; println(name)
Kotlin允许一条语句可以跨多行,例如,如下语句都是正确的:
var str = "fkit"
// 表达式可以跨多行
str +=
".org"
// 调用方法时可以跨多行
var hasFk = str
.startsWith("fk")
var st = "fkit"
// 访问属性时也可以跨多行
var len = st
.length
值得指出的是,一个字符串、变量名不能跨多行。例如,下面的Kotlin语句是错误的:
// 字符串不能跨多行
var a = "dddddd
xxxxxxx"
// 变量名不能跨越多行
var na
me = "李刚"
不仅如此,虽然Kotlin语法允许一行书写多条语句,但从程序可读性角度来看,应该避免在一行书写多条语句。
注意:Kotlin语句可以跨越多行书写,但字符串和变量名不能跨越多行。虽然Kotlin语法允许一行书写多条语句,但从程序可读性角度来看,应该避免在一行书写多条语句。
2. 花括号
花括号的作用就是定义一个代码块,一个代码块指的就是“{”和“}”所包含的一段代码,代码块在逻辑上是一个整体。对Kotlin语言而言,类体部分、枚举需要放在花括号中定义,方法体也必须放在花括号中定义。除此之外,条件语句中的条件执行体和循环语句中的循环体通常也放在代码块里。
花括号一般是成对出现的,有一个“{”则必然有一个“}”,反之亦然。后面将会详细介绍花括号分隔符的用法,此处不再赘述。
3. 方括号
Kotlin的方括号是一个索引运算符,方括号的主要作用是用于访问数组元素、List集合元素和Map集合元素,方括号通常紧跟数组变量名、List变量名字或Map变量名,而方括号里指定希望访问的数组元素、List元素的索引或Map元素的key。
例如如下代码:
// 下面代码试图对名为a的数组的第四个元素赋值
a[3] = 3
// 下面代码试图对名为scores的字典中key为Kotlin的元素赋值
scores["Kotlin"] = 92
关于方括号分隔符的用法,后面还会有更进一步的介绍,此处不再赘述。
4. 圆括号
圆括号是一个功能非常丰富的分隔符,定义函数、方法时必须使用圆括号来包含所有的形参声明,调用函数时也必须使用圆括号来传入实参值。不仅如此,圆括号还可以将表达式中某个部分括成一个整体,保证这个部分优先计算。
关于圆括号分隔符在后面还有更进一步的介绍,此处不再赘述。
5. 空格
Kotlin语言使用空格分隔一条语句的不同部分。Kotlin语言是一门格式自由的语言,所以空格几乎可以出现在Kotlin程序的任何地方,也可以出现任意多个空格,但不要使用空格把一个变量名隔开成2个,这将导致程序出错。
Kotlin语言中的空格包含空格符(Space)、制表符(Tab)和回车符(Enter)等。
除此之外,Kotlin源程序还会使用空格合理地缩进代码,从而提供更好的可读性。
6. 圆点
圆点(.)通常用作类、结构体、枚举、实例和它的成员(包括属性和方法)之间的分隔符,表明调用某个类或某个实例的指定成员。关于圆点分隔符的用法,后面还会有更进一步的介绍,此处不再赘述。
1.2 标识符规则
所谓标识符就是用于给程序中的变量、类、枚举、函数等命名的名字。Kotlin语言的标识符必须以字符(包括Unicode字符)、下画线(_)开头,后面可以跟任意数目的字符、数字和下画线(_)。此处的字符并不局限于26个英文字母,可以包含中文字符、日文字符等。标识符中不可以包含空格,不能使用Kotlin的硬关键字,特定上下文内不允许使用软关键字,标识符的长度没有限制。
Kotlin语言是区分大小写的,因此abc和Abc是两个不同的标识符。
使用标识符时,需要注意如下规则。
- 标识符可以由字符、数字和下画线(_)组成,但不能以数字开头。
- 标识符不能是Kotlin的硬关键字,特殊上下文中不能使用软关键字和修饰符关键字,但可以包含关键字。
- 标识符不能包含空格。
- 标识符只能包含下画线(_),不能包含@、#等其他特殊字符。
1.3 Kotlin的关键字
Kotlin语言中有一些具有特殊用途的单词被称为关键字(keyword),当定义标识符时,不要让标识符与关键字相同,否则将引起错误。例如,如下代码将会引起错误:
// 下面代码试图定义一个名字为class的变量,但class是关键字,不能作为标识符
var class = 2
Kotlin的关键字可分为3类:
Kotlin的关键字可分为3类:
硬关键字:这些关键字无论在什么情况下都不能用作标识符。 软关键字:这些关键字可以在它们不起作用的上下文内用作标识符。
修饰符关键字:这些关键字也可以在代码中用作标识符。
Kotlin的硬关键字包括:
- as:用于做类型转换或为import语句指定别名。
- as?:类型安装的 is used for safe type casts
- break:中断循环。
- class:声明类。
- continue:忽略本次循环剩下的语句,重新开始下一次循环。
- do:用于do while循环。
- else:在if分支中使用。
- false:Boolean类型中表示假的直接量。
- for:用于for循环。
- fun:声明函数。
- if:在if分支中使用。
- in:在for循环中使用;in还可作为双目运算符,检查一个值是否处于区间或集合内;in还可在when表达式中使用;in还可用于标识一个形参为传入参数。
- !in:可作为双目运算符in的反义词;!in也可在when表达式中使用。
- is:用于做类型检查(类似Java的instanceof)或在when表达式中使用。
- !is:用于做类型检查(is的反义词)或在when表达式中使用。
- null:代表空的直接量。
- object:用于在声明类时创建它的实例。
- package:用于为当前文件指定包。
- return:声明函数的返回。
- super:用于引用父类实现的方法或属性或在子类构造器中调用父类构造器。
- this:代表当前类的对象或在构造器中调用当前类的其他构造器。
- throw:用于抛出异常。
- true:Boolean类型中表示真的直接量。
- try:开始异常处理。
- typealias:用于定义类型别名。
- val:声明只读属性或变量。
- var:声明可变属性或变量。
- when:用于when表达式。
- while:用于while循环或do while循环。
- Kotlin的软关键字包括:
- by:用于将接口或祖先类的实现代理给其他对象。
- catch:异常处理中用于捕捉异常。
- constructor:用于声明构造器。
- delegate: is used as an annotation use-site target
- dynamic:主要用于在Kotlin/JS引用一个动态类型。
- field: is used as an annotation use-site target
- file: is used as an annotation use-site target
- finally:异常处理中finally块。
- get:声明属性的getter方法或is used as an annotation use-site target
- import:用于导包。
- init:用于声明初始化块。
- param:is used as an annotation use-site target
- property:is used as an annotation use-site target
- receiveris:used as an annotation use-site target
- set:声明属性的setter方法或is used as an annotation use-site target
- setparam:is used as an annotation use-site target
- where:用于为泛型参数的增加限制。
- Kotlin的修饰符关键字包括:
- abstract:修饰抽象类或抽象成员。
- annotation:修饰一个注解类。
- companion:用于声明一个伴生对象。
- const:用于声明编译时常量。
- crossinline: forbids non-local returns in a lambda passed to an inline function
- data:用于声明数据类。 instructs the compiler to generate canonical members for a class
- enum:用于声明枚举。
- external:用于声明某个方法不由Kotlin实现(近似于Java的native)。
- final:禁止被重写。
- infix:声明该函数能以双目运算符的格式执行。
- inline tells the compiler to inline the function and the lambdas passed to it at the call site
- inner:声明内部类,内部类可以访问外部类的实例。
- internal:用于标识被修饰的声明只能在当前模块内可见。
- lateinit:修饰一个non-null属性,用于指定该属性可在构造器以外的地方完成初始化。
- noinline turns off inlining of a lambda passed to an inline function
- open:修饰类表示该类可派生子类或修饰成员变量该成员可以被重写。operator marks a function as overloading an operator or implementing a convention
- out marks a type parameter as covariant
- override marks a member as an override of a superclass member
- private:private访问权限。
- protected:protected访问权限。
- public:public访问权限。
- reified:marks a type parameter of an inline function as accessible at runtime
- sealed:声明一个密封类。
- suspend:标识一个函数后Lambda表达式可作为suspending。
- tailrec:修饰一个函数可作为尾随递归函数使用。
- vararg: allows passing a variable number of arguments for a parameter
1.4 声明变量
Kotlin是强类型的语言,Kotlin要求所有的变量必须先声明、后使用,声明变量时必须显式或隐式指定变量的类型,类型限制了一个变量能被赋的值,也限制了一个表达式可以产生的值,还限制了在这些值上可以进行的操作,并确定了这些操作的含义。
声明变量需要使用var或val,如下所示:
var|val 变量名 [:类型] [= 初始值]
其中var声明的变量是值可变的(可被多次赋值),val声明的变量是值不可变的(不能被重新赋值).
在上面声明变量的语法格式中,程序要么通过“: 类型”的形式显式指定该变量的类型,要么为该变量指定初始值——Kotlin编译器将会根据该初始值确定变量的类型,不能声明变量时既不指定变量类型,也不指定变量初始值。声明变量时可以既显式指定变量的类型,也指定该变量的初始值,但显式指定的变量类型必须与初始值的类型保持一致。
例如如下代码。
程序清单:codes\02\2.2\VarTest.kt
public fun main(args: Array<String>) {
// 声明变量时显式指定类型
var b : Int
// 声明变量时指定初始值,编译器会根据初始值确定该变量的类型为String
var name = "crazyit.org"
b = 20 // b的类型是Int(整型),赋值正确
name = "fkit.org" // name类型为String,赋值正确
name = 12 // name类型为String,但12为Int,赋值错误
// 声明变量时既显式指定类型,也指定初始值
// 显式指定的类型与初始值的类型一致,声明变量正确
var age: Int = 25
age = 12 // age的类型是Int,赋值正确
// 声明变量时既显式指定类型,也指定初始值
// 显式指定的类型与初始值的类型不一致,声明变量失败
var sun : String = 500
val book = "疯狂Kotlin讲义"
book = "a" // val声明的变量是不可变变量,不能被重新赋值
}
对上面程序中5行粗体字代码的解释如下。
第1行粗体字代码声明变量时只显式指定了变量的类型,并未指定初始值,因此变量b只能接受Int类型的值。
第2行粗体字代码声明name变量时并未显式指定变量的类型,但程序为该变量指定的初始值为字符串"crazyit.org",因此编译器可以确定该变量的类型为String,该变量只能接受字符串类型的值。
第3行粗体字代码声明age变量时既指定了该变量的类型为Int,也指定了该变量的初始值为25,程序指定的变量类型与变量初始值的类型是一致的,因此程序正确。
第4行粗体字代码声明sun变量时指定了该变量的类型为String,但程序又尝试为该变量指定500作为初始值,这样显式指定的变量类型与变量的初始值类型并不一致,因此这行代码是错误的。
第5行粗体字代码使用val声明了不可变的变量,因此该变量不允许被重新赋值,所以接下来为book重新赋值代码有错误。
使用val声明的不可变变量其实相当于常量,常量意味着它的值一旦被初始化之后,将不可以被重新赋值。根据常量所在的位置不同,Kotlin的常量分为2种。
局部范围的常量:这种常量允许在声明时不指定初始值,只要在第一次使用之前指定初始值即可。
类的常量属性:这种常量属性既可以在声明时指定初始值,也可以在类或结构体的构造器中指定初始值。
程序清单:codes\02\2.2\ValTest.kt
fun main(args: Array<String>) {
// 定义常量,没有显式指定类型,编译器根据初始值确定常量的类型
val maxAge = 120
// 定义常量时,既显式指定了常量的类型,也指定了常量的初始值
val eduName : String = "疯狂软件教育"
// 常量不允许重新赋值,因此下面代码是错误的
// maxAge = 12
// 同时定义多个变量
// 局部范围的常量,声明时不指定初始值
val herName: String
}
对上面程序中3行粗体字代码的解释如下。
第1行粗体字代码声明常量时只指定了常量的初始值,编译器将根据初始值来确定该常量的类型。
第2行粗体字代码声明eduName常量时既显式指定了该常量的类型,也指定了该常量的初始值。
第3行粗体字代码使用val声明了一个String类型的常量,但并未指定初始值——这是因为局部范围的常量,并不要求在定义时指定初始值,只要在第一次使用之前指定初始值即可。
需要指出的是,由于Kotlin程序编译的字节码必须遵守JVM规范,因此如果直接在Kotlin程序中定义变量、函数,kotlinc会将自动生成一个名为:文件名首字母大写+Kt的类,并将变量转换为该类的静态的getter、setter方法(其中val声明的只有getter方法),函数则转换为该类的静态方法。
注意:当使用kotlinc编译Kotlin程序所包含的函数、变量时,这些函数、变量都会转换成该文件对应类的静态方法。
另有一点需要说明的是:由于Kotlinc会为包含函数、变量的Kotlin程序生成额外的类,这就要求我们不能在该包下重复定义同名的类。例如,我们定义了一个名字为ligang.kt的Kotlin程序,且该程序中包含了函数或变量,那么kotlinc就会自动生成LigangKt类,因此我们就不能在该包下重复定义LigangKt类。