语法入门
作为一门拥有现代化编程思想的新兴语言,Kotlin语言拥有很多其他成熟语言且根据程序员使用和验证的特性。在抛弃了一些确定的同时也吸收了一些优点,可以说采取了各大语言的精华。同时,对于Kotlin来说,也有自己语言由于其他语言的特性。如:在字符上全面支持了Unicode符号、表达式的行尾可以不用写分好 “;” 、变量和常量类型定义可以由系统推断而得。在Java语言之中存在的基础数据类型(int、long等)和引用数据类型(Integer、Long等)在使用上经常容易弄错和混淆,Kotlin语言上放弃了对基本数据类型的支持,完全屏蔽了使用不当引发的异常。下面,我们从几个基本语法的定义入手,开始一步步的解开Kotlin的神秘面纱。
变量(var)和常量(val)
大多数的高级程序语言使用变量和常亮来存储和维护特定类型的数据或者值,Kotlin语言和其他高级语言一样,会维护自己的变量和常量类型。变量,即在声明之后还可以改变值。常量,在声明后不允许改变值。通常,我们可以这么理解,常量只能够在初始化的时候进行一次赋值,而变量随时都可以。在Kotlin语言之中,声明变量使用关键字var,声明常量则使用关键字val。
var x: Int // 变量
val y: Int // 常量
Kotlin语言是一门强类型语言,所有已经定义好类型的标量,不能赋给其他类型的值。即使,我们希望把整数型变量 “x” 的,赋给它一个浮点型的值,也是错误的做法。这种强类型校验机制,能够将语法错误消灭在编译期间,避免上线后Bug难以跟踪查找。
打开IntelliJ IDEA打开之前新建好的Kotlin项目,新建一个kotlin文件,在main方法中测试一下,输入如下内容。
var x: Int
val y: Int
var u = 1
val v = 1
var z: Int = 1
val w: Int = 1
val a // 编译报错 , 必须要有初始类型
x = 1.28 // 编译报错,类型不一致
y = 2 // 第一次赋值,不会报错
z = 3
w = 4 // 编译报错,重复两次给常量赋值
和其他JVM语言一样,Kotlin也是实时编译(JIT)进行语法检查的,如上诉例子,我们如果定义了常量w,给其进行二次赋值的时候,IDE就会给我们报错提示。
这里有几点值得注意的是:
1. 声明时类型是可选的,如果再声明时没有指定类型且对变量赋予初始值,编译器会自动推断常量或者变量的类型,这种机制被称为“推断判断”。如果声明时候既指定了类型又赋予初始值,那么指定的类型必须和赋予的值的类型一致。
变量或者常量在指定数据类型的时候,必须使用冒号 “:” 将变量或者常量与数据类型分开。如果,采用直接赋值的形式,系统则会自动推断,这个时候使用 “:” 指定的数据类型则可忽略不写。
- 当成功声明某个变量或者常量之后,不能再声明相同名字的变量或者常量,并且声明成功后变量和常量不能相互转化。
- 表达式作为程序代码最小的代码单位,无数的表达式组成了我们的程序。Kotlin和Java一样,同样允许我们使用 “;” 作为表达式的分割符。如果一行之中,存在多个表达式我们就必须使用 “;” 将表达式区分出来。如果一行之中只存在一个表达式,或者是一行之中最后一个表达式,那么我们就可以不用写 “;” 。如:
x = 1; x = 2; x = 3
// 最后一个表达式,可以不加 ";"
Kotlin之中存在整数型、浮点型,它们两个类型是不一样的,后面我们会具体介绍,所以Int类型不能赋值为1.28。
Kotlin中所定义的类型不存在默认值,没有进行初始化,不能进行操作。
注释(//、/**/)
代码注释本身不参与代码运行逻辑,是为了开发者更易于阅读理解整个代码的逻辑。在Kotlin语言中,的注释书写和Java语言完全一样。注释的书写方式有两种,分别是单行注释 “//” 和 多行注释“ \/**\/ ” :
// 这里是注释
/*
这里也是注释
我可以注释很多行
*/
整型(Int、Long、Short、Byte)
所说的整型就是我们在数学中所说的 正整数、负整数和零。Kotlin语言继承了Java语言中的整型Integer、Long的特性,Kotlin中的整型,用关键字 “Int” 表示,长整型用关键字 “Long” 表示。Long所表述的整型范围比Int的更为广泛,在系统中所占用的存储空间也越大。在Kotlin语言中,如果我们采用直接赋值的形式定义一个变量为整型,那么系统会默认推断它的类型为Int。如果希望指定类型为Long,可以在复制的数字后面紧贴一个字母“L”,表示类型为Long。
var IntA: Int
val IntB: Long
var IntC = 12 // 直接赋值,默认推断为Int
var IntD = 24L // 指定类型为Long
val IntE : Long = 48 // 制定类型为Long,并赋值为48
当然,这个整型的极大值和极小值并不是理论上的无穷,它的长度是固定的。在实际编码开发过程中,我们可以使用Int.MAX_VALUE、Int.MIN_VALUE、Long.MIN_VALUE、Long.MIN_VALUE来获取最大值和最小值。
println("Int max value = " + Int.MAX_VALUE)
println("Int min value = " + Int.MIN_VALUE)
println("Long max value = " + Long.MAX_VALUE)
println("Long min value = " + Long.MIN_VALUE)
println方法是Kotlin语言中的标准输入写法, 类似Java语言中的System.out.println。能将内存中的值打印到屏幕上。
运行后得到:
Int max value = 2147483647
Int min value = -2147483648
Long max value = 9223372036854775807
Long min value = -9223372036854775808
即整数Int类型的范围为:
2^{31}-1 \thicksim
-2^{31}
整数Long类型的范围在64位的设备上为:
2^{63}-1 \thicksim
-2^{63}
在整个整型的领域,除了Int和Long还存在两个长度比较短的整型Short、Byte。它们的使用方式与Int和Long完全相同,但相对于Int和Long来说,Short的长度只有16个字节,而Byte只有短短的8个字节。
值得一提的是:
Kotlin语言中的整型Int和Long类似于Java语言中的Integer和Long,Kotlin语言中所有东西都是对象,一切接皆为对象,不存在基础类型int、long、float、double。
浮点型(Float、Double)
浮点型也是我们常用的类型之一,与整型对比很好区别,拥有小数部分的数值就是浮点数。Kotlin语言为我们提供了两种有符号的浮点数类型,即 “Double” 和 “Float” 。Float是32位浮点数类型,Double是64位的浮点数类型。即,Double能够显示的精度比Float显示的精度高。浮点型的声明过程和整数型的声明过程基本相同,但值得注意的是,当使用直接赋值的形式定义一个变量或者常量为浮点型的时候,系统默认推断为Double类型。如果,希望指定类型为Float,我们可以在赋值后面紧跟一个字母 “F” 或者 “f”,表示类型为Float。
var floatA: Float
val floatB: Double
var floatC = 12.34 // 直接赋值,默认推断为Double
var floatD = 56.78F // 指定类型为Float
val floatE : Float = 90.12F // 指定类型为Float,并赋值为90.12
因为Double的精度二进制位数刚好是Float精度的二进制位数的一倍,所以我们又成Float为:单精度,Double为:双精度。在浮点型中,同样提供了MAX_VALUE和MIN_VALUE来获取类型上的最大值和最小值。这里,我们打印了一下浮点型的取值范围。
println("Float max value = " + Float.MAX_VALUE)
println("Float min value = " + Float.MIN_VALUE)
println("Double max value = " + Double.MAX_VALUE)
println("Double min value = " + Double.MIN_VALUE)
运行后,我们获得如下结果
Float max value = 3.4028235E38
Float min value = 1.4E-45
Double max value = 1.7976931348623157E308
Double min value = 4.9E-324
和整数型不一样,Float、Double两种类型的最小值与 Float.MIN_VALUE、 Double.MIN_VALUE 的值并不相同,实际上 Float.MIN_VALUE 和 Double.MIN_VALUE 分别指的是Float和Double类型所能表示的最小正数。也就是说存在这样一种情况,0 到 ±Float.MIN_VALUE 之间的值Float类型无法表示,0 到 ±Double.MIN_VALUE 之间的值double类型无法表示。这并没有什么好奇怪的,因为这些范围内的数值超出了它们的精度范围。Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的”E+数字”表示E之前的数字要乘以10的多少倍。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。
所以,对于Float类型,它的精度范围则是:
3.4028235 \times 10^{38}-1 \thicksim
1.4 \times 10^{-45}
Double的精度范围则是:
1.7976931348623157 \times 10^{308}-1 \thicksim
4.9 \times 10^{-324}
与整数不同的是,在我们运算中难免会遇到除0或者其他运算上不的错误。所以,浮点型还提供了几个自有值。如:正无穷(POSITIVE_INFINITY)、负无穷(NEGATIVE_INFINITY)、非数字类型(NaN)。
Double.POSITIVE_INFINITY
Double.NEGATIVE_INFINITY
Double.NaN
通常我们将整型、浮点型统称为数字型,每个数字类型都包含以下通用方法,进行相互转化,支持如下:
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
在计算机编程开发上,仅使用十进制的数字很多条件下满足不了我们的需求。常见的进制有二进制、十六进制,那么Kotlin在定义这些非十进制的值上与Java类似。使用 0x 或 OX 前缀表示十六进制,使用0b 或 0B 前缀二进制。如:
val hexBytes = 0xFF
val bytes = 0b1100在Kotlin1.1版本之后,为了方便阅读,可以在定义的值上加上下划线 “_” 做位分割符。如:
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
布尔型(Boolean)
布尔型,即Boolean的谐音,这个类型在高级程序语言中用于条件判断,在整个程序组成中是必不可少的。不同与其他高级语言,如果你在Kotlin语言中直接使用零或者非零来表示逻辑真假,编译器会报错。Boolean类型在Kotlin语言中,只有两种值 “true” 和 “false”,也就是我们通常所说的真和假。你可以在需要用到逻辑判断的地方直接使用布尔值或者使用“==”操作符来比较两个对象是否相等,从而返回布尔值。声明一个布尔类型的变量或者常量可以使用如下格式:
var booleanA: Boolean
var booleanB = true // 类型被推断为,Boolean型
var booleanC : Boolean = false // 定义为Boolean,并赋值为false
// 条件逻辑中使用Boolean型
if (booleanC) {
// do Something
}
// 循环逻辑中使用Boolean型
while (booleanC) {
// do Something
}
println("booleanB = " + booleanB)
println("booleanB = " + !booleanB)
在布尔型中,可以直接在其前面加入 “!” 进行布尔值的取反。如果原值为真,取反后则为假,如果原值为假,取反后则为真。运行后,得到如下结果:
booleanB = true
!booleanB = false
如同取反一样,Boolean类型内置的运算有:
符号 | 说明 |
---|---|
|| | 逻辑或 |
&& | 逻辑与 |
! | 逻辑非 |
字符(Char)
由于全面支持Unicode字符,每一个字符代表一个Unicode字符,在Kotlin语言中表示字符使用 Char 关键字。声明格式如下:
var charA : Char
var charB = 'C' // 推断为Char类型
var charC = '\uFF00' // Unicode 字符
值得注意的是:
1. 字符Char的赋值,必须使用单引号 “ ” ” 括起来。
2. 可以使用 “\” 来表示一些转义字符,如 \t, \b, \n, \r, \’, \”, \ 和 $。
3. 当然其他字符编码,可以使用Unicode编码来展示。如 \uFF00。
Kotlin是一门全面支持Unicode的语言。和ASCII一样,Unicode是一种跨语言、跨平台的文本编码国际标准,Unicode的第十万个字符在2005年被引入成为标准之一。它没有ASCII在多语言环境下的局限性,可以用Unicode表示来自任意语言的所有的字符。
\uxxxx这种格式是Unicode写法,表示一个字符,其中xxxx表示一个16进制数字,范围所0~65535. Unicode十六进制数只能包含数字0~9、大写字母A~F或者小写字母A~F。需要注意到是:Unicode的大小端问题,一般都是小端在前,例如 \u5c0f 表示汉语中的 ‘小’字,转换成10进制就是9215,所以在byte数组中应该是1592.
字符串型(String)
字符串是由数字、字母、下划线和其他字符符号组成的一串字符集合,也可以说是字符的集合,如 “ ”、“hello world” 、“Kotlin is goold language”。Kotlin语言的的字符串类型与Java语言的字符串类型很相似。针对字符串的常用操作,搜索、删除、插入等操作都有相应的API方法可以使用,其声明和操作形式如下:
var stringA : String
var stringB = "hello" // 推断为String类型
var stringC = "I'm a Unicode String \u0041\u2267\u03c9\u2266\u0020" // Unicode字符串
println("stringB length is = " + stringB.length) // 获取字符串长度
println("stringB char at 0 is = " + stringB.get(0)) // 获取stringB 第1个字符
println("stringC is = " + stringC)
Kotlin语言中的字符串类型支持Unicode字符集的特性,我们没有办法从字面字符直接确定每个字符的长度,必须调用String类型提供的API方法来获取字符串的各种属性。如上述例子,运行后得到:
stringB length is = 5
stringB char at 0 is = h
stringC is = I'm a Unicode String A≧ω≦
字符串字面值
Kotlin 有两种类型的字符串字面值: 转义字符串可以有转义字符,以及原生字符串可以包含换行和任意文本。转义字符串很像 Java 字符串:
val s = "Hello, world!\n"
为了避免一些特殊字符和代码产生混淆,高级语言上一般都采用转义的形式,转义采用传统的反斜杠方式,创建的转义字符有:
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\\ | 代表一个反斜线字符”\’ | 092 |
\’ | 代表一个单引号(撇号)字符 | 039 |
\” | 代表一个双引号字符 | 034 |
\? | 代表一个问号 | 063 |
转义字符串在阅读起来不是很直观,很多情况下为了所见即所得。Kotlin提供了三个引号 (”“”) 分界符括起来,这样字符串内部就不会有任何的转义字符出现,看见什么则输出什么。如:
val text = """
for (c in "foo")
print(c)
"""
输出后得到:
for (c in "foo")
print(c)
字符串模板
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:
val i = 10
val s = "i = $i" // 求值结果为 "i = 10"
或者用花括号扩起来的任意表达式:
val s = "abc"
val str = "$s.length is ${s.length}" // 求值结果为 "abc.length is 3"
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:
val price = """
${'$'}9.99
"""
数组型(Array)
与Java不一样,Kotlin放弃了使用中括号 “[]” 来表示数组。在Kotlin语言中,数组使用 Array 关键字来表示。了解Java语言的人看到Array很容易想到泛型中的一个用法,我们在IntelliJ IDEA中按住Ctrl跟踪Array的源码,看到Array的定义:
没错,这里的Array就是一个泛型列表,什么是泛型我们后面介绍,所以Kotlin语言中的Array绝大部分操作都与Java中的Array类似。
var arrayA = arrayOf(1, 2, 3, 4, 5) // 推测为一个Array数组,包含内容为1,2,3,4,5
var arrayB: Array<Int> // 定义一个整型Array数组
var arrayC: Array<Int> = arrayOf(1,2,3,4) // 定义一个整型Array数组,并赋值1,2,3,4,5
println("arrayA size = " + arrayA.size) // 打印数组长度
println("arrayA[2] = " + arrayA[2]) // 打印arrayA的第二个元素
println("arrayA.get(2) = " + arrayA.get(2)) // 打印arrayA的第二个元素
值得一提的是,在Java预言之中Array和数组是两个不同的概念,而在Kotlin之中将其两者进行了合并。我们可以直接使用中括号 “[]” 作为下标引用数组中的元素,也可以使用 get(index) 方法获取其中某项内容。
当然,这个合并让刚从Java转向Kotlin语言的开发者弄得晕头转向,故此,Kotlin针对每个类型的数组还进行了扩展。如:
val arrayInt : IntArray // Int数组,同Array<Int>
val arrayLong : LongArray // Long数组,同Array<Long>
val arrayFloat : FloatArray // Float数组,同Array<Float>
val arrayDouble : DoubleArray // Double数组,同Array<Double>
val arrayBoolean : BooleanArray // Boolean数组,同Array<Boolean>
//...
Kotlin类型与Java基础类型对比
记住,在Kotlin语言之中不存在原生类型,一切皆为对象。这里,我们将Kotlin语言基础类型与Java语言进行一个对比。
Java | Kotlin |
---|---|
int、Integer | Int |
long、Long | Long |
short、Short | Short |
byte、Byte | Byte |
float、Float | Float |
double、Double | Double |
boolean、Boolean | Boolean |
char、Char | Char |
String | String |
int[] | IntArray |
long[] | LongArray |
short[] | ShortArray |
byte[] | ByteArray |
float[] | FloatArray |
double[] | DoubleArray |
boolean[] | BooleanArray |
String[] | StringArray |
Kotlin基本类型共有八种,基本类型可以分为三类,字符类型Char、String,布尔类型Boolean以及数值类型Byte、Short、Int、Long、Float、Double。数值类型又可以分为整数类型Byte、Short、Int、Long和浮点数类型Float、Double。Kotlin中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。八种类型表示范围如下:
类型 | 取值范围 | 说明 |
---|---|---|
Byte | 8位 | 最大存储数据量是255,存放的数据范围是-128~127之间。 |
Short | 16位 | 最大数据存储量是65536,数据范围是-32768~32767之间。 |
Int | 32位 | 最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。 |
Long | 64位 | 最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。 |
Float | 32位 | 数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。 |
Double | 64位 | 数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。 |
Boolean | - | 只有true和false两个取值。 |
Char | 16位 | 存储Unicode码,用单引号赋值。 |
Kotlin决定了每种基本类型的大小。这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是基于JVM语言程序具有很强移植能力的原因之一。