第2章 Kotlin快速入门
- 编译型语言:编译器会将源代码一次性编译成计算机可识别的二进制文件(C/C++);
- 解释型语言:在程序运行时,解释器回一行一行读取源代码,然后实时地将这些源代码解释成二进制数据后再执行,效率稍差(Python/JavaScript);
Java虽需要编译,但其实是解释型语言。因为Java编译后生成的是class文件,再交由Java虚拟机来解释之。Kotlin和Java都可以生成class文件。
2.1 变量和函数
2.1.1 变量
变量前只允许声明两种关键字:
- val:value,不可变变量;
- var:variable,可变变量
Tips:永远优先使用val来声明一个变量。当val无法满足需求时再换成var。这样的程序会更加见状、也更符合高质量的编码规范。
2.1.2 函数
- if函数:Kotlin中的if函数允许有返回值。例如下面这段代码
fun largeNumber(a: Int, b: Int): Int = if (a>b) a else b
- when函数:就类似下面这种
fun checkNumber(num: Number) = when (num){
is Int -> println("The number is an Int.")
is Double -> println("The number is a Double.")
else -> println("fuck u.")
}
还有另一种写法:
fun getScore(name: String) = when {
name=="nayufeng" -> 100
name=="liuyuhe" -> 101
name.startsWith("he") -> 0 // 表示所有he开头的名字,分数都是0分
else -> 99
}
` for函数:
for (i in 0..10){
println(i)
} // 闭区间
for (i in 0 until 10 step 2){
println(i)
} // 左闭右开
for (i in 10 downTo 1){
println(i)
} // 倒序
2.1.3 面向对象编程:类与对象
在Kotlin中,任何一个非抽象类默认都是不可被继承的。想让一个类继承另一个类,必须再类前加上open
关键字才可以:
open class Person {
var name = ""
var age = 0
fun eat(){
println(name + "is eating. He is " + age + " years old.")
}
}
Kotlin中继承的关键字是一个冒号:
class Student: Person( ) {
var sno = ""
var grade = 0
}
- 主构造函数:
主构造函数标准定义格式:
class 类名 constructor( 构造函数参数 ){
//类成员
}
如果主构造函数前没有修饰词(private, protected, internal, public)并且没有注解,那么可以省略constructor关键词:
/*
省略 constructor 关键字的主构造函数
省略 constructor 前提 :
① 主构造函数没有可见性修饰符 , 如 private , public , protected , internal (下面有详细jie'shao)
② 主构造函数没有注解
*/
class Student (name : String, age : Int){
}
继承时的Person( ) 中的这个括号意思就是调用Person类的主构造函数。当其中没有参数时是要加一对空括号就可以了;但是如果Person类需要传入参数时:
open class Person(val name: String, val age: Int) {
...
}
此时的Student类在继承Person类时就必须在括号中同时传入name和age这两个参数:
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
...
}
注意这里name和age参数前面不用加任何val或者var关键字。
- 次构造函数
基本上不会用到次构造函数。Kotlin提供了给函数设定参数默认值的功能,基本上可以替代次构造函数的作用。为了知识结构的完整性,还是要学一下次构造函数的。
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有次构造函数都必须调用主构造函数(包括间接调用)。
类中也可以只有次构造函数,没有主构造函数,但是很少见。当一个类中没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的,例如:
class Student : Person{
constructor(name:String, age:Int) : super(name, age){
}
}
这里的super
关键字用来调用父类的构造函数。
2.1.4 接口
以下代码是一个Study接口:
interface Study {
fun readBooks()
fun doHomework()
}
接下来由Student来实现Study接口:
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.")
}
}
如果在定义接口时,给函数加上函数体,那么这个函数就有了**“默认实现”**,在实现接口时就不强制实现这个有函数体的接口了。
Kotlin中的修饰符:public
、private
、protected
、internal
。直接定义在fun
关键字前面即可。
public
:只对当前类内部可见;public
:对所有类可见。Kotlin中不加修饰符默认是public的;protected
:只对当前类和子类可见;internal
:只对同一模块中的类可见。
2.1.5 数据类和单例类
- 数据类:专门用来存储数据的类。数据类的属性通常是不可变的,而普通类的属性可能是可变的。
实现方法:在类之前加一个data
关键字即可; - 单例类:单例类在全局最多只能拥有一个实例,这样可以避免创建重复的对象。
实现方法:把class
关键字改成object
即可。
2.2 Lambda编程
2.2.1 集合的创建和遍历
- 三种数据格式:
List
、Set
和Map
; - 初始化方法分别为
listOf("Apple", "Banana")
、setOf("Apple", "Banana")
和mapOf("Apple" to 1, "Banana" to 2)
;
2.2.2 集合的函数式API
Lambda:一小段可以作为参数传递的代码;
Lambda表达式的语法结构:
Lambda表达式可以有很多规则简化:
- 当Lambda参数是函数的最后一个参数时,可以将其移到函数括号的外面;
val maxLengthFruit = list.maxBy( ) {fruit: String -> fruit.length}
- 如果Lambda参数是函数的唯一一个参数,还可以将函数的括号省略;
val maxLengthFruit = list.maxBy {fruit: String -> fruit.length}
- 当Lambda表达式只有一个参数时,也不必声明参数名,而是可以用
it
关键字来代替。
除了val maxLengthFruit = list.maxBy {it.length}
maxBy
这个函数之外,还有如下几个函数式API:
map
:将集合中的每个元素都映射成另一个值,映射规则在lambda表达式中指定,最终生成一个新的集合。例如下列代码就将list中的所有元素变成大写:
fun main() {
val list = listOf("nayufeng", "wangruike", "ningmengcha")
val newList = list.map { it.toUpperCase() }
for (i in newList){
println(i)
}
}
filter
:过滤集合中的数据,可以单独使用,也可以配合map
函数一起使用。例如:
fun main() {
val list = listOf("nayufeng", "wangruike", "ningmengcha")
val newList = list.filter {it.length <= 9}.map { it.uppercase() }
for (i in newList) {
println(i)
}
}
any
和all
:判断结婚中是否存在/全部元素满足条件。
2.2.3 Java函数式API
在Kotlin中调用Java方法时也可以使用函数式API,要求是:该Java方法跖接受一个Java单抽象方法接口参数(即接口中只有一个待实现方法)。
他妈的,没学过Java,书上的例子什么匿名泪实例的看不太懂。反正Kotlin更简单就对了。
2.3 空指针检查
Kotlin默认参数都必须是非空的。Kotlin将空指针异常的检查提前到了编译时期,如果程序存在空指针异常的风险,那么在编译时期就会直接报错,修正后才可以运行。
那如果想要需要某个参数或变量可空怎么办?只需要在类名后加一个问号就可以了。例如:
fun main() {
doStudy(null)
}
fun doStudy(study: Study?){
if (study != null){
study.readbooks()
study.doHomework()
}
}
注意:如果这里没有加if判断语句来判断study是否为空,那么编译是无法通过的。
2.3.1 判🈳️辅助工具
?.
操作符:当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。例如
if (a != null) {
a.doSomething()
}
这段代码用该操作符可以简单表示为:
a?.doSomething()
?:
操作符:左右两边各接受一个表达式。如果左边表达式结果不为空就返回左边表达式的结果;否则返回右边表达式的结果。例如
val c = if (a != null) {
a
} else {
b
}
这段代码用该操作符可以简单表示为:
val c = a ?: b
!!
:非空断言工具:加在参数后面表示我确信这个参数不可能为空。let
:这是一个函数,提供了函数式API的编程接口,并将原始调用对象作为参数传递到了Lambda表达式中。那么它和空指针检查有什么关系呢?请看:上文的Study函数如果改用?.
表达式,代码会是这样的:
fun doStudy(study: Study?){
study?.readbooks()
study?.doHomework()
}
实际上,每个?.
操作符都是一次if判断语句,因此这里本来只需要一个if,但受限于?.
本身的性质,还是啰嗦地执行了两次判断。但是我们可以通过使用let
函数来改写一下:
fun doStudy(study : Study?){
study?.let {
it.readbooks()
it.doHomework()
}
}
同时,let
函数还可以解决全局变量的判空问题。全局变量随时可能被其他线程修改,所以即使用if做了判空处理也无法保证其函数体内的参数不为空。
2.4 Kotlin中的小魔术
2.4.1 字符串内嵌表达式
在字符串中用${ }
嵌入表达式。当表达式中只有一个变量时,还可以将大括号省略。例如:
val brand = "Huawei"
val price = 9999
println("Cellphone(brand = $brand, price = $price)")
2.4.2 函数参数的默认值
直接看代码吧。
fun printParams(num: Int = 100, str: String){
println("num is $num, str is $str")
}
printParams(srt = "world") // 这里必须要写str=,不然kotlin默认将“world”传给第一个参数