Kotlin学习笔记

第一阶段

基本介绍

Kotlin成为Android的第一语言

Kotlin语言编译后产生字节码,JVM产生指令对,操作系统进行命令式的执行

Kotlin集聚各个语言的精华于一身,走全栈语言之路

val只读变量

1
2
3
4
5
6
7
val a:String = "111"
/**
* val只读,不可以改变
* 下面一行加上错误
* a = "222"
*/
println(a)

var可变变量

1
2
3
4
5
6
/**
* var可变变量,下面写法正确
*/
var a:String = "111"//这里的:String可以省略,kotlin会自动进行类型的推断
a = "222"
println(a)//println打印

Java语言有两种数据类型,基本数据类型(int,double等等)和引用类型(String)

Kotlin语言只有一种数据类型,看起来都是引用类型,实际上,编译器会在Java字节码中修改为基本类型

查看字节码

range表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
val number = 80
/**
* number in 10..100表示number大于等于10并且小于等于100
*/
if (number in 10..100) {
println("在10到100")
} else if (number in 0..9) {
println("在0到9")
} else {
println("大于100")
}

//还有!in表示不在。。。范围内

when表达式

相当于Java中的switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Java的if是语句
* Kotlin的if是表达式,有返回值的
*/
val week = 7
val info = when (week) {
1 -> "今天是星期一"
2 -> "今天是星期一"
3 -> "今天是星期一"
4 -> "今天是星期一"
5 -> "今天是星期一"
else -> {
/**
*这里如果直接用括号括起来会执行里面的语句,单行可以不写括号,多行才需要,但有括号并且有正常的String赋值给info,
*那么info为Any也就相当于Java中的Object(任何类型的父类)
*Unit代替了Java中的void关键字,是一个类
*println("今天是周末")
*/
"今天是周末"
}
}
println(info)

String模板

1
2
3
4
5
6
7
8
9
10
11
12
val time = 5
val garden = "中山公园"
println("今天天气晴朗,是星期$time,去${garden}玩")
/**
* ${变量}后面可以再跟相同内容,也就是不造成误解,相当于分隔开,$变量后面不可以直接跟具有误解的内容
*/

val isLogin = true
println("用户登录结果为:${if (isLogin) "登录成功" else "登录失败"}")
/**
* Kotlin的if是表达式,可以更加灵活
*/

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 其中private为修饰符,不写则默认为public
* fun为函数声明关键字
* method为函数名
* age: Int, name: String为函数参数
* 括号后冒号后跟的Int为返回类型
*/
private fun method(age: Int, name: String): Int {
println("年龄为$age,姓名为$name")
return 200
}

/**
* 上面代码转换成为java如下
* private static final int method(int age,String name){
* String var = "年龄为" + age + ",姓名为" + name;
* System.out.println(var);
* return 200;
* }
*/

函数参数的默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun main(args: Array<String>) {
action01("wangwu",30)
action02("wangwu")
action03()
}

private fun action01(name:String,age: Int){
println("名字为$name,年龄为$age")
}

/**
* 这里可以固定一个默认值,age不传入就为77,当然也可以传值进来
*/
private fun action02(name:String,age: Int=77){
println("名字为$name,年龄为$age")
}

private fun action03(name:String="Wangwu",age: Int=77){
println("名字为$name,年龄为$age")
}

具名函数参数

1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
login(username = "111", password = "222", age = 22, phoneNumber = "12345")
}

//默认返回类型为Unit,可以不加,相当于Java的void关键字(无参数返回),但Unit是Kotlin中的一个类
private fun login(username:String,password:String,phoneNumber:String,age:Int){
println("成功")
}

Nothing类型特点

1
2
3
4
5
6
7
8
9
10
11
12
fun main(args: Array<String>) {
show(-1)
}

//抛出异常,TODO()不是注释提示,会终止程序
private fun show(number: Int) {
when (number) {
-1 -> TODO("没有这个分数")
in 0..59 -> println("不及格")
in 60..70 -> println("成绩还可以")
}
}

反引号中函数名的特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fun main(args: Array<String>) {
//第一种情况
测试环境测试("111","222")

//第二种情况
/**
* 有一个Java类中有两个方法为in和is,方法名在Java中不是关键字,但是在Kotlin中是关键字,按照下面调用会出问题
* Test.in()
* Test.is()
*/
Test.`in`()

//第三种情况,很少发生,作用写在下面
`5553343`()
}


private fun `测试环境测试`(username:String,password:String){
println("username:$username,password:$password")
}

private fun `5553343`(){
//写了很复制的功能,核心功能
}

//公司加密私有的文档,对5553343函数进行了说明,说明函数的作用,外人就是反编译出代码,看到这个数字也不知道具体的含义

第二阶段

匿名函数

