一 概述
Android 1.5系统中Google 引入了NDK功能,支持使用C和C 语言来进行一些本地化开发。
17年官宣kotlin为安卓一级开发语言;所以对于安卓开发者,学习kotlin是必须的,这是java最好的第三方库(富语法糖java)。
kotlin将代码编译成同样规格的class文件让Java虚拟机识别,它继承了Java的全部财富,和Java100%兼容的,可以直接调用使用Java编写的代码,也可以无缝使用Java第三方的开源库;Kotlin的类型推断也在Java 8 中被推出,Lambda作为函数式编程的基础也在Java 8版本中加入,两者会越来越像。
Kotlin是函数式编程语言(注1:),Kotlin的语法非常像Scala,python,借鉴了很多语言,学了很多语言发现他们越来越像,就像C#之父Anders Hejlsberg说的:未来的编程语言将逐渐融合各自的特性,而不会只存在单纯的声明式语言或者函数编程语言。
对比Java的一些优势:
与作为命令式语言时代的产物:Java相比,Kotlin对内联函数的支持,使它运行Lambda表达式更快;
提前到编译期的空指针安全检测;
Kotlin有很多现代静态编程语言的特点:类型推断、多范式支持、可空性表达、扩展函数、DSL支持等。这些功能Java也陆续在加入。(2021.5.5注:该句有歧义,Koltlin本身就是静态语言,不要误以为其为动态语言)
二 基本类型
Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型,在 Kotlin 中,所有东西都是对象;
2.1 数字
基本Kotlin数值类型包括:Byte、Short、Int、Long、Float、Double等。与Java不同的是,Kotlin中的Charactor不属于数值类型。
Kotlin 提供了一组表示数字的内置类型。 对于整数,有四种不同大小的类型,因此值的范围也不同。
类型 | 大小(比特数) | 最小值 | 最大值 |
---|---|---|---|
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
Long | 64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
所有以未超出 Int 最大值的整型值初始化的变量都会推断为 Int 类型。如果初始值超过了其最大值,那么推断为 Long 类型。 如需显式指定 Long 型值,请在该值后追加 L 后缀。
代码语言:txt复制val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
对于浮点数,Kotlin 提供了 Float 与 Double 类型。
类型 | 大小(比特数) | 有效数字比特数 | 指数比特数 | 十进制位数 |
---|---|---|---|---|
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
对于以小数初始化的变量,编译器会推断为 Double 类型。 如需将一个值显式指定为 Float 类型,请添加 f 或 F 后缀。 如果这样的值包含多于 6~7 位十进制数,那么会将其舍入。
代码语言:txt复制val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float,实际值为 2.7182817
请注意,与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用。
注:=== 表示比较对象地址,== 表示比较两个值大小。
2.2 字符类型
Kotlin中字符类型用Charactor表示,与Java不同的是,它们不能直接当作数字,它无法直接使用Char类型的ASCII进行算数运算。如果需要用,得用toInt函数转为相应ASCII。
2.3 布尔型
一些语言比如OC或python中,false、true可以用0、1代替,在Kotlin/Java中不允许。
2.4 数组类型
Kotlin中数组用Array类表示,可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它。Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray、 ShortArray、IntArray 等等。这些类与 Array 并没有继承关系,但是它们有同样的方法属性集。
代码语言:txt复制// 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组
val arr = IntArray(5)
// 例如:用常量初始化数组中的值
// 大小为 5、值为 [42, 42, 42, 42, 42] 的整型数组
val arr = IntArray(5) { 42 }
// 例如:使用 lambda 表达式初始化数组中的值
// 大小为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)
var arr = IntArray(5) { it * 1 }
2.5 字符串
Kotlin中字符串同Java,用String表示。
字符串用 String 类型表示。字符串是不可变的。 字符串的元素——字符可以使用索引运算符访问: si。 可以用 for 循环迭代字符串。
Kotlin提供了字符串的内嵌表达式,也被称为字符串模板。语法规则:
代码语言:txt复制"hello, ${obj.name}. nice to meet you!"
字符串里嵌入${}这种语法结构的表达式,并在运行时使用表达式执行的结果替代这一部分内容,当表达式中仅有一个变量的时候,还可以将两边的大括号省略:
代码语言:txt复制"hello, $name. nice to meet you!"
三 类型转换和变量定义
3.1 类型转换
精度缺失的问题就不多说了,一般两个不同类型数字在逻辑运算时并不需要特别注意,算术运算符会重载适应不同类型:
代码语言:txt复制val a = 1L 3 // Long Int => Long
显示转换:
类型 | 显示转换 |
---|---|
Byte | toByte() |
Short | toShort() |
Int | toInt() |
Long | toLong() |
Float | toFloat() |
Double | toDouble() |
Char | toChar() |
String | toString() |
3.2 变量
Kotlin中定义一个变量,只允许在变量前声明两种关键字:val和var。
- val(value的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中的final变量。
- var(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值,对应Java中的非final变量。
这样设计,是为了解决Java中final关键字没有被合理使用的问题。
使用指导:永远优先使用val来声明一个变量,而当val没有办法满足你的需求时再使用var。
Kotlin的类型推导机制:如下,val关键字定义了一个变量a赋值为10,这里a就会被自动推导成整型变量。
代码语言:txt复制fun main() {
val a = 10
println("a = " a)
}
但对一个变量延迟赋值的话,Kotlin就无法自动推导它的类型了。这时候就需要显式地声明变量类型才行:
代码语言:txt复制val a: Int = 10
四 函数
Kotlin函数语法规则:
代码语言:txt复制fun methodName(param1: Int, param2: Int): Int {
return 0
}
参数括号后面的部分用于声明该函数会返回什么类型的数据,上例表示该函数会返回一个Int类型的数据。
如果一个函数不返回任何有用的值,它的返回类型是 Unit。如果函数不需要返回任何数据,这部分可以不写。
当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可:
代码语言:txt复制fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
再结合Kotlin出色的类型推导机制,由于max()函数返回的是一个Int值,因此Kotlin可以推导出largerNumber()函数返回的必然也是一个Int值,代码可以进一步简化成如下形式:
代码语言:txt复制fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
默认参数:
函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量:
代码语言:txt复制fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*……*/ }
如果在默认参数之后的最后一个参数是 lambda表达式,那么它既可以作为具名参数在括号内传入,也可以在括号外传入:
代码语言:txt复制fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }
foo(1) { println("hello") } // 使用默认值 baz = 1
foo(qux = { println("hello") }) // 使用两个默认值 bar = 0 与 baz = 1
foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
Kotlin函数的可见性修饰符和Java相比变化较大。
Java和Kotlin中函数可见性修饰符之间的区别:
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | (默认类型)所有类可见 |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | (默认类型)同一包路径下的类可见 | 无 |
internal | 无 | 同一模块中的类可见 |
五 控制流:if、when、for、while
5.1 if条件语句
代码语言:txt复制fun largerNumber(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}
Kotlin中的if语句相比于Java有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。因此,上述代码就可以简化成如下形式:
代码语言:txt复制fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
kotlin的语法糖里,当一个函数只有一行代码时,可以省略函数体部分,直接将这一行代码使用等号串连在函数定义的尾部。虽然上述代码中的largerNumber()函数不止只有一行代码,但是它和只有一行代码的作用是相同的,只是返回了一下if语句的返回值而已,符合该语法糖的使用条件。那么我们就可以将代码进一步精简:
代码语言:txt复制fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
再精练:
代码语言:txt复制fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
5.2 when条件语句
Kotlin中的when语句类似于Java中的switch语句,但比Java中的switch语句要灵活很多。Java中的switch只能传入整型或短于整型的变量作为条件,JDK 1.7之后增加了对字符串变量的支持。
编写一个查询考试成绩的功能,输入一个学生的姓名,返回该学生考试的分数,if写法:
代码语言:txt复制fun getScore(name: String) = if (name == "Tom") {
86
} else if (name == "Jim") {
77
} else if (name == "Jack") {
95
} else if (name == "Lily") {
100
} else {
0
}
when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件,格式是:
代码语言:txt复制匹配值 -> { 执行逻辑 }
当你的执行逻辑只有一行代码时,{ }可以省略。
当使用when语句的时候,现在我们将代码改成如下写法:
代码语言:txt复制fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
除了精确匹配之外,when语句还允许进行类型匹配。举个例子。定义一个checkNumber()函数,如下所示:
代码语言:txt复制fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
上述代码中,is关键字就是类型匹配的核心,它相当于Java中的instanceof关键字。
由于checkNumber()函数接收一个Number类型的参数,这是Kotlin内置的一个抽象类,像Int、Long、Float、Double等与数字相关的类都是它的子类,所以就里就可以使用类型匹配来判断传入的参数到底属于什么类型,如果是Int型或Double型,就将该类型打印出来,否则就打印不支持该参数的类型。
when语句还有一种不带参数的用法,虽然这种用法可能不太常用,但有的时候却能发挥很强的扩展性。
拿刚才的getScore()函数举例,如果我们不在when语句中传入参数的话,还可以这么写:
代码语言:txt复制fun getScore(name: String) = when {
name == "Tom" -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
可以看到,这种用法是将判断的表达式完整地写在when的结构体当中。注意,Kotlin中判断字符串或对象是否相等可以直接使用==关键字,而不用像Java那样调用equals()方法。可能你会觉得这种无参数的when语句写起来比较冗余,但有些场景必须使用这种写法才能实现。举个例子,假设所有名字以Tom开头的人,他的分数都是86分,这种场景如果用带参数的when语句来写就无法实现,而使用不带参数的when语句就可以这样写:
代码语言:txt复制fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
现在不管你传入的名字是Tom还是Tommy,只要是以Tom开头的名字,他的分数就是86分。
5.3 For循环
Java中主要有两种循环语句:while循环和for循环。Kotlin的while循环和Java中的while循环没有任何区别。
Kotlin在for循环方面做了很大幅度的修改,Java中最常用的for-i循环在Kotlin中直接被舍弃了,而Java中另一种for-each循环则被Kotlin进行了大幅度的加强,变成了for-in循环,所以我们只需要学习for-in循环的用法就可以了。
区间的概念
闭区间:0, 10
代码语言:txt复制val range = 0..10
左闭右开区间:[0,10):
代码语言:txt复制val range = 0 until 10
有了区间之后,我们就可以通过for-in循环来遍历这个区间,比如在main()函数中编写如下代码:
代码语言:txt复制fun main() {
for (i in 0..10) {
println(i)
}
}
默认情况下,for-in循环每次执行循环时会在区间范围内递增1,相当于Java for-i循环中i 的效果,而如果你想跳过其中的一些元素,可以使用step关键字:
代码语言:txt复制fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
上述代码表示在遍历[0, 10)这个区间的时候,每次执行循环都会在区间范围内递增2,相当于for-i循环中i = i 2的效果。
.. 和until关键字都要求区间的左端必须小于等于区间的右端,也就是这两种关键字创建的都是一个升序的区间。如果你想创建一个降序的区间,可以使用downTo关键字,用法如下:
代码语言:txt复制fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
这里我们创建了一个10, 1的降序区间。
标注1:
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。更多了解推荐参考:https://www.zhihu.com/question/28292740
学习参考
1 官网文档 https://www.kotlincn.net/docs/reference/basic-syntax.html
2 郭霖:《第一行代码》