本篇内容是来自《第一行代码》第三版Kotlin相关内容的一个简单的总结。
变量和函数
变量
由于Kotlin有出色的类型推导机制,所以我们可以使用val和var定义变量
-
val(value的简写)用来声明一个不可变的变量,这种变量在树初始赋值以后就再也不能重新赋值,对应Java中的final变量
fun main() { val j = 10 val s = "Hello World" } -
var(variable的简写)用来声明一个可变的变量,这种变量在树初始赋值以后仍然可以被重新赋值,对应Java中的非final变量
fun main() { var j = 10 j *= 10 }
但是Kotlin的类型推导机制并不总是可以正常工作的,所以需要显式声明变量类型才行,语法如下:
val a: Int = 10
在Kotlin里面,完全抛弃了Java里面的基本数据类型,全部使用了对象数据类型。
在定义变量时,优先使用val去定义变量,当val无法满足我们的需求时,就可以改为使用var来定义变量。
函数
函数定义,语法规则如下:
fun methonMane(param1: Int, param2: Int): Int {
return 0
}
定义函数只能用fun来声明
参数声明格式:参数名:参数类型
函数返回值类型声明:():返回值类型,这部分是可选的,如果需要返回值就写,不需要就不写
简单定义一个函数,代码如下:
fun main() {
val a = 37
val b = 40
val value = largerNumber(a, b)
println("larger number is " + value)
}
fun largerNumber(num1: Int, num2: Int): Int{
return max(num1, num2)
}
来看一下Kotlin函数的语法糖
当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数的尾部,中间用等号连接即可。
将上面的largerNumber函数简化一下:
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
由于存在推导机制,返回值类型可以省略,我们继续简化一下:
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
程序的逻辑控制
执行语句主要分为3种:顺序语句、条件语句和循环语句。
条件语句
if条件语句
Kotlin中的条件语句主要有两种实现方式:if和when
先看个简单的例子:
fun largerNumber(num1: Int, num2: Int): Int{
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}
这里看到和java的用法是完全一样的,但是Kotlin中的if语句相比于java有一个额外的功能,它是可以有返回值的
返回值就是if语句每一个条件中最后一行代码的返回值,我们简化一下上面的代码:
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
仔细看一下现在的代码,结合上面的所讲,继续简化一下代码:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
when条件语句
写个简单的例子来看一下when的用法,查询考试成绩的功能,输入一个学生的姓名,返回该学生的考试分时。
先用if语句实现看看
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
}
这里可以看到,我们的if和else if,还有大括号,以及name都是重复的,下面用when语句改写:
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
可以看到,代码简洁了许多,并且when语句也是可以有返回值的,也可以使用单行的语法糖
when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件,格式如下:
匹配值 -> { 执行逻辑 }
除了精准匹配之外,when语法还允许进行类型匹配。
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关键字。
when语句还有一种不带参数的用法,看看是怎么使用的。
fun getScore(name: String) = when {
name == "Tom" -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
可以看到这种写法将判断的表达式完整的写在了when的结构体当中。现在看来这种写法并没有上面的写法好,但是有一些特殊的情况必须使用这种写法才能实现。举个例子,假设所有名字以Tom开头的人,他的分数都是86分,这种场景如果用带参数的when语句就无法实现,使用不带参数的when语句可以这样写:
fun getScore(name: String) = when {
name.startWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
好了,when的用法就写到这里
循环语句
java中主要有两种循环语句:while循环和for循环,在Kotlin也提供了while循环和for循环,其中while循环在使用上和java一模一样,就不过多说明。接下来主要讲一讲for循环。
Kotline在for循环方面做了很大幅度的修改,Java中最常用的for-i循环直接在Kotlin中被舍弃了,而Java中的另一种for-each循环则被Kotlin进行了大幅度加强,变成了for-in循环,接下来学习一下for-in。
在Kotlin多出了一个区间的概念,可以使用如下代码表示一个区间:
val range = 0..10
上诉代码表示了[0, 10],闭区间,包含0和10
fun main() {
for (i in 0..10) {
println(i)
}
}
很多情况下,双端闭区间没有单端闭区间好用,怎么表示呢,假设创建0到10的单端闭区间[0,10),可以使用until关键字来创建
val range = 0 until 10
如果想跳过for-in循环里面的元素,可以使用step关键字
fun main() {
for (i in 0 until 10 step 2) {
// 输出0 2 4 6 8
println(i)
}
}
可以知道..和until关键字都要求区间的左端必须小于等于区间的右端,可以理解为这两个关键字创建的都是一个升序的区间。
如果想创建一个降序区间,可以使用downTo关键字,用法如下:
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
这里创建了一个[10,1]的降序区间,降序区间也可以使用step关键字跳过区间中的一些元素。
面向对象
类
面向对象:个人理解为,类就是对事物的一种封装,或者对某一类事物的统称,比如说人、汽车、房子等任何事物,都可以将它封装为一个类,类名通常是名词。类中拥有自己的字段和函数,字段表示该类所拥有的属性,函数表示该类所用的某些行为。
Kotlin中也是使用class关键字来声明一个类的
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + "years old.")
}
}
接下来对这个进行实例化
val p = Person()
Kotlin中实例化一个类的方式和Java是基本类似的,只是去掉了new关键字而已。因为当你调用某个类的构造函数时,你的意图也就是实例化这个类而已,即便没有new,也能清晰表达出你的意图。
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
继承与构造函数
概念和Java里面基本一样,就是用法不太一样。
在Java里面,只要不是加了final定义的类,都是可以被继承的,但是在Kotlin里面是不一样的,默认所有的非抽象类是禁止被继承的。
想要Person类可以被继承,只需要在Person类的前面加上open关键字就可以了,如下所示:
open class Person {
...
}
加上open关键字以后,Person这个类是专门为继承而设计的,这样子Person就允许被继承了。
我们创建一个类来继承Person,在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号,写法如下:
class Student : Person() {
...
}
这里需要重点了解,为什么Person后面还要加上括号呢,是因为设计到主构造函数和次构造函数。
主构造函数
接下来先学习一下主构造函数和次构造函数的知识。
主构造函数是我们最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,也可以显示地给它指明构造函数。主构造函数的特点是没有函数载体,直接定义在类名的后面即可,写法如下:
class Student(val sno: String, val grade: Int) : Person() {
}
这里将学号和年级这两个字段都放到了主构造函数当中,就表明在对Student类进行实例化的时候,必须传入构造函数中要求的两个参数,如下:
val student = Student("a123", 5)
现在看来,主构造函数没有载体,我们如何进行一些初始化的操作呢,Kotlin提供了一个init结构体,所有主构造函数的逻辑都可以写在里面:
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
现在如果我们去创建Student类的实例,就会将构造函数中传入的数值打印出来。
到现在为止还挺好理解,那么为啥继承的时候需要加上父类类名加括号呢,是因为在Java继承时的一个特性,子类中的构造函数必须调用父类中的构造函数,这个规定在Kotlin中也要遵守。
现在回头看一下Student类,我们声明了主构造函数,根据继承的特性,子类的构造函数必须调用父类的构造函数,可是很多时候我们的主构造函数并没有载体,我们怎么样才能去调用父类的构造函数呢?这个时候可能很多人会想到在init结构体中去调用父类的构造函数,这或许是一种方法,但是在很多场景下面我们并不需要编写init结构体。Kotlin没有采用这种设计,而是通过在继承的时候通过括号的方式来指定子类的主构造函数调用父类中的哪个构造函数。
class Student(val sno: String, val grade: Int) : Person() {
}
现在再看这段代码,就能理解为什么要添加括号了,这就表示Student类的主构造函数在初始化的时候会调用Person的无参构造函数,即使在无参数的情况下,这对括号也不能省略。
如果我们将Person类改造一下,将姓名和年龄都放在主构造函数中,如下:
open class Person(val name: String, val age: Int) {
...
}
此时之前定义的Student就会报错了,这时候我们也要修改一下Student类:
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
}
注意我们在Student类的主构造函数中增加name和age字段时,不能再将他们声明为val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此,这里的name和age参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数中即可。
现在可以通过以下代码来创建一个Student类的实例:
val student = Student("A123", 5, "Jack", 19)
到这里主构造函数就讲完了,接下来看一下次构造函数
次构造函数
简单说一下,任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类,次构造函数是有函数体的。
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)
写个简单的例子看一下:
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age) {
}
constructor() : this("", 0) {
}
}
次构造函数是通过construtor关键字来定义的,定义了两个次构造函数
第一个次构造函数接受name和age参数,然后它又通过this关键字调用了主构造函数,并将sno和grade这两个参数赋值成初始值。
第二个次构造函数不接受任何参数,然后它又通过this关键字调用了第一个次构造函数,并将name和age参数也赋值成初始值,这样第二个次构造函数也算间接调用了主构造函数,因此这仍然是合法的。
再看看如何去初始化Student类
val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)
以上是次构造函数的常用场景,然而次构造函数还有一种特殊的情况:类中只有次构造函数,没有主构造函数。这种情况真的很少见,但是在Kotlin中是允许的。
当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的,结合代码看一下:
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
注意这里的代码变化,首先Student类的后面没有显式地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。既然没有主构造函数,继承Person类的时候也就不需要再加括号了。
另外,由于Student类没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字。
接口
接口的内容基本上和Java一样。
未完待续