1
2
3
4
5
6
7
8
9
10
11
fun main() {
/* val len = "Test".count()//获取字符串长度
println(len)*/


//下面这个括号的部分就是函数,匿名函数
val len = "Test".count {
it == 'e'//这里count拿到每个字符,并把它给it,这里字符为e才计算长度
}
println(len)
}

函数类型和隐式返回

匿名函数的声明和实现以及调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   //1.函数输入输出的声明
val test: () -> String

//2.对上面函数的实现
test = {
//这里的括号内的内容相当于下面的return部分的内容
//注意匿名函数不要写return,最后一行就是返回值
val tt = "66"
"test$tt"
//这里隐式返回,并且自动推断返回类型是否正确,这里返回int类型就会报错
}

//3.调用函数
println(test())

/**
* 上面大致等价于下面
* fun test():String{
* return "test"
* }
*/

函数参数

匿名函数简洁写法,并且需要传入参数

1
2
3
4
5
6
7
8
9
10
//上面内容是拆解的写法,这里把上面的第一步和第二步进行合并书写,并且进行传参
val test: (Int, Int, Int) -> String = {
//按照正常的函数书写的方法,这里的三个Int后面应该跟变量名的,但是这里没有,就需要按照下面写法
num1, num2, num3 ->
val tt = "传入参数内容如下:"
println("${tt}num1:${num1},num2:${num2},num3:${num3}")
"${tt}num1:${num1},num2:${num2},num3:${num3}"
}
test.invoke(1, 2, 3)//等价于下面那行代码
test(1, 2, 3)

it关键字的特点

匿名函数中的it关键字

1
2
3
4
5
//匿名函数是只有一个入参,就有一个it
val test2: (String) -> String = {
"自带一个it,为传入的内容,为$it"
}
println(test2("111"))

匿名函数的类型推断

如果不能推断出唯一的类型,那么就推断为Any

1
2
3
4
5
6
7
8
//上面的写法还需要手动指定返回值的类型,其实可以自动推断,需要按照下面的写法,传参的前面加冒号需要指定返回值类型
//这里写一个匿名函数,类型推断为String
val method1 = { val1: Double, val2: Float, val3: String ->
//上面的val1,val2,val3相当于入参
/*5555//自动推断为Int类型返回值*/
"传入参数分别为:$val1,$val2,$val3"
}
println(method1(1.34,123f,"1da"))

lambda

匿名函数其实就是lambda表达式

函数定义参数是函数的函数

在Java中可以通过接口来实现,在kotlin可以更加简单地实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
fun main() {
//这里模拟登录为例
//最后一个参数可以写到外面去
loginAction("wuleizhenshang", "10086") { msg: String, code: Int ->
println("登录情况如下:$msg,$code")
}

//上面写法等价于下面这种写法
loginAction("wuleizhenshang", "10086",{ msg: String, code: Int ->
println("登录情况如下:$msg,$code")
})

//还可以等价为
loginAction("wuleizhenshang", "10086", responseResult = { msg: String, code: Int ->
println("登录情况如下:$msg,$code")
})
}

//模拟:数据库为SQLServer
const val USER_NAME_DB = "wuleizhenshang"
const val USER_PWD_DB = "10086"

//登录API
//这里responseResult:(String,Int)->Unit相当于Java的回调接口,回调给用户登录成功与否的状态
fun loginAction(username: String, password: String, responseResult: (String, Int) -> Unit) {
if (username == null || password == null) {
//这里的==相当于java中的equals()
TODO("用户名或密码为空")//出现问题,终止程序
}

//做更多的前端校验
if (username.length > 3 && password.length > 3) {
if (webServiceLoginApi(username, password)) {
//登录成功,还可以做更多事情
responseResult("login success", 200)
} else {
//登录失败的逻辑处理
responseResult("login fail", 404)
}
} else {
TODO("用户名或密码不合格")//出现问题,终止程序
}
}

//登录的API暴露,模拟服务器
private fun webServiceLoginApi(username: String, password: String): Boolean {
//kt的if是表达式,java的if是语句
return username == USER_NAME_DB && password == USER_PWD_DB
}

内联学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package s2

fun main() {
//这里模拟登录为例
//最后一个参数可以写到外面去
loginAction("wuleizhenshang", "10086") { msg: String, code: Int ->
println("登录情况如下:$msg,$code")
}

//上面写法等价于下面这种写法
loginAction("wuleizhenshang", "10086", { msg: String, code: Int ->
println("登录情况如下:$msg,$code")
})

//还可以等价为
loginAction("wuleizhenshang", "10086", responseResult = { msg: String, code: Int ->
println("登录情况如下:$msg,$code")
})

}

//模拟:数据库为SQLServer
const val USER_NAME_DB = "wuleizhenshang"
const val USER_PWD_DB = "10086"

