《第一行代码》
第2章
2.1 Kotlin语言介绍
…
2.2 如果运行Kotlin
2.3 变成之本:变量和函数
2.3.1 变量
- val(value的简写):不可变的变量。初始赋值后再也不能重新复制,对应Java中的fianl变量
- var(variable的简写):可变的变量。初始复制之后仍然可以在被重新赋值,对应Java中的非final变量
- 变量名:变量类型:显示地声明变量类型
2.3.2 函数
函数和方法都是同一个概念。函数:function,方法:method 没有区别
Kotlin中函数的叫法更普遍
fun 方法名(参数): 返回值类型{
return 返回值
}
fun largerNumber(num1: Int, num2: Int): Int{
return max(num1, num2)
}
// 语法糖
// 当函数中只有一行代码:函数体可省略
// 中间用等号(=)连接
// return 关键字也可省略,等于号足以表达返回值的意思
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
// Kotlin出色的推导机制,max函数返回的是一个Int值
// largerNumber()函数的尾部又使用等号连接max()函数
// Kotlin可以推导出largerNumber()函数的返回值必然是一个Int值
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
2.4 程序的逻辑控制
2.4.1 if条件语句
// 和Java几乎一样
fun largerNumber(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2){
value = num1
} else {
value = num2
}
}
// if有返回值
// 每个条件的最后一行代码作为返回值
fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
2.4.2 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
}
// when语句允许传入任意类型的参数,然后可以在when的结构体中定义一系列的条件
// 匹配值 -> { 执行逻辑 }
fun getScore(name: String) = when (name) {
"Tom" ->87
"Jim" ->77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
// 当执行逻辑只有一行时{ }可以省略
// when还允许类型匹配
// is相当于java中的instanceof关键字
// Number类型时Kotlin内置的一个抽象类,Int、Long、Float、Double等与数字相关的类都是它的子类
fun checkNumber(num: Number){
when (num){
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
// 不带参数用法
// Kotlin中判断字符串或对象是否相等可以直接使用==关键字
fun getScore(name: String) = when {
name == "Tom" ->87
name == "Jim" ->77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
// 有些情况必须使用不带参数的when
fun getScore(name: String) = when {
name.startsWith("Tom") ->87
name.startsWith("Jim") ->77
name.startsWith("Jack") -> 95
name.startsWith("Lily") -> 100
else -> 0
}
2.4.3 循环语句
和Java一样提供了while循环和for循环,其中while循环不管使用语法还是使用技巧上都和Java中的while循环没有任何区别,因此直接跳过while循环
// 创建0-10的区间,并且两端都是闭区间,一位置0到10这两端点都是包含在区间中的
// [0,10]
// .. 创建两端闭区间的关键字
val rnge = 0..10
// for-in循环遍历
for main(){
for(i in 0..10){
println(i)
}
}
// 左闭右开区间
// 0到10的左闭右开区间
// [0,10)
val range = 0 until 10
// for-in默认每次循环执行时会在区间范围内递增1
// step跳过其中一些元素
fun main(){
for(i in 0 until 10 step 2){
println(i)
}
}
// ..和until关键字都要求区间的左端必须小于等于区间的右端
// 也就是说这两种关键字的创建都是一个升序的区间
// 创建一个降序区间 downTo
// [10,1]的降序区间
// 也可使用 step关键字
fun main(){
for(i in 10 downTo 1){
prinln(i)
}
}
2.5 面向对象编程
2.5.1 类与对象
class Person{
var name = ""
var age = 0
fun eat(){
println(name + "is eating.He is" + age + " year.old.")
}
}
// 实例化Person类,省区了Java中的new关键字
val p = Person()
2.5.2 继承与构造函数
// 声明Person类可被继承
open class Person{
...
}
class Student : Person(){
var sno = ""
var grade = 0
}
// 主构造函数
// 每个类默认都会有一个不带参数的主构造函数
// 特点是没有函数体,直接定义在类名后面即可
// 由于构造函数的参数是在创建实例的时候传入的,不像之前的写法那样还得重新赋值,因此我们可以将参数全部声明成val
// 子类实例化时会调用父类构造
// 子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定
// 48行问题:也就是说这个继承类后面的括号是服务于主构造函数的,当没有显示声明主构造函数,并写了次构造时,继承类后面的括号作用也就失效了。
class Student(val sno: String, val grade: Int) : Person(){
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
var student = Student("a123",5)
// 继承
open class Person(val name: String, val age: Int){
...
}
// 在主构造函数中添加name和age字段时,不能再将他们声明成val或var,因为var或val会将参数自动成为该类的字段,会导致和父类中同名的name和age字段造成冲突。
// 因此,这里的name和age参数前面不用加任何关键字,让他作用域仅限定在主构造函数当中
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age){
}
// 次构造函数
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age){
// this关键字调用主构造函数
constructor(name: String, age: Int) : this("", 0, name, age){
}
// this关键字调用次构造函数
constructor() : this("", 0){
}
}
class Student : Person{
// 没有现实定义主构造函数,同时又因为定义了次构造函数
// !!!此时Student类是没有主构造函数的
// 此时继承Person类时页就不需要再加上括号了
// 次构造函数只能直接调用父类的构造函数 super关键字!
constructor(name: String, age: Int) : super(name, age){
}
}
2.5.3 接口
与Java几乎完全一致。单继承多实现
insterface Study{
fun readBooks()
fun doHomework()
}
// Kotlin中继承与实现统一使用冒号,中间用逗号进行分隔
// 接口后面不用加括号,因为接口没有构造函数
class Student(name: String, age: Int) : Person(name, age),Study{
override fun readBooks(){
...
}
override fun doHomework(){
...
}
}
fun main(){
val student = Student("Jack", 19)
doStudy(student)
}
fun doStudy(study: Study){
study.readBooks()
study.doHomework
}
// Kotlin允许对接口中定义的函数进行默认实现(Java1.8也开始支持)
// 如果有默认实现,就不强制子类实现
insterface Study{
fun readBooks()
fun doHomework(){
...
}
}
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
2.5.4 数据类与单例类
// data关键字
// Kotlin会根据主构造函数中的参数将equals() hashCode() toString() 等固定且无实际逻辑意义的方法自动生成
data class Cellphone(val brand: String, val price: Double)
// object关键字
// 单例类
object Singleton{
fun singletonTest(){
...
}
}
// 单例类调用
Singleton.singletonTest()
2.6 Lambda 编程
2.6.1 集合的创建及遍历
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
...
// Kotlin内置listOf()函数来简化初始化集合
// listOf()函数创建的时不可变的集合
val list = listOf("Apple","Banana")
for(fruit in list){
println(fruit)
}
// mutableListOf()函数创建可变集合
val list = mutableListOf("Apple","Banana")
list.add("Watermelon")
for...
// setOf()和mutableSetOf()和List集合用法一样
// 与Java相似创建map
val map = HashMap<String, Int>()
map.put("Apple", 1)
...
// Kotlin并不建议使用put()和get()方法来对Map进行添加和读取数据操作
// 添加数据
map["Apple"] = 1
// 读取数据
val number = map["Apple"]
// Kotlin也提供了一对 mapOf()和mutableMapOf()函数来简化Map用法
// to并不是关键字,而是一个infix函数
val map = mapOf("Apple" to 1, "Banana" to 2)
// 遍历map集合
for((fruit, number) in map){
println("fruit is " + fruit + ", number is " + number)
}
2.6.2 集合的函数时API
// Lambda就是一小段可以作为参数传递的代码
// Kotlin对Lambda没有进行限制,但时通常不建议再Lambda表达式中编写太长的代码,否则可能会影响代码的可读性
// Lambda表达式的语法结构:
// {参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
// 如果有参数传入到Lambda表达式中的话,需要声明参数列表,参数列表结尾使用->符号,表示参数列表结束以及函数体的开始,函数体中可以编写任意行代码(虽然不建议编写太长的代码),并且最后一行代码会自动作为Lambda表达式的返回值
val list = listOf("Apple", "Banana", "Orange", "Pear")
var lambda = { fruit: String -> fruit.length }
val maxlengthFruit = list.maxBy(lambda)
// 直接将lambda表达式传入maxBy
val maxlengthFruit = list.maxBy({ fruit: String -> fruit.length })
// Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面
val maxlengthFruit = list.maxBy(){ fruit: String -> fruit.length }
// Lambda参数时唯一一个参数的话,可以将函数的括号省略
val maxlengthFruit = list.maxBy{ fruit: String -> fruit.length }
// Lambda表达式中的参数列表大多数情况下不必声明参数类型
val maxlengthFruit = list.maxBy{ fruit -> fruit.length }
// Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键词来代替
val maxlengthFruit = list.maxBy{ it.length }
集合中比较常用的函数式API
// map:将集合中的每个元素都映射成一个另外的值,映射的规则再Lambda表达式中指定,最终生成新的集合
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear")
// 转换成大写
val newList = list.map{ it.toUpperCase() }
for (fruit in newList){
println(fruit)
}
}
// filter:过滤集合中的数据
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear")
// 先过滤字母长度小于5的,再转换成大写
val newList = list.filter{ it.length <= 5 }.map { it.UpperCase()}
for (fruit in newList){
println(fruit)
}
}
// any:判断集合中是否至少存在一个元素满足指定条件
// all:判断集合中是否所有元素都满足指定条件
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear")
val anyResult = list.any{ it.length <=5 }
val allResult = list.all{ it.length <=5 }
}
2.6.3 Java函数式API的使用
在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API
单抽象方法接口:接口中只有一个抽象方法,如果有多个,则无法使用函数式API
// Java:
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread is running")
}
})
// Kotlin:
// Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候不能再使用new了,而是改成了object关键字
// 然而并没有比Java简化到哪里去
// Thread类的构造方法时符合Java函数式API的使用条件的
Thread(Object : Runnable {
override fun run(){
println("Thread is running")
}
})
// 简化:Runnable是单抽象方法接口
Thread(Runnable {
println("Thread is running")
}).start()
// 简化:Thread只有一个参数
Thread({
println("Thread is running")
}).start()
// 简化:Lambda表达式是方法的最后一个参数时
Thread{
println("Thread is running")
}.start()
2.7 空指针检查
Java判空方式
if(study != null){
study.readBooks();
study.doHomework();
}
2.7.1 可空类型系统
Kotlin默认所有的参数和变量都不可为空
Kotlin的判空方式
-
?
-
放置在类名后面表示可为空类型:需要处理空指针异常
fun doStudy(study: Study?){ if(study != null){ study.readBooks() study.doHomework() } }
-
2.7.2 判空辅助工具
Kotlin提供了一系列的辅助工具,使开发者能够更轻松的进行判空处理。
-
?.
-
当对象不为空时正常调用相应方法,当对象为空时则什么都不做
study?.readBooks(); study?.doHomework();
-
-
?:
-
左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。
// text不为空时返回length,否则返回0 fun getTextlength(text: String)text?.length ?: 0
-
-
!!
- 当我们可能从逻辑上已经将空指针异常处理了,但Kotlin的编译器并不知道,还是会编译出错
// kotlin忘了叫啥,java叫属性、字段、域 var content: String?= "hello" // main方法,调用printUpperCase fun main(){ if(content != null){ printUperCase() } } // 编译不通过!!! // 无法运行,方法内不知道在main方法中已经对content变量进行非空检查 // content转换成大写并打印 fun printUpperCase(){ val upperCase = content.toUpperCase() println(upperCase) } // 使用“!!” // 告诉Kotlin,我非常确信这里的对象不会为空 fun printUpperCase(){ val upperCase = content!!.toUpperCase() println(upperCase) }
- 当我们可能从逻辑上已经将空指针异常处理了,但Kotlin的编译器并不知道,还是会编译出错
-
let
-
函数(方法),提供了函数式API的编程接口并将原始调用对象作为参数传递到Lambda表达式中
// obj=obj2 obj.let{ obj2 -> // 编写具体的业务逻辑 }
-
let特性配合**?.**操作符可以在空指针检查时起到很大作用
fun doStudy(study: Study?){ study?.let{ stu -> stu.readBooks() stu.doHomework } } // 优化:当Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,直接使用it关键字来代替即可 fun doStudy(study: Study?){ study?.let{ it.readBooks() it.doHomework() } }
-
处理全局变量的判空
var study: Study? = null // 报错! // 全局变量的值随时都有可能被其他线程锁修改(比如赋值为null),即使做了判空处理,也仍然无法保证if语句中的study变量没有空指针风险。 // 从这点上也能体现出let函数的优势 fun doStudy(){ if(stu != null){ study.readBooks() study.doHomework() } }
-
2.8 Kotlin中的小魔术
2.8.1 字符串内嵌表达式
-
${}
// 表达式执行结果替代这部分内容 "hello,${obj.name}. nice to meet you!" // 当表达式中仅有一个变量时,省略大括号 "hello,${obj.name}. nice to meet you!"
2.8.2 函数的参数默认值
fun printParams(num: Int, str: String = "hello"){
println("num is $num,str is $str")
}
printParams(100)
// 当第一个参数有默认值
fun printParams(num: Int = 100, str: String){
println("num is $num,str is $str")
}
printParams(str = "word")