前序
在19年的Google I/O大会上,Kotlin 成为 Android 开发首选语言。而著名的OkHttp 已经开始用 Kotlin 进行重写工作。是时候通过写博客归纳来巩固Kotlin基础知识。
(一)、语法上的变更
- 创建对象不需要new关键字
- 语句不需要;结尾,加;也无所谓
- 变量类型后置,即变量名在前,变量类型在后。例如 str:String
- 使用 println() 替代 System.out.println()
(二)、定义变量
Kotlin中使用val关键字声明常量(即变量初始化之后不可再次赋值),var关键字声明变量。
变量定义时可以不显示指定类型,编译器会根据其初始化器的类型进行推断。
//自动推断出 `Int` 类型
var daqi = 1
//可以显式地指定变量的类型
val a :String = "daqi"
//如果变量没有初始化器,需要显式地指定它的类型
val c: Int
c = 3
当编译器能确保val变量只有唯一一条初始化语句会被执行,可以根据条件对它初始化不同的值。
val daqi:String
var isChange = true
if (isChange){
daqi = "1"
}else{
daqi = "2"
}
val变量的引用自身是不可变的,但是它指向的对象是可变的。
val languages = arrayListOf("Java")
languages.add("Kotlin")
var 关键字允许变量改变自己的值,但不能改变自己的类型。
var daqi = 1
//编译不通过
daqi = ""
(三)、定义函数
Kotlin 中使用 fun 关键字声明函数。
完整的函数定义:
修饰符(默认public)+ fun + 方法名 + (参数列表) :返回值 {
函数体
}
public fun daqi(name:String):String {
}
表达式函数体
当单个表达式构成完整的函数体时,可以直接去掉 {} 和 return 语句,函数的返回值类型编译器也会自动推断。这种函数就是表达式函数。
fun sum(a: Int, b: Int) = a + b
语句和表达式的区别在于:
- 表达式有值,并且能作为另一个表达式的一部分使用;
- 语句总是包围着它的代码块中的顶层元素,并且没有自己的值。
在 Java 中,所有的控制结构都是语句。而在 Kotlin 中,除了循环( for, do 和 do/while )以外大多数控制结构都是表达式(例如:if、 when 以及 try 属于表达式)
表达式函数不光用在些简单的单行函数中 ,也可以用在对更复杂的单个表达式求值的函数中。
fun max(a: Int, b: Int ) = if (a > b) a else b
无返回值的函数
类似Java的返回值类型为void的函数
fun daqi():Unit{
}
可省略Unit类型
fun daqi(){
}
(三)、字符串模板
在 Java 中,当需要打印变量描述和其值时,往往如下打印:
String str = "daqi";
System.out.println("str = " + str);
Kotlin支持字符串模板,可以在字符串中引用局部变量,但需要在变量名前加上$。表达式会进行静态检查, 如果$引用一个不存在的变量,代码不会编译通过。
val str = "daqi"
printlin("str = $str")
$不仅限于引用于简单的变量名称,还可以引用更复杂的表达式。
val daqi = intArrayOf(1,2,3)
println("${daqi[0]}")
在$引用的表达式内(即花括号{}中)可以直接嵌套双引号
println("daqi的第一个元素: ${if(daqi.size > 0) daqi[0] else "null"}")
当需要打印$符号时,可以对其进行转义,或者利用表达式对其进行打印:
//打印结果为:$str
val str = "daqi"
println("\$str")
println(${'$'}daqi)
(四)、类和属性
在java中定义一个简单类:
public class Person{
private final String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
}
用Kotlin写的Person类(默认public修饰)
class Person(val name:String)
延伸:若想知道Kotlin对应的具体Java实现,可以通过idea的工具,对Kotlin文件进行反编译:
Tools ->Kotlin -> Show Kotlin Bytecode -> 右侧弹出字节码框 ->左上角 Decompile按钮
整体基本和Java类相似,但是该类被定义为final类,即太监类,不可被继承。
setter 和 getter
在Kotlin中,当你声明属性的时候,也就声明了对应的访问器(即get和set)
val属性只有一个getter,var属性既有 getter 和 setter。
声明一个属性的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其中,初始化器,getter 和 setter 都是可选的。如果类型可以从初始化器或者getter 返回值中推断出来,也可以省略。
访问器的默认实现非常简单,即对对应变量的返回和更改。
当需要在值被访问或修改时提供额外的逻辑,可以通过自定义getter和setter
自定义getter
在Kotlin中的实现
class Rectangle(val height:Int,val width:Int){
val isSquare:Boolean
get(){
return height == width
}
}
对应的Java实现:
自定义setter
在Kotlin中实现:
class Rectangle(val height:Int,val width:Int){
var isSquare:Boolean = false
set(value) {
field = value
}
}
在setter方法中,使用特殊的标识符field来访问支持字段(幕后字段)的值。具体幕后字段后面再说。
注意:
Kolin中,名称以is开头的变量。转换成对应的Java get 和 set 方法时,getter不会添加任何的前准,setter名称中的is会被替换成set。
class Person(
var isDaqi:Boolean = false
)
该对象在Java中的访问器为:
(五)、迭代
Kotlin中 while 和 do-while循环,其语法与Java的基本没区别。
而for循环仅以一种形式存在,和for-each差不多。但用 in 取代了 :
fun iterationArray(args:Array<String>){
for (str in args) {
}
}
for循环还可以遍历区间。区间本质上是两个值之间的间隔。使用..运算符表示区间。
Kotlin中的区间是闭合区间,即结束值也是区间的一部分。而区间默认迭代步长为1。
val oneToTen = 1..10
可以通过step关键字修改步长。遍历时,i变量其增长幅度将变为2,即每次迭代时都会自增2。
for(i in 0..100 step 2){
}
如果想要一个半闭合区间,即结束值不属于区间的一部分,可以使用until关键字,
val oneToNine = 1 until 10
如果需要一个倒序的区间,可以使用downTo关键字
val tenToOne = 10 downTo 1
此时,将从10一直递减到1进行循环。downTo表示的区间也是一个闭合区间。
如果想实现普通for循环一样,对数据索引进行循环,可以使用数组的indices变量
for (i in args.indices) {
println(args[i])
}
(六)、if表达式
在Kotlin中,if是一个表达式,即它会返回一个值。
if的分支为代码块时,最后的表达式将作为该代码块的值:
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
(七)、when表达式
Kotlin中的when,对应的是Java中的switch,但比起更加强大。
when 将它的参数与所有的分支条件按顺序比较,直到某个分支满足条件,执行其分支对应的代码。当多分支需要用相同的方式处理时,可以把其放在一起,用逗号分隔。
当when作为表达式使用时,必须有 else 分支, 除非编译器检测出所有的可能情况都已经被覆盖。(例如枚举类)
when (x) {
0, 1 -> print("x == 0 or x == 1")
2 -> print("x == 2")
else -> print("otherwise")
}
有没有发现,没有了那繁琐的case和break!!
when的参数可有可无,并且无限制类型!而且可以用任意表达式作为分支条件。(switch要求必须使用常量作为分支条件~)
when作为表达式函数的的函数体,直接使用函数的参数,自身并无设置参数。并在条件语句中使用in或者!in 判断一个变量是否在区间或者集合中。
fun daqi(num:Int,intArray: IntArray) = when{
num in 1..10 -> "1~10"
num !in intArray -> "no in Array"
else -> "other"
}
when 也可以用来取代 if-else if链.
fun daqi(num:Int) = when{
num < 0 -> "小于0"
num >0 && num < 10 -> "1..10"
else -> "other"
}
is可以检查一个变量是否是某种类型,再配合智能转换(检查过某个变量的类型后,不再需要再转换它,直接可以把它当作检查过的类型使用)可以很方便的对进行类型安全操作。
//Any类似于Java的Object
fun daqi(num:Any) = when(num){
is String -> {
//此时num已经为String类型
println("该变量类型为String,获取其长度")
println(num.length)
}
is Int -> {
//此时num已经为Int类型
println("该变量类型为Int,将其与10进行对比")
num.rangeTo(10)
}
else -> "other"
}
(八)、Kotlin中的异常
Kotlin的异常处理与Java类似。当抛出异常时,不需要使用new关键字创建异常实例。
var daqi:String? = null
if (daqi == null){
throw NullPointerException("daqi is null")
}
try也可以作为表达式,将其赋值给变量。当代码执行正常,则try代码块中最后一个表达式作为结果,如果捕获到异常,对应catch代码块中最后一个表达式作为结果。finally 块中的内容不会影响表达式的结果。
fun readNumber(reader:BufferedReader):Int? = try{
Integer.parseInt(reader.readLine())
}catch(e:NumberFormatException){
null
}catch (e:NullPointerException){
null
}finally {
reader.close()
}
可以有0到多个 catch 块。finally 块可以省略。 但是 catch 与 finally 块至少应该存在一个。
(九)、枚举
kotlin中用两个关键字enum和class声明枚举类。enum是一个软关键字,只有当它出现在class前面时才有特殊的意义。
声明普通枚举类:
enum class Color{
RED,BLUE
}
声明带属性的枚举类:
enum class Color(val r:Int,val g:Int,val b:Int){
RED(255,0,0),BLUE(0,0,255);
fun rgb = (r * 256 + g) * 256 + b
}
如果在枚举类型中定义任何方法,需要使用分号;把枚举常量列表和方法定义分开。
参考文献:
- 《Kotlin实战》
- Kotlin官网