//登录API
//这里responseResult:(String,Int)->Unit相当于Java的回调接口,回调给用户登录成功与否的状态
//此函数使用lambda作为参数,就需要声明为内联
//如果此函数没有使用内联,那么在调用端会生成多个对象来完成lambda调用,会造成性能损耗(内部java会这么做)
//这里的inline就是使用内联,相当于转换成C++的#define,会自动替换为直接调用,而不是不断创建对象进行调用,没有函数开辟损耗
inline fun loginAction(username: String, password: String, responseResult: (String, Int) -> Unit) {
if (username == null || password == null) {
//这里的==相当于java中的equals()
TODO("用户名或密码为空")//出现问题,终止程序
}

//做更多的前端校验
if (username.length > 3 && password.length > 3) {
if (webServiceLoginApi(username, password)) {
//登录成功,还可以做更多事情
responseResult("login success", 200)
} else {
//登录失败的逻辑处理
responseResult("login fail", 404)
}
} else {
TODO("用户名或密码不合格")//出现问题,终止程序
}
}

//登录的API暴露,模拟服务器
//此函数没有使用lambda作为参数,就不需要声明为内联
fun webServiceLoginApi(username: String, password: String): Boolean {
//kt的if是表达式,java的if是语句
return username == USER_NAME_DB && password == USER_PWD_DB
}

不使用内联转换成Java如下

image-20240224214102272

使用内联转换成Java如下

函数引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const val USER_NAME = "wuleizhenshang"
const val USER_PWD = "123456"

//函数引用
fun main() {
//lambda属于函数类型的对象,需要把methodResponseResult普通函数变成函数类型的对象(函数引用)
login("wuleizhenshang", "123456", ::methodResponseResult)
}

fun methodResponseResult(msg: String, code: Int) {
println("最终登录结果为:$code,$msg")
}

//这里原本是一个lambda
inline fun login(name: String, pwd: String, responseResult: (String, Int) -> Unit) {
if (USER_NAME == name && USER_PWD == pwd) {
//登录成功
responseResult("登录成功", 200)
} else {
//登录失败
responseResult("登录失败", 404)
}
}

函数类型作为返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val show1 = show("tt")//这里返回一个匿名函数
println(show1("wuleizhenshang", 20))//然后调用show1的返回的函数
}

//这里再返回一个匿名函数,需要定义好入参和出参,不能直接使用类型推断完成
fun show(info: String): (String, Int) -> String {
println("我是show函数,info:$info")

//这里return一个函数类型,是一个匿名函数
//这里不能
return { name: String, age: Int ->
"name:$name,age:$age"
}
}

匿名函数和具名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun main() {
//匿名函数
showPersonInfo("wuleizhenshang", 20, '男') {

}

//具名函数
showPersonInfo("wuleizhenshang", 20, '男', ::showResultImpl)
}


//具体的函数,需要把函数转成对象,::转成对象
fun showResultImpl(result: String) {
println("显示结果:$result")
}


inline fun showPersonInfo(name: String, age: Int, sex: Char, showResult: (String) -> Unit) {
val result = "name$name,age:$age,sex:$sex"
showResult(result)
}

//Java完成匿名和具名,其实就是内部类直接实现接口和让一个类去继承接口并实现方法

第三阶段

