笔记源于《第一行代码》-郭霖
2.3 变量和函数
2.3.1 变量
Kotlin中定义一个变量,只允许在变量前声明两种关键字:val 和 var
val用来声明一个不可变的变量,这种变量在初始赋值后就再也不能重新赋值,对应Java中的final变量 var用来声明一个可变的变量,这种变量在初始赋值后可以重新赋值,对应Java中的非final变量
要注意的是,在Kotlin中每行代码的结尾是不需要加分号的
为什么只有两种变量类型? 因为Kotlin有类型推导机制。
fun main(){
val a = 10
println("a = " + a)
}
但是Kotlin的类型推导机制并不是总可以正常工作的,如果我们对一个变量延迟赋值的话,Kotlin就无法自动推导它的类型了。这时候就需要显式地声明变量类型才行。
val a: Int = 10
既然val关键字有这些束缚,为何还要使用这个关键字? Kotlin这样的设计是为了解决Java中final关键字没有被合理利用的问题。 在Java中,除非主动在变量前声明final关键字,否则该变量就是可变的。但当项目愈发复杂的时候,一个可变的变量很有可能不知道就被谁修改了,就会出现难以想象的问题,因此,一个好的习惯就是,除非一个变量明确允许被修改,否则都应该加上final关键字,但是许多人都没有这样的习惯,所以Kotlin在设计时就提供了val和var两个关键字,必须由开发者自行选择该变量是否可变。 在开发时应该优先使用val,当val无法满足使用时再使用var。
Kotlin的数据类型的名称与Java的有所区别,Kotlin中的数据类型的名称的首字母是大写的,而Java中则全为小写。不要小看两者大小写之间的差距,这表示Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。在Java中int是关键字,而在Kotlin中Int变成了一个类,它拥有自己的方法和继承结构。
Kotlin | Java |
---|
Int | int | Long | long | Short | short | Float | float | Double | double | Char | char | Boolean | boolean | Byte | byte |
2.3.2 函数
Kotlin的函数语法规则大致如下:
fun methodName(param1: Int, param2: Int): Int{
return 0
}
参数的声明格式是“参数名:参数类型",如果不想接收任何参数,就只写一对空括号即可。 参数括号后面的那部分是可选的,用于声明该函数会返回什么类型的数据,如果函数不需要返回任何数据,这部分可以不写。
mian函数是kotlin程序的入口函数。 例:
fun main(){
val a = 35
val b = 45
val value = largerNumber(a, b)
println("larger number is " + value)
}
fun largerNumber(num1: Int, num2: Int): Int{
return max(num1, num2)
}
当一个函数中只有一行代码时,Kotlin允许不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可,比如上方的largerNumber()函数可以简化:
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
使用这种语法,return关键字也可以省略,等号足以表达返回值的意思,另外,上方还提到了kotlin的类型推导机制,在此也可以发挥作用。由于max()函数返回的是一个Int值,且我们在largerNumber()函数的尾部使用了等号连接max()函数,因此Kotlin可以推导出largerNumber()函数返回的必然是一个Int值,这样就不用显式地声明返回值类型了。
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
2.4 程序的逻辑控制
2.4.1 if条件语句
Kotlin中的条件语句主要有两种实现方式:if和when 可以将上方的largerNumber()改写:
fun largerNumber(num1: Int, num2: Int): Int{
var value = 0
if(num1 > num2){
value = num1
}else{
value = num2
}
return value
}
Kotlin中的if语句比Java的多了一个额外功能,它可以有返回值,返回值就是if语句中每一个条件中最后一行代码的返回值。因此,上述代码就可以简化:
fun largerNumber(num1: Int, num2: Int): Int{
val value = if(num1 > num2){
num1
}else{
num2
}
return value
}
但是上述代码可以再精简,value变量也可以删去
fun largerNumber(num1: Int, num2: Int):Int{
return if(num1 > num2){
num1
}else{
num2
}
}
注意,在Kotlin中当一个函数只有一行代码时,可以省略函数体部分,直接将这一行代码使用等号串联在函数定义的尾部。虽然上述函数代码不只一行,但是它和只有一行代码的作用是相同的,只是返回了if的返回值,符合语法糖的使用条件。
fun largerNumber(num1: Int, num2: Int) = if(num1 > num2){
num1
}else{
num2
}
fun largerNumber(num1: Int, num2: Int) = if(num1 > num2) num1 else num2
2.4.2 when条件语句
Kotlin中的when语句有点类似与Java中的switch语句,但它的功能较多。
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
}
fun getScore(name: String) = when(name){
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
when语句和if语句一样,也是可以有返回值的。 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")
}
}
when语句还有一种不带参数的用法
fun getScore(name: String) = when{
name == "Tom" -> 86
name == "jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
fun getScore(name: String) = when{
name.startsWith("Tom") -> 86
name == "jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
2.4.3 循环语句
Kotlin也提供了while循环和for循环,其中while循环不管是语法还是使用技巧上都和Java中的while循环没有任何区别。 我认为Kotlin中的for循环类似于Python中的for循环。 用Kotlin代码表示一个区间:
val range = 0..10
有了区间后,就可以通过for-in循环来遍历这个区间
fun main(){
for(i in 0..10){
println(i)
}
}
但是在很多情况下,双端闭区间却不如单端闭区间好用,许多数组的下标都是从0开始,一个长度为10的数组,下标范围为0到9,因此左闭右开的区间在程序设计中更常用。Kotlin中可以使用until关键字来创建一个左闭右开的区间
val range = 0 until 10
Kotlin中也有步长step关键字
fun main(){
for (i in 0 until 10 step 2){
println(i)
}
}
创建降序的区间,可以使用downTo关键字
fun main(){
for(i in 10 downTo 1){
println(i)
}
}
2.5 面向对象编程
Kotlin是面向对象的
2.5.1 类与对象
Kotlin中也是使用class关键字来声明一个类
class Person{
var name = ""
var age = 0
fun eat(){
println(name + " is eating. He is " + age + " years old.")
}
}
Kotlin中如何对类进行实例化?
val p = Person()
可以在main()函数中对p对象进行一些操作
fun main(){
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
2.5.2 继承与构造函数
要是子类继承父类,第一件事,使父类可以被继承。可能有人会觉得奇怪,一个类本身不就是可以被继承的吗?为什么还要父类可以继承?这就是Kotlin不同的地方,在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于在Java中给类声明了final关键字。之所以这么设计,其实和val关键字的原因差不多,因为类和变量一样,最好都是不可变的,而一个类允许被继承的话,它无法预知子类会如何实现。 所以Kotlin默认所有非抽象类都是不可被继承的。要使父类可以被继承,只需要在父类前面加上open关键字就可以了:
open class Person(){
...
}
第二件事,要让子类继承父类,在Java中继承的关键字是extends,而在Kotlin中变成一个冒号:
class Student : Person(){
var sno = ""
var grade = 0
}
继承的写法如果只是替换一下关键字倒也简单,但是为什么Person类的后面要加上一对括号呢?因为这里还设计主构造函数、次构造函数等方面的知识。 任何一个面向对象的编程语言都会有构造函数的概念,Kotlin中也有,但是Kotlin将构造函数分为两种:主构造函数和次构造函数。 主构造函数将会是你最常用的构造函数,每个类默认都会有一个不带参数的主构造函数当然也可以显式地指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可:
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)
}
}
那这些跟那对括号有什么关系呢?这就涉及到Java继承特性中的一个规定,子类中的构造函数必须调用父类中的构造函数,在Kotlin中也要遵守。 回头看一下Student类,现在我们声明了一个主构造函数,根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体,该如何去调用父类的构造函数?你可能会说,在init结构体中调用不就好了?这或许是一种办法,但绝对不是一种好办法,因为在绝大多数的场景下,我们是不需要编写init结构体的。 Kotlin采用了一种简单但是不太好理解的设计方式:括号。子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。所以回头再看一遍这段函数:
class Student(val sno: String, val grade: Int): Person(){}
如果我们将Person改造一下:
open class Person(val name: String, val age: Int){...}
如果想解决这错误,就必须给Person类的构造函数传入name和age字段 ,可是Student类也没有这两个字段。没有的话那就加上,我们可以在Student类的主构造函数中加上name和age两个参数,再将这两个参数传给Person类的构造函数:
class Student(val sno: String, val grade: Int, name: String, age: Int): Person(name, age){
...
}
那什么是次构造函数呢?任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数可以用于实例化一个类,这一点和主构造函数没什么不同,只不过它是有函数体的。 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){}
constructot(): this("",0){}
}
还有一种非常特殊的情况:类中只有次构造函数,没有主构造函数。这种情况真的十分少见,但在Kotlin中是允许的。当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。
class Student: Person{
constructor(name: String, age: Int): super(name, age){}
}
2.5.3 接口
接口是用于实现多态编程的重要组成部分。我们都知道,Java是单继承结构的语言,任何一个类最多只能继承一个父类,但是却可以实现任意多个接口,Kotlin也是如此。我们可以在接口中定义一系列的抽象行为,然后由具体的类去实现。
interface Study{
fun readBooks()
fun doHomework()
}
class Student(name: String, age: Int): Person(name, age), Study{
override fun readBooks(){
println(name + " is reading.")
}
override fun doHomework(){
println(name + " is doing homework.")
}
}
我们可以在main()函数中编写如下代码来调用两个接口中的函数:
fun main(){
val student = Student("Jack", 19)
doStudy(student)
}
fun doStudy(study: Study){
study.readBooks()
study.doHomework()
}
Kotlin还有一个额外的功能:允许对接口中定义的函数进行默认实现。其实Java在JDK1.8后也开始支持这个功能了,因此总体来说,Kotlin和Java在接口方面的功能仍然是一模一样的。
interface Study{
fun readBooks()
fun doHomework(){
println("do homework default implementation.")
}
}
接下来我们在来看一下Kotlin和Java相比变化较大的部分–函数的可见性修饰符。 熟悉Java的人都知道,Java中有public, private, protected, default(什么都不写)这4种函数可见性修饰符。Kotlin中也有4中,分别是public, private, protected, internal,需要使用哪种修饰符时,直接定义在fun关键字的前面即可。
修饰符 | Java | Kotlin |
---|
public | 所有类可见 | 所有类可见(默认) | private | 当前类可见 | 当前类可见 | protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 | default | 同一包路径下的类可见(默认) | 无 | internal | 无 | 同一模块中的类可见 |
2.5.4 数据类与单例类
在一个规范的系统架构中,数据类通常占据着非常重要的角色,它们用于将服务器端或者数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。 Kotlin中只需要在一个类前面声明了data关键字,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参数帮你将equals(), hashCode(), toString()等固定且无实际逻辑意义的方法自动生成,从而大大减少开发的工作量。
data class Cellphone(val brand: String, val price: Double)
fun main(){
val cellphone1 = Cellphone("Samsung", 1299.99)
val cellphone2 = Cellphone("Samsung", 1299.99)
println(cellphone1)
println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}
在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可。
object Singleton{}
现在Singleton就已经是一个单例类了,我们可以直接在这个类中编写需要的函数
object Singleton{
fun singletonTest(){
println("singletonTest is called.")
}
}
可以看到,在Kotlin中我们不需要私有化构造函数,也不需要提供getInstance()这样的静态方法,只需要把class关键字改成object关键字,一个单例类就创建完成了,而调用单例类中的函数也很简单,比较类似于Java中静态方法的调用方式:
Singleton.singletonTest()
这种写法虽然看上去像是静态方法的调用,但其实Kotlin在背后自动帮我们创建了一个Singleton类的实例,并且保证全局只会存在一个Singleton实例。
|