可空性特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
//第一种情况,默认不可空类型,不能随意赋值为null
*/
/**
* 下面这两行代码会报错
* var name:String = "wuleizhenshang"
* name = null
*//*


//第二种情况,声明时指定为可空类型
//下面代码就不会报错
var name2: String?
name2 = null
println(name2)
}

安全调用操作符

1
2
3
4
5
6
7
8
9
fun main() {
var name: String? = "zhangsan"
name = null
//name.capitalize() name是可空类型,想要使用name,必须给出补救措施
name?.capitalize()//如果name是null,那么?后面的内容就不会执行
println(name)
name = "hello"
println(name)
}

带let的安全调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
var name: String? = null
//name = ""
//name = "wueleizhenshang"
val r = name?.let {
//可以将name拿到括号内进行使用,默认有it参数,并且name为null那么后面不执行,如果能够执行,那么括号里面it不为null
if (it.isBlank()){
//如果name为空值,也就是"",没有内容,那么返回
"Default"
}else{
it
}
}
println(r)
}

非空断言操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main(){
var name: String? = "zhangsan"
name = null
//name.capitalize() name是可空类型,想要使用name,必须给出补救措施

//1.这里已经学习了一种补救措施了,还有就是断言进行补救
name?.capitalize()//如果name是null,那么?后面的内容就不会执行
println(name)

//2.
val r = name!!.capitalize()//两个感叹号的意思就是不管name是不是null都执行,就和Java一样,这里会出现空指针异常,不为null当然没什么问题
//如果百分百能保证name不为null,那么就可以使用断言!!,否则有空指针异常的风险
println(r)
}

使用if判断null情况

1
2
3
4
5
6
7
8
9
10
fun main(){
//编译器自动通过
var name: String? = null
if (name!=null){
val r = name.capitalize()
println(r)
}else{
println("name为null")
}
}

空合并操作符

1
2
3
4
5
6
7
8
9
10
11
fun main() {
var info: String? = "wuleizhenshang"
info = null
println(info ?: "原来你是null")
//如果为null就执行?:后面的内容,不为null就正常操作


//使用前面的let函数+空合并操作符完成下面的内容
println(info?.let { "{$it}" } ?: "原来你是null")
//不为null就执行let里面的内容,为null原本会直接输出null,后面使用?:让为null输出原来你是null
}

异常处理与自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {
try {
var info: String? = null

//检查异常
checkException(info)

println(info!!.length)//这里肯定会出错的,断言执行但为null
} catch (e: Exception) {
println("有问题$e")
}
}

//检查异常
fun checkException(info: String?) {
//这里info可能为null
info ?: throw CustomException()//为null抛出异常
}

//自定义异常
class CustomException : IllegalArgumentException("你的代码不严谨")

先决条件函数

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
var value: String? = null

//checkNotNull(value)//自带,为null抛出异常

//requireNotNull(value)//自带,为null抛出异常

//判断布尔类型也可以,使用下面方法

var bool :Boolean = false
require(bool)//自带,为false抛出异常
}

KT中的substring

1
2
3
4
5
6
fun main() {
//截取函数,需要一个范围
val index1 = INFO.indexOf('i')
println(INFO.substring(0, index1))//左闭右开
println(INFO.substring(0 until index1))//跟上面等价,KT基本用这种方式
}

KT中的split

1
2
3
4
5
6
7
8
fun main() {
//自动类型推断 list == List<String>
val list = INFO.split(" ")

//C++结构
val (v1, v2, v3, v4) = list//这里能分成4个元素
println("v1:$v1,v2:$v2,v3:$v3,v4:$v4")
}

KT中的replace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
val sourcePwd = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
println("原本密码是:$sourcePwd")

val r1 = sourcePwd.replace(Regex("[AKMNO]")) {
when (it.value) {
//这里的每一个字符 A B C D
"A" -> "9"
"K" -> "7"
"M" -> "5"
"N" -> "1"
"O" -> "6"
else -> it.value
}
}

println("加密后的密码是:$r1")

}

==和===比较操作

1
2
3
4
5
6
7
8
9
fun main() {
//kotlin中的==比较的值就是数值是否相等,===比较的是两个对象地址是否相等
//==相当于Java中的equals
val a: Int = 999
val b: Int? = a
val c: Int? = a
println(b == c) //true
println(b === c) //false
}

字符串遍历操作

1
2
3
4
5
6
fun main(){
val pwd = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
pwd.forEach {
println("所有字符:$it")
}
}

数字类型的安全转换函数

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val number: Int = "666".toInt()
println(number)
/**
* 下面的字符串是double类型,转换成Int类型会报错
* val number2: Int = "666.66".toInt()
* println(number2)
*/

val number3: Int? = "666.66".toIntOrNull()//转换成功就返回,不成功返回null
println(number3)
}

Double转Int

1
2
3
4
5
6
7
8
9
10
fun main() {
println(65.4353.toInt())//65 四舍五入
println(65.4353.roundToInt())//65 四舍五入
println(65.8353.roundToInt())//66 四舍五入

//roundToInt()函数保证double转Int,保持四舍五入效果

val r = "%.3f".format(64.5732)//返回字符串类型,但保留3位小数
println(r)
}

apply内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
fun main() {
val info = "name is Wuleizhenshang"

//普通方式
println("info字符串的长度是:${info.length}")
println("info最后一个字符是:${info[info.length - 1]}")
println("info全部转换成小写:${info.toLowerCase()}")

//apply内置函数的方式,apply始终返回info本身的String类型
val infoNew: String = info.apply {
println("info字符串的长度是:${length}")
println("info最后一个字符是:${this[length - 1]}")
println("info全部转换成小写:${toLowerCase()}")
}
println(infoNew)

//真正使用apply函数的写法规则如下:
//info.apply特点:因为始终返回info本身,所以可以链式调用
info.apply {
println("info长度为:$length")
}.apply {
println("最后一个字符为${this[length - 1]}")
}.apply {
println("全部转成小写是${toLowerCase()}")
}


//普通写法
val file = File("D:\\a.txt")
file.setExecutable(true)
file.setReadable(true)
println(file.readLines())

//apply写法
file.apply {
setExecutable(true)
}.apply {
setReadable(true)
}.apply {
println(file.readLines())
}
}